#!/usr/local/bin/perl
#
# $Id: usercreate,v 1.2 2003/05/21 22:32:36 visick Exp $
#
# usercreate - add user(s) to LDAP
#
# Copyright (C) 2002 Steven Barrus
# Copyright (C) 2002 Dana Dahlstrom
# Copyright (C) 2002 Robert Ricci
# Copyright (C) 2002 Spencer Visick
#
# See the AUTHORS file for contact info
#
# 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

use Getopt::Long;
use Term::ReadLine;

require 'usertools.ph';

&GetOptions("bulk:s","leet");
$term = new Term::ReadLine;

my @uids = ();
$lastuid = 1001;

#Connect to Ldap
$ldap = ldap_connect() || exit(1);

$objectclasses = [ 'account', 'posixAccount', 'top', 'shadowAccount'];
if ($config{user_objectclass}){
  for (split(/,/,$config{user_objectclass})){
    push(@$objectclasses,$_)
  }
}
@fields = ('gecos','uid','homedirectory', 'loginshell', 
           'gidnumber', 'userpassword');
if ($config{required_attribute}){
  @required = split(/,/,$config{required_attribute});
  @fields = (@fields ,@required);
}

foreach $pass_attr (split(/,/,$config{password_attribute})){
 @password = (@password, grep(/$pass_attr/, @required));
 @required = grep(!/$pass_attr/, @required);
}

$interactive = ! $opt_bulk;


%u = ();
if ($opt_bulk) {
  &bulk_parse;
  exit;
}

while ($interactive) {

  if (&newuser) {

    my $confirmation = &confirm();
    if ($confirmation == 1) {
      &get_uid;
      if (!(&ldap_insert)) {
        print "Setup home directory? (Y/n)";
        make_homedir()
            unless lc(<STDIN>) =~ /^n/;
      }
    } elsif ($confirmation == 2) {
      next;
    } else {
      %u = ();
    }
  }
  %u = ();
  last if (!&again);

}


sub newuser {

  &get_name;
  &get_gecos;
  &get_gid;
  &get_uname;
  &get_password("userpassword");
  &get_home;
  &get_shell;
  for (@password){
    get_password("$_");
  }
  for (@required){  
    get_value($_);
  }

  return 1; 
}

sub confirm {
  if ($interactive) {
    &print_entry;
    do {
      print "Does this look okay? [(y)es,(n)o,(m)odify] ";
      $response = <STDIN>;
      if ($response =~ /y/) {
        return 1;
      } elsif ($response =~ /n/) {
        return 0;
      } elsif ($response =~ /m/) {
        return 2;
      }
    } while (1);
  } else {
    return 1;
  }
}

sub print_entry {
  print "**************************************************\n";
  foreach $field (@fields) {
    printf("%15s: ",$field);
    my $value = $u{$field};
    if ($value) {
      print $value;
    } else {
      print "<blank>";
    }
    print "\n";
  }
  print "**************************************************\n";
}

sub again {
  print "Create another user? (y/N) ";
  if (<STDIN> =~ /[yY]/) {
    return 1;
  } else {
    return 0; 
  }
}

sub readline {
  my ($prompt, $fill) = @_;
  return $term->readline($prompt . " ", $fill);
}

sub readvar {
  # Pass in $prompt,$var 
  # Skip if not interactive
  return unless $interactive;
  if ($_[1]) {
    $_[1] = &readline($_[0], $_[1]);
  } else {
    $_[1] = &readline($_[0], "");
  }
}

sub get_name {
  if ($interactive) {
    &readvar("First Name:",$u{first});
    &readvar("Middle Name:",$u{middle});
    &readvar("Last Name:",$u{last});
  }
}

sub get_gecos {
  # Make sure that we don't include extra spaces around blank words
  my $string = $u{first};
  if ($u{first}) {
    $string .= " ";
  }
  $string .= $u{middle};
  if ($u{middle}) {
    $string .= " ";
  }
  $string .= $u{last};
  if ($string =~ /\w/ || $opt_leet){ 
    $u{gecos} = $string;
  }else{
    print "Gecos can't be blank\n";
    &get_name;
    &get_gecos;
  }
}

