#/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(':',); 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