#/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 |
[email protected]