#!/usr/bin/perl
#
# addftpuser: a utility to create an anonymous FTP account
#
# Copyright (C) 1995 Peter Tobias <tobias@et-inf.fho-emden.de>
#
#    addftpuser 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.
#
#    addftpuser 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 addftpuser; if not, write to the Free Software Foundation,
#    Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

$version = '$Revision: 1.8 $';
$version =~ s/\S+: (\S+) \$/$1/;

$plock = "/etc/ptmp";        # standard method of locking the passwd file
$uid = 10;                   # start searching for a free uid at uid 10
$default_home = "/home/ftp"; # the default ftp home directory
$default_dir_mode = 0755;    # the default directory permissions
$group = "staff";            # the default group for the ftp hierarchy


die "You must be root to run this script.\n" if ($> != 0);

# strip directory from the filename
$0 =~ s#.*/##;

# don't change the permissions
umask(000);

while ($ARGV[0] =~ m/^-/) {
  $_ = shift(@ARGV);
  if (/--help$/) {
    &usage;
  } elsif (/--version$/) {
    &version;
  } elsif (/--group$/) {
    $group = shift(@ARGV);
    die "$0: Option group requires an argument\n" unless ($group);
  } else {
    print "$0: Unknown option: $_\n";
    print "$0: Try `$0 --help' for more information.\n";
    exit(1);
  }
}

if (@ARGV) {
  print "$0: Unknown argument: @ARGV\n";
  print "$0: Try `$0 --help' for more information.\n";
  exit(1);
}

# check if the user "ftp" already exists
setpwent;
if ($home = (getpwnam("ftp"))[7]) {
   if (-d $home) {
     exit(0);
   } else {
     print "\nYou already have an anonymous FTP account, but the FTP home\n";
     print "directory [$home] does not exist!\n\nDo you want to create it? [y] ";

     if (<STDIN> =~ /^n/i) {
       exit(0);
     }
     $have_passwd_entry = 1;
   }
}
endpwent;

