#/usr/local/bin/perl

# o  it will directly insert a new user into the hash password file
#    (/etc/passwd.{pag,dir}) if these files exist.
#
# o  it adds the quota by calling the quota system call from perl
#
# o  the next uid is taken by doing a 'tail -1 /etc/passwd', so your
#    passwd file should be sorted by uid (at least the last entry
#    should be the highest).
#
#We have used it to add over 2000 students, and has performed quite well.
#
#It has the following options:
#
# usage:  add_user [-options ...] username
#
#where options include:                                    Defaults -
#    -dir      dir             parent directory            least used disk
#    -full     "full name"     full name of new user       username
#    -group    group           group of new user           $def_group
#    -password password        password of new user        username (don't use!)
#    -quota    quota           disk quota in kbytes        $def_quota
#    -shell    login shell     login shell of new user     $def_shell
#
#All options can be abbreviated up to one letter.

$| = 1;
$exit_status=0;
$working_dir = "/etc";
@user_dirs = ("/u");
$def_group = "users";
$def_gid   = 500;
$def_shell = "/bin/csh";
$def_quota = 2000;

$max_uid = 32000;

chdir($working_dir) || die "can't change to $working_dir\n";

# parse command options

while ( $_ = $ARGV[0]) {
	shift;
	last if /^--$/;
	if    (/^-d/)		{ $nu_parent = &get_option("-dir");	} 
        elsif (/^-f/)  		{ $nu_full   = &get_option("-full");    }
        elsif (/^-g/)  		{ $nu_group  = &get_option("-full");    }
	elsif (/^-h/)		{ &do_help; }
        elsif (/^-p/)  		{ $nu_passwd  = &get_option("-pass");    }
        elsif (/^-q/)  		{ $nu_quota  = &get_option("-quota");    }
        elsif (/^-s/)  		{ $nu_shell  = &get_option("-shell");    }
	elsif (/^[a-zA-Z]*/)    { 
	  &cleanup("can't specify more then one user!",7) if ($nu_user ne "");
	  $nu_user = $_;
	}
	else 		        { &usage("unknown argument: $_");          }
	
}

&usage("must specify one user") if ( $nu_user eq "" );

&usage("must specify a passwd") if ( $nu_passwd eq "");

if ("$nu_full" eq "")   { $nu_full = "$nu_user"; }
if ("$nu_parent" eq "") {
    $lowest_links=32768;
    foreach (@user_dirs) {
        $links = (stat($_))[3];
        if ($links < $lowest_links) { $lowest_links=$links; $nu_parent=$_; }
    }
}

&cleanup("$nu_parent directory not found!",5) if (! -d $nu_parent);

if ($nu_group eq "")   { 
	$nu_group = "$def_group"; 
	$nu_gid   = $def_gid;
} else {
	($t,$t,$nu_gid) = getgrnam($nu_group);
	&cleanup("unknown group: $nu_group",4) if ($nu_gid eq '');
}

if ($nu_shell eq "")   { $nu_shell = "$def_shell"; }
if ($nu_quota eq "")   { $nu_quota = "$def_quota"; }

&catch_signals;
&passwd_lock || &cleanup("couldn't lock passwd file!",1);

($name)=getpwnam($nu_user);

&cleanup("user $nu_user already in passwd file.",3) if ($name ne "");

print "adduser: Adding $nu_user, quota=$nu_quota group=$nu_group\n";

$nu_uid = &next_uid;

if ($nu_uid eq "" || $nu_uid<100 || $nu_uid> $max_uid) {
  &cleanup("next uid error. uid $nu_uid is invalid",6);
}

$nu_home_dir = "$nu_parent/$nu_user";

$nu_encrypted_passwd = &encrypt_passwd($nu_user,$nu_passwd);


(
 open(PASSWD,">>passwd") && 
 (print PASSWD "$nu_user:$nu_encrypted_passwd:$nu_uid:$nu_gid:$nu_full:$nu_home_dir:$nu_shell\n") &&
 close(PASSWD)
) || &cleanup("error creating new passwd file!",10);