sub get_uid {
  if (!@uids) {

    print "Getting list of UIDs - this could take several seconds\n";
    my $mesg = $ldap->search(base => $config{userbase},
                             filter => "(objectclass=posixAccount)",
                             attrs => ['uidNumber']);
    print $mesg->error if $mesg->code;
    @entry = $mesg->entries;
    for (@entry) {
      my $uid = $_->get_value("uidNumber");
      if ($uid) {
        $uids[$uid] = 1;
      }
    }
  }

  while (1) {
    if ($uids[$lastuid]) {
      $lastuid++;
    } else {
      if (($ldap->search(base=>$config{userbase},
                         filter => "(uidNumber=$lastuid)" ))->entry()) {
        $uids[$lastuid++] = 1;
      } else {
        $uids[$lastuid] = 1;
        $u{uidnumber} = $lastuid++;
        return;
      }
    }
  }
}

sub get_uname {
  my ($last, $first, $middle) = ($u{last}, $u{first}, $u{middle});
  $last =~ s/-//g; $first =~ s/-//g; $middle =~ s/-//g;
  $last =~ tr/A-Z/a-z/; $first =~ tr/A-Z/a-z/; $middle =~ tr/A-Z/a-z/;
  unless($opt_leet){
    $last =~ s/\W//g; $first =~ s/\W//g; $middle =~ s/\W//g;
    $last =~ s/\d//g; $first =~ s/\d//g; $middle =~ s/\d//g;
  }
  @tries = ( substr($last,0,8),               # last name
      substr($first,0,1) . substr($last,0,7), # first initial, last name
      substr($first,0,7) . substr($last,0,1), # first name, last initial
      substr($first,0,1) . substr($middle,0,1) . substr($last,0,6),
      substr($first,0,8),                     # first name
      substr($first,0,2) . substr($last,0,6), # combination of first and last
      substr($first,0,3) . substr($last,0,5),
      substr($first,0,4) . substr($last,0,4),
      substr($first,0,1) . substr($middle,0,1) . substr($last,0,1), #initals
      substr($first,0,1). substr($last,0,1) ); # initials

  foreach $try ( @tries ) {
    if (!&user_exists($try)) {
      $u{uid} = $try;
      last;
    }
  }

  while ($interactive) { 
    &readvar("Username:",$u{uid});
    if (&user_exists($u{uid})) {
      print "Sorry, $u{uid} already exists. Try another one\n";
    } elsif (!$opt_leet && length($u{uid}) > 8) {
      print "Dude, $u{uid} is longer than 8 characters! Bogus.\n";
    } elsif (!$opt_leet && $u{uid} =~ /\W|\d/){
      print "Only alpha characters please.\n";
    } elsif (!$u{uid}) {
      print "What's my name again?\n";
    } else {
      return;
    }
  }

}

sub user_exists {
  my $username = shift;
  my $mesg = $ldap->search(base => $config{userbase}, filter => "(uid=$username)" );
  my $user_entry = $mesg->entry();
  return $user_entry;
}

sub get_home {
  $u{homedirectory} =  "/home/" . $u{uid};
  &readvar("Home directory:", $u{homedirectory}); 
}

sub get_gid {
  if ($config{default_gid} ne "ask"){
    $u{gidnumber} = $config{default_gid};
  }
  &readvar("Gid:", $u{gidnumber});
}
  

sub get_password {
  my ($passname) = @_;
  @chars = ('a' .. 'k','m' .. 'z',2..9);
  $u{$passname} = "";
  for (1 .. 8) {
    $u{$passname} .= $chars[rand @chars];
  }
  $u{$passname} =  enc_passwd($u{$passname});
}

sub get_shell {
  if ($config{default_shell} ne "ask"){
    $u{loginshell} = $config{default_shell};
  }
  &readvar("Shell:", $u{loginshell});

}

