Annotation of loncom/lcuseradd, revision 1.13

1.1       harris41    1: #!/usr/bin/perl
                      2: #
                      3: # lcuseradd
                      4: #
                      5: # Scott Harrison
1.13    ! harris41    6: # SH: October 27, 2000
        !             7: # SH: October 29, 2000
1.1       harris41    8: 
                      9: use strict;
                     10: 
                     11: # This script is a setuid script that should
                     12: # be run by user 'www'.  It creates a /home/USERNAME directory
                     13: # as well as a /home/USERNAME/public_html directory.
                     14: # It adds user entries to
                     15: # /etc/passwd and /etc/groups.
1.2       harris41   16: # Passwords are set with lcpasswd.
                     17: # www becomes a member of this user group.
1.1       harris41   18: 
                     19: # Standard input usage
                     20: # First line is USERNAME
                     21: # Second line is PASSWORD
1.3       harris41   22: # Third line is PASSWORD
1.1       harris41   23: 
1.7       harris41   24: # Valid passwords must consist of the
                     25: # ascii characters within the inclusive
                     26: # range of 0x20 (32) to 0x7E (126).
                     27: # These characters are:
                     28: # SPACE and
                     29: # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO
                     30: # PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
                     31: 
                     32: # Valid user names must consist of ascii
                     33: # characters that are alphabetical characters
                     34: # (A-Z,a-z), numeric (0-9), or the underscore
                     35: # mark (_). (Essentially, the perl regex \w).
                     36: 
1.3       harris41   37: # Command-line arguments [USERNAME] [PASSWORD] [PASSWORD]
1.1       harris41   38: # Yes, but be very careful here (don't pass shell commands)
                     39: # and this is only supported to allow perl-system calls.
                     40: 
1.4       harris41   41: # Usage within code
                     42: #
                     43: # $exitcode=system("/home/httpd/perl/lcuseradd","NAME","PASSWORD1","PASSWORD2")/256;
                     44: # print "uh-oh" if $exitcode;
                     45: 
                     46: # These are the exit codes.
1.13    ! harris41   47: # ( (0,"ok"),
        !            48: # (1,"User ID mismatch.  This program must be run as user 'www'"),
        !            49: # (2,"Error. This program needs 3 command-line arguments (username, password 1, password 2)."),
        !            50: # (3,"Error. Three lines should be entered into standard input."),
        !            51: # (4,"Error. Too many other simultaneous password change requests being made."),
        !            52: # (5,"Error. User $username does not exist."),
        !            53: # (6,"Error. Could not make www a member of the group \"$safeusername\"."),
        !            54: # (7,"Error. Root was not successfully enabled.),
        !            55: # (8,"Error. Cannot open /etc/passwd."),
        !            56: # (9,"Error. The user name specified has invalid characters."),
        !            57: # (10,"Error. A password entry had an invalid character."),
        !            58: # (11,"Error. User already exists.),
        !            59: # (12,"Error. Something went wrong with the addition of user \"$safeusername\"."),
        !            60: # (13,"Error. Password mismatch."),
1.4       harris41   61: 
1.1       harris41   62: # Security
                     63: $ENV{'PATH'}=""; # Nullify path information.
                     64: $ENV{'BASH_ENV'}=""; # Nullify shell environment information.
1.2       harris41   65: 
1.4       harris41   66: # Do not print error messages if there are command-line arguments
                     67: my $noprint=0;
                     68: if (@ARGV) {
                     69:     $noprint=1;
                     70: }
                     71: 
                     72: # Read in /etc/passwd, and make sure this process is running from user=www
                     73: open (IN, "</etc/passwd");
                     74: my @lines=<IN>;
                     75: close IN;
                     76: my $wwwid;
                     77: for my $l (@lines) {
                     78:     chop $l;
                     79:     my @F=split(/\:/,$l);
                     80:     if ($F[0] eq 'www') {$wwwid=$F[2];}
                     81: }
                     82: if ($wwwid!=$<) {
                     83:     print("User ID mismatch.  This program must be run as user 'www'\n") unless $noprint;
                     84:     exit 1;
                     85: }
                     86: &disable_root_capability;
                     87: 
                     88: # Handle case of another lcpasswd process
                     89: unless (&try_to_lock("/tmp/lock_lcpasswd")) {
                     90:     print "Error. Too many other simultaneous password change requests being made.\n" unless $noprint;
                     91:     exit 4;
                     92: }
                     93: 
                     94: # Gather input.  Should be 3 values (user name, password 1, password 2).
                     95: my @input;
