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

$version = '$Revision: 1.13 $';
$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
$pathmsg = "/etc/ftpd/pathmsg";   # the pathmsg file
$welcomemsg = "/etc/ftpd/welcome.msg"; # the welcome.msg file


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

# set OUTPUT_AUTOFLUSH
$| = 1;

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

$updatebin = 0;

while ($ARGV[0] =~ m/^-/) {
  $_ = shift(@ARGV);
  if (/--help$/) {
    &usage;
  } elsif (/--version$/) {
    &version;
  } elsif (/--check-binaries$/) {
    &checkbin;
  } elsif (/--update-binaries$/) {
    $updatebin = 1;
  } 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(10);
  }
}

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

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

# check if the user "ftp" already exists
setpwent;
if ($home = (getpwnam("ftp"))[7]) {
   if (-d $home) {
     exit(0) unless $updatebin;
   } 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";
}

# if the directory exist fix at least the permissions
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/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/lib", "$home/etc", "$home/pub");

if ($want_incoming and not $updatebin) {
  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";
}

# optional binaries
if (-f "/usr/bin/zip") {
  system("cp /usr/bin/zip $home/bin/zip") &&
    warn "$0: can't copy /usr/bin/zip to $home/bin/zip (ignored)\n";
  chmod(0111, "$home/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");
        if ($libc =~ /\.old$/) {
            $libc =~ s/\.old$//;
            system("mv $home/lib/$libc.old $home/lib/$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";
    }
} else {
    $ftplib = readlink("$home/lib/libc.so.5");
    unlink("$home/lib/$ftplib");
    unlink("$home/lib/ld-linux.so.1");
    unlink("$home/lib/libc.so.5");
}
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");
        if ($libc =~ /\.old$/) {
            $libc =~ s/\.old$//;
            system("mv $home/lib/$libc.old $home/lib/$libc");
        }
        symlink("$libc", "$home/lib/libc.so.4");
    } else {
        print "$0: a.out binaries found but libc.so.4 not available\n";
    }
} else {
    $ftplib = readlink("$home/lib/libc.so.4");
    unlink("$home/lib/$ftplib");
    unlink("$home/lib/ld.so");
    unlink("$home/lib/libc.so.4");
}

# copy the pathmsg file (if available)
system("cp $pathmsg $home/etc/pathmsg") unless (-f "$home/etc/pathmsg");

# copy the welcome.msg file (if available)
system("cp $welcomemsg $home/welcome.msg") unless (-f "$home/welcome.msg");

# 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 "$group\:\:$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 "--check-binaries      check whether the binaries and libraries of the\n";
  print "                      ftp hierarchy should be updated or not\n";
  print "--update-binaries     update binaries and libraries of the ftp hierarchy\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 {
    # ($aout, $elf) = &filetype($base_directory, @filenames_without_path);
    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);
}

sub checkbin {
    # exit with error level 1 if the file formats of /bin/ls
    # and ~ftp/bin/ls are different.
    my($home, $binls_elf, $ftpls_elf);
    setpwent;
    if ($home = (getpwnam("ftp"))[7]) {
        if (-d $home) {
	   (undef, $binls_elf) = &filetype("/bin", "ls");
	   (undef, $ftpls_elf) = &filetype("$home/bin", "ls");
	   if ($binls_elf == $ftpls_elf) {
              # /bin/ls and ~ftp/bin/ls are both elf or a.out
              # no action required
              exit(0);
           } else {
              # the file formats of /bin/ls and ~ftp/bin/ls are
              # different. Return err_lvl 1
              print "\nOld binaries/libraries in $home/bin detected ...\n";
              exit(1);
           }
        }
    }
    endpwent;
}