sub ldap_insert {
  my $entry = Net::LDAP::Entry->new;

  my $dn = &get_dn($u{uid});
  $entry->dn($dn);

  $entry->add('objectclass' => $objectclasses);
  $entry->add('uid' => $u{uid});
  $entry->add('uidNumber' => $u{uidnumber});
  $entry->add('gidNumber' => $u{gidnumber});

  $entry->add('homedirectory' => $u{homedirectory});
  $entry->add('cn' => $u{gecos} );
  $entry->add('gecos' => $u{gecos});
  
  $entry->add('userPassword' => $u{userpassword});
  $entry->add('loginShell' => $u{loginshell});
  foreach (@password){
    if (defined($u{$_})){
      $entry->add($_ => $u{$_});
    }
  }
  foreach (@required){
    if (defined($u{$_})){
      $entry->add($_ => $u{$_});
    }
  }

  $mesg = $entry->update( $ldap );
  print "Error inserting $u{uid} into LDAP", $mesg->error, "\n" if $mesg->code;

  return $mesg->code;

}

sub make_homedir {
  unless ( -e $u{homedirectory}) {
    system "$config{homedir_command} $u{homedirectory}";
  }
}

sub get_dn {
  my ($username) = @_;
  return "uid=" . $username . "," . $config{userbase};
}


sub bulk_parse {
  my ($new, $skipname) = (0,0);
  open(PASSWD,"$opt_bulk") || die "Couldn't open $opt_bulk :$!";
  while (<PASSWD>) {
    chomp;
    next unless $_; # Skip blank lines
    ($u{uid}, $u{userpassword}, $u{uidnumber}, 
     $u{gidnumber}, $u{gecos},
     $u{homedirectory}, $u{loginshell}) =  split (/:/);
    
    $mesg = $ldap->search(base => $config{userbase}, 
                          filter => "(uid=$u{uid})");
    if ($mesg->entry()){
       print "*** Skipped $u{uid} by username ***\n";
       $skipname++;
       next;
    }
    if (($u{uid} eq "root") || ($u{uidnumber} == 0)){
       print "***Found $u{uid} with uid number $u{uidnumber}***\n";
       print "*** This is not advised. Superusers should have local accounts***\n";
       print "Would you like to add them anyway? (y/N)";
       unless (<STDIN> =~ /[yY]/) {
         $skipname++;
         next;
       }
    }
    $crypt = check_pass_type($u{userpassword});
    $u{userpassword} = "{$crypt}" . $u{userpassword};
    ldap_insert();
    print "$u{uid}\n";
    make_homedir();
    $new++;
  }
  
  close PASSWD;
  print "**** Completed: $new new accounts, $skipname skipped by name\n";
}

sub get_value{
  my $value = shift;
  while ($interactive) {
    readvar("$value: ", $u{$value});
    if ($u{$value} ne "") {
       return;
    } else {
       print "$value must not be blank.\n";
    }
  }
}

sub check_pass_type{
  my $pwd = shift;
  if ($pwd =~ /^\$1\$/){
    return "md5"
  }
  return "crypt";
}

__END__
=head1 NAME

usercreate - add user(s) to LDAP

=head1 SYNOPSIS
     
B<usercreate> [-h]
 
B<usercreate> [--bulk] I<passwd>

B<usercreate> [--leet] 

=head1 DESCRIPTION

B<usercreate> adds a user to the LDAP directory specified in
in /etc/usertools.conf or $HOME/.usertoolsrc. You will be prompted 
for a password with which to bind. You will be prompted for 
first, middle, and last name, gid, username, home directory, shell,  
and any other attributes in the list user_attribute in the 
configuration file.

=head1 OPTIONS

B<-h>

Print out usage information.

B<--bulk> I<passwd>

Adds users to ldap found in I<passwd> file. Format is:
username:password:uid:gid:gecos:home directory:shell

B<--leet>

Disables safety checking.

=head1 BUGS

Lots probably, sorry. Contact AUTHORS if you find any.

=head1 AUTHORS

The usertools were written at the CADE by various opers. See the AUTHORS file 
for details.

=head1 SEE ALSO

userdelete(1), usermodify(1), userrevert(1), usersearch(1).
groupcreate(1), groupmodify(1), groupsearch(1),

=cut 