1.5       harris41   96: if (@ARGV==3) {
1.4       harris41   97:     @input=@ARGV;
                     98: }
                     99: elsif (@ARGV) {
                    100:     print("Error. This program needs 3 command-line arguments (username, password 1, password 2).\n") unless $noprint;
                    101:     unlink('/tmp/lock_lcpasswd');
                    102:     exit 2;
                    103: }
                    104: else {
                    105:     @input=<>;
1.5       harris41  106:     if (@input!=3) {
1.4       harris41  107: 	print("Error. Three lines should be entered into standard input.\n") unless $noprint;
                    108: 	unlink('/tmp/lock_lcpasswd');
                    109: 	exit 3;
                    110:     }
                    111:     map {chop} @input;
                    112: }
                    113: 
                    114: my ($username,$password1,$password2)=@input;
                    115: $username=~/^(\w+)$/;
                    116: my $safeusername=$1;
1.8       harris41  117: if ($username ne $safeusername) {
                    118:     print "Error. The user name specified has invalid characters.\n";
                    119:     unlink('/tmp/lock_lcpasswd');
                    120:     exit 9;
                    121: }
                    122: my $pbad=0;
1.9       harris41  123: map {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}} (split(//,$password1));
                    124: map {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}} (split(//,$password2));
1.8       harris41  125: if ($pbad) {
                    126:     print "Error. A password entry had an invalid character.\n";
                    127:     unlink('/tmp/lock_lcpasswd');
                    128:     exit 10;
                    129: }
1.5       harris41  130: 
1.7       harris41  131: # Only add user if we can create a brand new home directory (/home/username).
                    132: if (-e "/home/$safeusername") {
                    133:     print "Error. User already exists.\n" unless $noprint;
                    134:     unlink('/tmp/lock_lcpasswd');
1.13    ! harris41  135:     exit 11;
1.7       harris41  136: }
                    137: 
                    138: # Only add user if the two password arguments match.
1.5       harris41  139: if ($password1 ne $password2) {
1.6       harris41  140:     print "Error. Password mismatch.\n" unless $noprint;
1.5       harris41  141:     unlink('/tmp/lock_lcpasswd');
1.13    ! harris41  142:     exit 13;
1.5       harris41  143: }
1.4       harris41  144: 
                    145: &enable_root_capability;
                    146: 
1.3       harris41  147: # Add user entry to /etc/passwd and /etc/groups in such
                    148: # a way that www is a member of the user-specific group
                    149: 
1.5       harris41  150: if (system('/usr/sbin/useradd','-c','LON-CAPA user',$safeusername)) {
1.6       harris41  151:     print "Error.  Something went wrong with the addition of user \"$safeusername\".\n" unless $noprint;
1.4       harris41  152:     unlink('/tmp/lock_lcpasswd');
1.13    ! harris41  153:     exit 12;
1.4       harris41  154: }
1.7       harris41  155: 
                    156: # Make www a member of that user group.
1.5       harris41  157: if (system('/usr/sbin/usermod','-G',$safeusername,'www')) {
1.6       harris41  158:     print "Error. Could not make www a member of the group \"$safeusername\".\n" unless $noprint;
1.5       harris41  159:     unlink('/tmp/lock_lcpasswd');
                    160:     exit 6;
                    161: }
                    162: 
                    163: # Set password with lcpasswd-style algorithm (which creates smbpasswd entry).
                    164: # I cannot place a direct shell call to lcpasswd since I have to allow
                    165: # for crazy characters in the password, and the setuid environment of perl
                    166: # requires me to make everything safe.
1.7       harris41  167: 
1.8       harris41  168: # Grab the line corresponding to username
1.9       harris41  169: open (IN, "</etc/passwd");
                    170: @lines=<IN>;
                    171: close IN;
1.8       harris41  172: my ($userid,$useroldcryptpwd);
                    173: my @F; my @U;
                    174: for my $l (@lines) {
1.11      harris41  175:     chop $l;
1.8       harris41  176:     @F=split(/\:/,$l);
                    177:     if ($F[0] eq $username) {($userid,$useroldcryptpwd)=($F[2],$F[1]); @U=@F;}
                    178: }
1.5       harris41  179: 
1.8       harris41  180: # Verify existence of user
                    181: if (!defined($userid)) {
                    182:     print "Error. User $username does not exist.\n" unless $noprint;
                    183:     unlink('/tmp/lock_lcpasswd');
                    184:     exit 5;
                    185: }
1.2       harris41  186: 
1.8       harris41  187: # Construct new password entry (random salt)
1.10      harris41  188: my $newcryptpwd=crypt($password1,(join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64]));
1.8       harris41  189: $U[1]=$newcryptpwd;
                    190: my $userline=join(':',@U);
                    191: my $rootid=&enable_root_capability;
                    192: if ($rootid!=0) {
                    193:     print "Error.  Root was not successfully enabled.\n" unless $noprint;
                    194:     unlink('/tmp/lock_lcpasswd');
                    195:     exit 7;
                    196: }
                    197: open PASSWORDFILE, '>/etc/passwd' or (print("Error.  Cannot open /etc/passwd.\n") && unlink('/tmp/lock_lcpasswd') && exit(8));
                    198: for my $l (@lines) {
                    199:     @F=split(/\:/,$l);
                    200:     if ($F[0] eq $username) {print PASSWORDFILE "$userline\n";}
                    201:     else {print PASSWORDFILE "$l\n";}
                    202: }
                    203: close PASSWORDFILE;
                    204: 
                    205: ($>,$<)=(0,0); # fool smbpasswd here to think this is not a setuid environment
                    206: unless (-e '/etc/smbpasswd') {
                    207:     open (OUT,'>/etc/smbpasswd'); close OUT;
                    208: }
                    209: my $smbexist=0;
                    210: open (IN, '</etc/smbpasswd');
                    211: my @lines=<IN>;
                    212: close IN;
                    213: for my $l (@lines) {
                    214:     chop $l;
                    215:     my @F=split(/\:/,$l);
                    216:     if ($F[0] eq $username) {$smbexist=1;}
                    217: }
                    218: unless ($smbexist) {
                    219:     open(OUT,'>>/etc/smbpasswd');
                    220:     print OUT join(':',($safeusername,$userid,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX','','/home/'.$safeusername,'/bin/bash')) . "\n";
                    221:     close OUT;
                    222: }
                    223: open(OUT,"|/usr/bin/smbpasswd -s $safeusername>/dev/null");
1.10      harris41  224: print OUT $password1; print OUT "\n";
                    225: print OUT $password1; print OUT "\n";
1.8       harris41  226: close OUT;
                    227: $<=$wwwid; # unfool the program
                    228: 
                    229: # Make final modifications to the user directory.
                    230: # Add a public_html file with a stand-in index.html file.
                    231: 
1.12      harris41  232: system('/bin/chmod','-R','0660',"/home/$safeusername");
                    233: system('/bin/chmod','0770',"/home/$safeusername");
                    234: mkdir "/home/$safeusername/public_html",0770;
1.11      harris41  235: open OUT,">/home/$safeusername/public_html/index.html";
1.8       harris41  236: print OUT<<END;
                    237: <HTML>
                    238: <HEAD>
                    239: <TITLE>$safeusername</TITLE>
                    240: </HEAD>
                    241: <BODY>
                    242: <H1>$safeusername</H1>
                    243: <P>
                    244: Learning Online Network
                    245: </P>
                    246: <P>
                    247: This area provides for:
                    248: </P>
                    249: <UL>
                    250: <LI>resource construction
                    251: <LI>resource publication
                    252: <LI>record-keeping
                    253: </UL>
                    254: </BODY>
                    255: </HTML>
                    256: END
                    257: close OUT;
1.11      harris41  258: system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername");
1.8       harris41  259: &disable_root_capability;
                    260: unlink('/tmp/lock_lcpasswd');
                    261: exit 0;
1.1       harris41  262: 
1.5       harris41  263: # ----------------------------------------------------------- have setuid script run as root
                    264: sub enable_root_capability {
                    265:     if ($wwwid==$>) {
                    266: 	($<,$>)=($>,$<);
                    267: 	($(,$))=($),$();
                    268:     }
                    269:     else {
                    270: 	# root capability is already enabled
                    271:     }
                    272:     return $>;
                    273: }
                    274: 
                    275: # ----------------------------------------------------------- have setuid script run as www
                    276: sub disable_root_capability {
                    277:     if ($wwwid==$<) {
                    278: 	($<,$>)=($>,$<);
                    279: 	($(,$))=($),$();
                    280:     }
                    281:     else {
                    282: 	# root capability is already disabled
                    283:     }
                    284: }
                    285: 
                    286: # ----------------------------------- make sure that another lcpasswd process isn't running
                    287: sub try_to_lock {
                    288:     my ($lockfile)=@_;
                    289:     my $currentpid;
                    290:     my $lastpid;
                    291:     # Do not manipulate lock file as root
                    292:     if ($>==0) {
                    293: 	return 0;
                    294:     }
                    295:     # Try to generate lock file.
                    296:     # Wait 3 seconds.  If same process id is in
                    297:     # lock file, then assume lock file is stale, and
                    298:     # go ahead.  If process id's fluctuate, try
                    299:     # for a maximum of 10 times.
                    300:     for (0..10) {
                    301: 	if (-e $lockfile) {
                    302: 	    open(LOCK,"<$lockfile");
                    303: 	    $currentpid=<LOCK>;
                    304: 	    close LOCK;
                    305: 	    if ($currentpid==$lastpid) {
                    306: 		last;
                    307: 	    }
                    308: 	    sleep 3;
                    309: 	    $lastpid=$currentpid;
                    310: 	}
                    311: 	else {
                    312: 	    last;
                    313: 	}
                    314: 	if ($_==10) {
                    315: 	    return 0;
                    316: 	}
                    317:     }
                    318:     open(LOCK,">$lockfile");
                    319:     print LOCK $$;
                    320:     close LOCK;
                    321:     return 1;
                    322: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>