if ( -f passwd.dir && -f passwd.pag ) {
     dbmopen(%DBM_PASSWD,"passwd",0644);
     $buf = &pack_passwd_dbm($nu_user,$nu_encrypted_passwd,$nu_uid,$nu_gid,
             $nu_full,$nu_home_dir,$nu_shell);
     $DBM_PASSWD{$nu_user}=$buf;
     $DBM_PASSWD{pack("i",$nu_uid)}=$buf;
     dbmclose(%DBM_PASSWD);
}

&cancel_passwd_lock;

#set the quota here!
if (!&set_new_quota($nu_uid,$nu_quota)) {
   print "adduser: warning: error setting quota!\n";
}


( 
  mkdir("$nu_home_dir",0711) &&   
  chown($nu_uid,$nu_gid,"$nu_home_dir")
) || &cleanup("error creating home directory $nu_home_dir",9);

chdir("$nu_home_dir") || &cleanup("error changing to directory $nu_home_dir",9);

(
 mkdir("bin",0711) && 
 chown($nu_uid,$nu_gid,"bin")
) || &cleanup("error creating bin directory $nu_home_dir/bin",9);

system "cp /admin/skel/.profile /admin/skel/.cshrc /admin/skel/.login .";
chmod(0711,".profile",".cshrc",".login");
chown($nu_uid,$nu_gid,".profile",".cshrc",".login");
chmod(0755,".forward");

chdir($working_dir) || die "can't change to $working_dir\n";

exit 0;


sub usage {
  local($mess) = @_;

  print "adduser: $mess\n\n";

  print <<"_EOF_";
usage:  add_user [-options ...] username

where options include:                                    Defaults -
    -dir      dir             parent directory            least used disk
    -full     "full name"     full name of new user       username
    -group    group           group of new user           $def_group
    -password password        password of new user        username
    -quota    quota           disk quota in kbytes        $def_quota
    -shell    login shell     login shell of new user     $def_shell

All options can be abbreviated up to one letter.

Possible exit codes:
           0 - normal, success           1 - password file is busy
           2 - interrupted               3 - user already in passwd file
           4 - bad group specified       5 - bad parent directory
           6 - error getting new uid     7 - bad arguements (usage)
           8 - error from remote system  9 - error creating user files
          10 - error creating new passwd
_EOF_

 exit 7;
}