unless ($have_passwd_entry) {

  print "\nDo you want to set up an anonymous FTP account now? [n] ";
  if (<STDIN> =~ /^[^y]/i) {
    print "\nYou can add an anonymous FTP account later using /usr/sbin/addftpuser\n";
    exit(0);
  }

  # find the first free uid
  setpwent;
  while (getpwuid($uid)) {
    ++$uid;
  }
  endpwent;

  # we need the gid for the ftp account
  setgrent;
   $gid = (getgrnam($group))[2] ||
     die "$0: Oops, I can't find your \"$group\" group\n";
  endgrent;

  # we need the name of the home directory
  while(1) {
    $home = "";
    print "\nEnter the name of the FTP home directory: [$default_home] ";
    chop($home = <STDIN>);
    $home =~ s/\s+//g;         # remove spaces ...
    $home =~ s#//+#/#g;        # we don't want things like // in $home
    $home =~ s#/$##;           # remove trailing slash if it exists

    if (! $home) {
      $home = $default_home;
    }

    if (! ($home =~ m#^/#)) {
      print "\nYou have to use an absolute path for the home directory.\n";
      print "In other words, it must begin with a slash.\n";
      next;
    }
    if (-d $home) {
      print "\n$home does already exist, should I use it? [n] ";
      if (<STDIN> =~ /^[^y]/i) {
        next;
      }
    }
    last;
  }

  print "\nDo you want to create a directory for user uploads? [n] ";
  if (<STDIN> =~ /^[^y]/i) {
    $want_incoming = 0;
  } else {
    $want_incoming = 1;
    print "\nPlease look at /etc/ftpd/ftpaccess and its manual page for\n";
    print "further information on how to make /pub/incoming more secure.\n";
  }

  # don't let the user interrupt us
  &ignore_signals;

  # create a lockfile, add the "ftp" user and remove the lockfile
  if (-f $plock) {
    die "$0: /etc/passwd is locked. Try again later.\n";
  }
  link("/etc/passwd", "$plock") || die "$0: Can't create lockfile\n";

  print "\nCreating anonymous FTP account ...\n";

  open(PASSWD, ">>/etc/passwd") || die "$0: Couldn't append to /etc/passwd\n";
    print PASSWD "ftp:*:$uid:$gid:Anonymous FTP:$home:\n";
  close(PASSWD);

  unlink($plock);
}

# don't let the user interrupt us
if($have_passwd_entry) {
  &ignore_signals;
}

# create the ftp home directory and its subdirectories
@dirs = split(/\//, $home);
shift(@dirs);    # remove the first element (it's empty because of the
                 # leading slash in $home)
pop(@dirs);      # remove the last element (we will create it later)
$done = "";

while(@dirs) {
   $element = shift(@dirs);
   unless(-d "$done/$element") {
     mkdir("$done/$element", $default_dir_mode);
   }
   $done = "$done/$element";
}

chmod(0555, "$home") || mkdir("$home", 0555) ||
	die "$0: can't mkdir $home: $!\n";
chmod(0111, "$home/bin") || mkdir("$home/bin", 0111) ||
	die "$0: can't mkdir $home/bin: $!\n";
chmod(0111, "$home/dev") || mkdir("$home/dev", 0111) ||
	die "$0: can't mkdir $home/dev: $!\n";
chmod(0111, "$home/lib") || mkdir("$home/lib", 0111) ||
	die "$0: can't mkdir $home/lib: $!\n";
chmod(0111, "$home/etc") || mkdir("$home/etc", 0111) ||
	die "$0: can't mkdir $home/etc: $!\n";
chmod(0555, "$home/pub") || mkdir("$home/pub", 0555) ||
	die "$0: can't mkdir $home/pub: $!\n";
chown(0, 0, "$home", "$home/bin", "$home/dev", "$home/lib", "$home/etc", "$home/pub");

if ($want_incoming) {
  chmod(0753, "$home/pub/incoming") || mkdir("$home/pub/incoming", 0753) ||
	die "$0: can't mkdir $home/pub/incoming: $!\n";
  chown(0, 0, "$home/pub/incoming");
}

foreach $prog ("/bin/ls", "/bin/gzip", "/bin/tar") {
  unlink("$home$prog");
  system("cp $prog $home$prog") &&
    die "$0: can't copy $prog to $home$prog. Giving up\n";
}

if (-f "/usr/bin/zip") {
  system("cp /usr/bin/zip $home/bin/zip") &&
    die "$0: can't copy /usr/bin/zip to $home/bin/zip. Giving up\n";
  chmod(0111, "/usr/bin/zip");
}

opendir(FTPBIN, "$home/bin");
  @ftpfiles = readdir(FTPBIN);
closedir(FTPBIN);

($aout, $elf) = &filetype("$home/bin", @ftpfiles);

if($elf) {
    $libc_link=&findlib(5);
    if ($libc_link) {
        $libc=readlink($libc_link) || die "$0: broken symbolic link: $!";
        ($dir = $libc_link) =~ s#[^/]+$## unless($libc =~ m#/#);
        system("cp /lib/ld-linux.so.1 $home/lib") && die "$0: can't copy ld-linux.so.1: $!\n";
        system("cp $dir$libc $home/lib") && die "$0: can't copy $dir$libc: $!\n";
        chmod(0555, "$home/lib/ld-linux.so.1", "$dir$libc");
        symlink("$libc", "$home/lib/libc.so.5");
        system("mknod -m 0666 $home/dev/zero c 1 5") && die "$0: mknod failed: $!\n";
    } else {
        print "$0: ELF binaries found but libc.so.5 not available\n";
    }
}
if($aout) {
    $libc_link=&findlib(4);
    if ($libc_link) {
        $libc = readlink($libc_link) || die "$0: broken symbolic link: $!";
        ($dir = $libc_link) =~ s#[^/]+$## unless($libc =~ m#/#);
        system("cp /lib/ld.so $home/lib") && die "$0: can't copy ld.so: $!\n";
        system("cp $dir$libc $home/lib") && die "$0: can't copy $dir$libc: $!\n";
        chmod(0555, "$home/lib/ld.so", "$dir$libc");
        symlink("$libc", "$home/lib/libc.so.4");
    } else {
        print "$0: a.out binaries found but libc.so.4 not available\n";
    }
}

system("cp /etc/ftpd/pathmsg $home/etc/pathmsg") &&
  die "$0: can't copy /etc/ftpd/pathmsg to $home/etc/pathmsg. Giving up\n";

# create the passwd file for the new anonymous ftp hierarchy
open(FPASSWD,">$home/etc/passwd");
  print FPASSWD "root:*:0:0:root::\n";
  print FPASSWD "ftp:*:$uid:$gid:Anonymous FTP::\n";
close(FPASSWD);

# create the group file for the new anonymous ftp hierarchy
open(FGROUP,">$home/etc/group");
  print FGROUP "root::0:\n";
  print FGROUP "ftp::$gid:\n";
close(FGROUP);

# fix a few permissions
chmod(0444, "$home/etc/passwd", "$home/etc/group", "$home/etc/pathmsg");
chmod(0111, "$home/bin/ls", "$home/bin/gzip", "$home/bin/tar");

# restore the default signal action. Not really necessary ...
&restore_signals;

############################################################################

sub usage {
  print "Usage: $0 [OPTION]\n\n";
  print "--group group         use this group for the anonymous FTP account\n";
  print "--help                display this help and exit\n";
  print "--version             output version information and exit\n";
  exit(0);
}

sub version {
  print "$0 $version\n";
  exit(0);
}

sub ignore_signals {
  $SIG{'HUP'} = 'IGNORE';
  $SIG{'INT'} = 'IGNORE';
  $SIG{'QUIT'} = 'IGNORE';
  $SIG{'TERM'} = 'IGNORE';
}

sub restore_signals {
  $SIG{'HUP'} = 'DEFAULT';
  $SIG{'INT'} = 'DEFAULT';
  $SIG{'QUIT'} = 'DEFAULT';
  $SIG{'TERM'} = 'DEFAULT';
}

sub findlib {
    my($v) = @_;
    open(LD, "/etc/ld.so.conf");
        chomp(@ld=<LD>);
    close(LD);
    unshift(@ld, ("/lib", "/usr/lib"));

    while(@ld) {
        $_ = shift(@ld);
        return("$_/libc.so.$v") if (-f "$_/libc.so.$v");
    }
    return(0);
}

sub filetype {
    my($dir, @files) = @_;
    my($aout, $elf, $string);
    while(@files) {
        $_ = shift(@files);
        next if ($_ eq "." or $_ eq "..");
        open(CH, "$dir/$_");
        read(CH, $string, 4);
        if ($string =~ m/\177ELF/) {
           ++$elf;
        } elsif ($string =~ m/..\144./) {
           ++$aout;
        }
        close(CH);
        undef($string);
    }
    return($aout, $elf);
}