sub get_option {
	&usage("missing argument for $_[0]") if ($#ARGV==-1) ;
	$result = $ARGV[0];
	shift @ARGV;	 
	return $result;
}

sub cancel_passwd_lock {
   if (!$passwd_file_locked) { 
	return 0;
   }
   else {
	unlink 'ptmp';
	$passwd_file_locked=0;
	return 1;
   }
}

sub finish_passwd_lock {
   if (!$passwd_file_locked) { 
	return 0;
   } else {
	close(PASSWD);
	close(PTMP);
        chmod 0644,'ptmp';
        rename('passwd','passwd.old');
        rename('ptmp','passwd') || die "can't install new passwd file: $!\n";
	$passwd_file_locked=0;
   } 
}

sub passwd_lock {
  local($retry)=0;

  $the_ptmp = "ptmp.$$";

  open(PTMP,">$the_ptmp") || die"can't create tmp passwd file: $the_ptmp\n";
  close(PTMP);

  if (!link("$the_ptmp",'ptmp') ) {
     print "passwd file busy.";
     while (!link("$the_ptmp",'ptmp')) {
	   if ($retry++ == 24) {
               printf "giving up!\n";
	       unlink("$the_ptmp");
               $passwd_file_locked=0;
	       return 0;
           }
	   sleep(5);
   	   print ".";
     }
     printf "locked!\n";
  }

  $passwd_file_locked=1;
  unlink("$the_ptmp");
  open(PTMP,">ptmp") || die "can't copy passwd file\n";
  open(PASSWD,"passwd") || die "can't open passwd file\n";
  return 1;	

}

sub encrypt_passwd {
  local($user,$pass)=@_;
  local($nslat,$week,$now,$pert1,$pert2);
  local(@salt_set)=('a'..'z','A'..'Z','0'..'9','.','/');
  $now=time;
  ($pert1,$per2) = unpack("C2",$user);
  $week = $now / (60*60*24*7) + $pert1 + $pert2;
  $nsalt = $salt_set[$week % 64] . $salt_set[$now %64];
  return crypt($pass,$nsalt);
}

sub next_uid {
	local(*FILE);
	open (FILE,'tail -1 /etc/passwd|') ||
		die "Can't get last used uid: $?";
	local($name,$pass,$uid)=split(':',<FILE>);
        close(FILE);
        return $uid+1;
}

sub catch_signals {
  $SIG{'INT'} = 'SIGNAL_CLEANUP';
  $SIG{'HUP'} = 'SIGNAL_CLEANUP';
  $SIG{'QUIT'} = 'SIGNAL_CLEANUP';
  $SIG{'PIPE'} = 'SIGNAL_CLEANUP';
  $SIG{'ALRM'} = 'SIGNAL_CLEANUP';
}

sub cleanup {
  local($message,$exit_status) = @_;
  &cancel_passwd_lock;
  unlink("$the_ptmp") if (defined ($the_ptmp));
  print "adduser: $message\n";
  exit $exit_status;
}

sub SIGNAL_CLEANUP {
  &cancel_passwd_lock;
  unlink("$the_ptmp") if (defined ($the_ptmp));
  print "\nadduser: interrupted!\n";
  exit 2;
}

sub unpack_passwd_dbm {
local($buf) = $_[0];
local($i,$l,$name,$passwd,$uid,$gid,$quota,$comment,$gecos,$dir,$shell);

$name	= substr($buf,$i,$l=index($buf,"\0",$i));       $i += $l+1;
$passwd	= substr($buf,$i,$l=index($buf,"\0",$i)-$i);  	$i += $l+1;
($uid,$gid,$quota)=unpack("i i i",substr($buf,$i,12));	$i += 12;
$comment= substr($buf,$i,$l=index($buf,"\0",$i)-$i);	$i += $l+1;
$gecos	= substr($buf,$i,$l=index($buf,"\0",$i)-$i);	$i += $l+1;
$dir	= substr($buf,$i,$l=index($buf,"\0",$i)-$i);	$i += $l+1;
$shell	= substr($buf,$i,$l=index($buf,"\0",$i)-$i);	$i += $l+1;
return ($name,$passwd,$uid,$gid,$gecos,$dir,$shell);
}


sub pack_passwd_dbm {

local($name,$passwd,$uid,$gid,$gecos,$dir,$shell) = @_;
local($i,$l,$quota,$comment,$buf);

$buf = $name . "\0" . $passwd . "\0" . pack("iii",$uid,$gid,0)  .
       "\0" . $gecos . "\0" . $dir . "\0" . $shell . "\0";

return $buf;
}
#
#sub set_new_quota { #user,dir,bs
#  local ($SYS_quota)=149;
#  local ($Q_SETDLIM)=1;
#  local ($uid,$dir,$bs) = @_;
#  local ($dev)   = stat($dir);
#  local ($dqblk) = pack("LLLSSSCC",($bs+1000)*2,($bs*2),0,0,0,0,3,3);
#  local ($stat,$buf);
#  $stat=syscall($SYS_quota,$Q_SETDLIM,$uid,$dev,$dqblk);
#  return $stat==0;
#}
#
#
# this is all i had to change, besides the default home dir and stuff

sub set_new_quota { #user,blocks
  local ($SYS_quota)=148;
  local ($Q_SETDLIM)=3;
  local ($uid,$bs) = @_ ;
  local ($dir) = "/dev/dsk/6s0\0";
  local ($dqblk)  = pack("LLLLLLLL",10000,$bs,0,1000,200,0,0,0); 
local ($stat,$buf); 
$stat=syscall($SYS_quota,$Q_SETDLIM,$dir,$uid+0,$dqblk); 
return $stat==0;
}


#Roland J. Schemers III              |            Networking Systems
#Systems Programmer                  |            168 Pine Hall   (415)-723-6740
#Distributed Computing Group         |            Stanford, CA 94305-4122
#Stanford University                 |            schemers@jessica.Stanford.EDU