File:  [LON-CAPA] / loncom / Attic / lcuseradd
Revision 1.41: download - view: text, annotated - select for diffs
Wed Mar 28 20:43:37 2007 UTC (17 years, 1 month ago) by albertel
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_99_0, version_2_9_1, version_2_9_0, version_2_8_X, version_2_8_99_1, version_2_8_99_0, version_2_8_2, version_2_8_1, version_2_8_0, version_2_7_X, version_2_7_99_1, version_2_7_99_0, version_2_7_1, version_2_7_0, version_2_6_X, version_2_6_99_1, version_2_6_99_0, version_2_6_3, version_2_6_2, version_2_6_1, version_2_6_0, version_2_5_X, version_2_5_99_1, version_2_5_99_0, version_2_5_2, version_2_5_1, version_2_5_0, version_2_4_X, version_2_4_99_0, version_2_4_2, version_2_4_1, version_2_4_0, version_2_3_99_0, version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0_RC1, version_2_11_0, version_2_10_X, version_2_10_1, version_2_10_0_RC2, version_2_10_0_RC1, version_2_10_0, loncapaMITrelate_1, bz6209-base, bz6209, bz5969, bz2851, PRINT_INCOMPLETE_base, PRINT_INCOMPLETE, HEAD, GCI_3, GCI_2, GCI_1, BZ5971-printing-apage, BZ5434-fox
- some cleanups and informational adds

    1: #!/usr/bin/perl
    2: 
    3: # The Learning Online Network with CAPA
    4: #
    5: # lcuseradd - LON-CAPA setuid script to coordinate all actions
    6: #             with adding a user with filesystem privileges (e.g. author)
    7: #
    8: #
    9: # $Id: lcuseradd,v 1.41 2007/03/28 20:43:37 albertel Exp $
   10: ###
   11: 
   12: ###############################################################################
   13: ##                                                                           ##
   14: ## ORGANIZATION OF THIS PERL SCRIPT                                          ##
   15: ##                                                                           ##
   16: ## 1. Description of script                                                  ##
   17: ## 2. Invoking script (standard input)                                       ##
   18: ## 3. Example usage inside another piece of code                             ##
   19: ## 4. Description of functions                                               ##
   20: ## 5. Exit codes                                                             ##
   21: ## 6. Initializations                                                        ##
   22: ## 7. Make sure this process is running from user=www                        ##
   23: ## 8. Start running script with www permissions                              ##
   24: ## 9. Handle case of another lcpasswd process (locking)                      ##
   25: ## 10. Error-check input, need 3 values (user name, password 1, password 2)  ##
   26: ## 11. Start running script with root permissions                            ##
   27: ## 12. Add user and make www a member of the user-specific group             ##
   28: ## 13. Set password                                                          ##
   29: ## 14. Make final modifications to the user directory                        ##
   30: ## 15. Exit script (unlock)                                                  ##
   31: ##                                                                           ##
   32: ###############################################################################
   33: 
   34: use strict;
   35: use File::Find;
   36: 
   37: 
   38: # ------------------------------------------------------- Description of script
   39: #
   40: # This script is a setuid script that should
   41: # be run by user 'www'.  It creates a /home/USERNAME directory.
   42: # It adds a user to the unix system.
   43: # Passwords are set with lcpasswd.
   44: # www becomes a member of this user group.
   45: 
   46: # -------------- Invoking script (standard input versus command-line arguments)
   47: #                Otherwise sensitive information will be available to ps-ers for
   48: #                a small but exploitable time window.
   49: #
   50: # Standard input (STDIN) usage
   51: # First line is USERNAME
   52: # Second line is PASSWORD
   53: # Third line is PASSWORD
   54: # Fouth line is the name of a file to which an error code will be written.
   55: #            If the fourth line is omitted, no error file will be written.
   56: #            In either case, the program Exits with the code as its Exit status.
   57: #            The error file will just be a single line containing an
   58: #            error code.
   59: #            
   60: #  
   61: #
   62: # Command-line arguments [USERNAME] [PASSWORD] [PASSWORD]
   63: # Yes, but be very careful here (don't pass shell commands)
   64: # and this is only supported to allow perl-system calls.
   65: #
   66: # Valid passwords must consist of the
   67: # ascii characters within the inclusive
   68: # range of 0x20 (32) to 0x7E (126).
   69: # These characters are:
   70: # SPACE and
   71: # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO
   72: # PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
   73: #
   74: # Valid user names must consist of ascii
   75: # characters that are alphabetical characters
   76: # (A-Z,a-z), numeric (0-9), or the underscore
   77: # mark (_). (Essentially, the perl regex \w).
   78: # User names must begin with an alphabetical character
   79: # (A-Z,a-z).
   80: 
   81: # ---------------------------------- Example usage inside another piece of code
   82: # Usage within code
   83: #
   84: # $Exitcode=
   85: #      system("/home/httpd/perl/lcuseradd","NAME","PASSWORD1","PASSWORD2")/256;
   86: # print "uh-oh" if $Exitcode;
   87: 
   88: # ---------------------------------------------------- Description of functions
   89: # enable_root_capability() : have setuid script run as root
   90: # disable_root_capability() : have setuid script run as www
   91: # try_to_lock() : make sure that another lcpasswd process isn't running
   92: 
   93: # ------------------------------------------------------------------ Exit codes
   94: # These are the Exit codes.
   95: # ( (0,"ok"),
   96: # (1,"User ID mismatch.  This program must be run as user 'www'"),
   97: # (2,"Error. This program needs 3 command-line arguments (username, ".
   98: #    "password 1, password 2)."),
   99: # (3,"Error. Three lines should be entered into standard input."),
  100: # (4,"Error. Too many other simultaneous password change requests being ".
  101: #    "made."),
  102: # (5,"Error. User $username does not exist."),
  103: # (6,"Error. Could not make www a member of the group \"$safeusername\"."),
  104: # (7,"Error. Root was not successfully enabled.),
  105: # (8,"Error. Cannot set password."),
  106: # (9,"Error. The user name specified has invalid characters."),
  107: # (10,"Error. A password entry had an invalid character."),
  108: # (11,"Error. User already exists.),
  109: # (12,"Error. Something went wrong with the addition of user ".
  110: #     "\"$safeusername\"."),
  111: # (13,"Error. Password mismatch."),
  112: # (14, "Error filename is invalid"),
  113: # (15, "Error. Could not add home directory.")
  114: 
  115: # ------------------------------------------------------------- Initializations
  116: # Security
  117: $ENV{'PATH'}='/bin/:/usr/bin:/usr/local/sbin:/home/httpd/perl'; # Nullify path
  118:                                                                 # information
  119: delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # nullify potential taints
  120: 
  121: # Do not print error messages.
  122: my $noprint=1;
  123: 
  124: print "In lcuseradd\n" unless $noprint;
  125: 
  126: # ----------------------------- Make sure this process is running from user=www
  127: my $wwwid=getpwnam('www');
  128: &disable_root_capability;
  129: if ($wwwid!=$>) {
  130:     print("User ID mismatch.  This program must be run as user 'www'\n")
  131: 	unless $noprint;
  132:     &Exit(1);
  133: }
  134: 
  135: # ----------------------------------- Start running script with www permissions
  136: &disable_root_capability;
  137: 
  138: # --------------------------- Handle case of another lcpasswd process (locking)
  139: unless (&try_to_lock("/tmp/lock_lcpasswd")) {
  140:     print "Error. Too many other simultaneous password change requests being ".
  141: 	"made.\n" unless $noprint;
  142:     &Exit(4);
  143: }
  144: 
  145: # ------- Error-check input, need 3 values (user name, password 1, password 2).
  146: my @input;
  147: if (@ARGV>=3) {
  148:     @input=@ARGV;
  149: } elsif (@ARGV) {
  150:     print("Error. This program needs at least 3 command-line arguments (username, ".
  151: 	  "password 1, password 2 [errorfile]).\n") unless $noprint;
  152:     unlink('/tmp/lock_lcpasswd');
  153:     &Exit(2);
  154: } else {
  155:     @input=<>;
  156:     if (@input < 3) {
  157: 	print("Error. At least three lines should be entered into standard input.\n")
  158: 	    unless $noprint;
  159: 	unlink('/tmp/lock_lcpasswd');
  160: 	&Exit(3);
  161:     }
  162:     foreach (@input) {chomp;}
  163: }
  164: 
  165: my ($username,$password1,$password2, $error_file)=@input;
  166: print "Username = ".$username."\n" unless $noprint;
  167: $username=~/^(\w+)$/;
  168: print "Username after substitution - ".$username unless $noprint;
  169: my $safeusername=$1;
  170: print "Safe username = $safeusername \n" unless $noprint;
  171: 
  172: if (($username ne $safeusername) or ($safeusername!~/^[A-Za-z]/)) {
  173:     print "Error. The user name specified $username $safeusername  has invalid characters.\n"
  174: 	unless $noprint;
  175:     unlink('/tmp/lock_lcpasswd');
  176:     &Exit(9);
  177: }
  178: my $pbad=0;
  179: foreach (split(//,$password1)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
  180: foreach (split(//,$password2)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
  181: if ($pbad) {
  182:     print "Error. A password entry had an invalid character.\n" unless $noprint;
  183:     unlink('/tmp/lock_lcpasswd');
  184:     &Exit(10);
  185: }
  186: 
  187: #
  188: #   Safe the filename.  For our case, it must only have alpha, numeric, period
  189: #   and path sparators..
  190: #
  191: 
  192: print "Error file is $error_file \n" unless $noprint;
  193: 
  194: if($error_file) {
  195:     if($error_file =~ /^([(\w)(\d)\.\/]+)$/) {
  196: 	print "Error file matched pattern $error_file : $1\n" unless $noprint;
  197: 	my $safe_error_file = $1;	# Untainted I think.
  198: 	print "Error file after transform $safe_error_file\n"
  199: 	    unless $noprint;
  200: 	if($error_file == $safe_error_file) {
  201: 	    $error_file = $safe_error_file; # untainted error_file.
  202: 	} else {
  203: 	    $error_file ="";
  204: 	    print "Invalid error filename\n" unless $noprint;
  205: 	    Exit(14);
  206: 	}
  207: 
  208:     } else {
  209: 	$error_file="";
  210: 	print "Invalid error filename\n" unless $noprint;
  211: 	Exit(14);
  212:     }
  213: }
  214: 
  215: 
  216: # -- Only add the user if they are >not< in /etc/passwd.
  217: #    Used to look for the ability to create a new directory for the
  218: #    user, however that disallows authentication changes from i
  219: #    internal->fs.. so just check the passwd file instead.
  220: #
  221: my $not_found = system("cut -d: -f1 /etc/passwd | grep -q \"^$safeusername\$\" ");
  222: if (!$not_found) {
  223:     print "Error user already exists\n" unless $noprint;
  224:     unlink('/tmp/lock_lcpasswd');
  225:     &Exit(11);
  226: }
  227: 
  228: 
  229: 
  230: # -- Only add user if the two password arguments match.
  231: 
  232: if ($password1 ne $password2) {
  233:     print "Error. Password mismatch.\n" unless $noprint;
  234:     unlink('/tmp/lock_lcpasswd');
  235:     &Exit(13);
  236: }
  237: print "enabling root\n" unless $noprint;
  238: # ---------------------------------- Start running script with root permissions
  239: &enable_root_capability;
  240: 
  241: # ------------------- Add group and user, and make www a member of the group
  242: # -- Add group
  243: 
  244: print "adding group: $safeusername \n" unless $noprint;
  245: my $status = system('/usr/sbin/groupadd', $safeusername);
  246: if ($status) {
  247:     print "Error.  Something went wrong with the addition of group ".
  248:           "\"$safeusername\".\n" unless $noprint;
  249:     print "Final status of groupadd = $status\n";
  250:     unlink('/tmp/lock_lcpasswd');
  251:     &Exit(12);
  252: }
  253: my $gid = getgrnam($safeusername);
  254:                                                                                 
  255: # -- Add user
  256: 
  257: print "adding user: $safeusername \n" unless $noprint;
  258: my $status = system('/usr/sbin/useradd','-c','LON-CAPA user','-g',$gid,$safeusername);
  259: if ($status) {
  260:     print "Error.  Something went wrong with the addition of user ".
  261: 	  "\"$safeusername\".\n" unless $noprint;
  262:     system("/usr/sbin/groupdel $safeusername");
  263:     print "Final status of useradd = $status\n";
  264:     unlink('/tmp/lock_lcpasswd');
  265:     &Exit(12);
  266: }
  267: 
  268: print "Done adding user\n" unless $noprint;
  269: # Make www a member of that user group.
  270: my $groups=`/usr/bin/groups www` or &Exit(6);
  271: # untaint
  272: my ($safegroups)=($groups=~/:\s*([\s\w]+)/);
  273: $groups=$safegroups;
  274: chomp $groups; $groups=~s/^\S+\s+\:\s+//;
  275: my @grouplist=split(/\s+/,$groups);
  276: my @ugrouplist=grep {!/www|$safeusername/} @grouplist;
  277: my $gl=join(',',(@ugrouplist,$safeusername));
  278: print "Putting www in user's group\n" unless $noprint;
  279: if (system('/usr/sbin/usermod','-G',$gl,'www')) {
  280:     print "Error. Could not make www a member of the group ".
  281: 	  "\"$safeusername\".\n" unless $noprint;
  282:     unlink('/tmp/lock_lcpasswd');
  283:     &Exit(6);
  284: }
  285: 
  286: # ---------------------------------------------------------------- Set password
  287: # Set password with lcpasswd (which creates smbpasswd entry).
  288: 
  289: unlink('/tmp/lock_lcpasswd');
  290: &disable_root_capability;
  291: ($>,$<)=($wwwid,$wwwid);
  292: print "Opening lcpasswd pipeline\n" unless $noprint;
  293: open OUT,"|/home/httpd/perl/lcpasswd";
  294: print OUT $safeusername;
  295: print OUT "\n";
  296: print OUT $password1;
  297: print OUT "\n";
  298: print OUT $password1;
  299: print OUT "\n";
  300: close OUT;
  301: if ($?) {
  302:     print "abnormal Exit from close lcpasswd\n" unless $noprint;
  303:     &Exit(8);
  304: }
  305: ($>,$<)=($wwwid,0);
  306: &enable_root_capability;
  307: 
  308: # Check if home directory exists for user
  309: # If not, create one.
  310: if (!-e "/home/$safeusername") {
  311:     if (!mkdir("/home/$safeusername",0710)) {
  312:         print "Error. Could not add home directory for ".
  313:           "\"$safeusername\".\n" unless $noprint;
  314:         unlink('/tmp/lock_lcpasswd');
  315:         &Exit(15);
  316:     }
  317: }
  318: 
  319: # ------------------------------ Make final modifications to the user directory
  320: # -- Add a public_html file with a stand-in index.html file
  321: 
  322: if (-d "/home/$safeusername") {
  323:     system('/bin/chmod','-R','0660',"/home/$safeusername");
  324:     system('/bin/chmod','0710',"/home/$safeusername");
  325:     mkdir "/home/$safeusername/public_html",0755;
  326:     open OUT,">/home/$safeusername/public_html/index.html";
  327:     print OUT<<END;
  328: <html>
  329: <head>
  330: <title>$safeusername</title>
  331: </head>
  332: <body>
  333: <h1>Construction Space</h1>
  334: <h3>$safeusername</h3>
  335: </body>
  336: </html>
  337: END
  338: close OUT;
  339: }
  340: 
  341: #
  342: #   In order to allow the loncapa daemons appropriate access
  343: #   to public_html, Top level and public_html directories should
  344: #   be owned by safeusername:safeusername as should the smaple index.html..
  345: print "lcuseradd ownership\n" unless $noprint;
  346: system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername"); # First set std ownership on everything.
  347: &set_public_html_permissions("/home/$safeusername/public_html");
  348: #  system('/bin/chown',"$safeusername:www","/home/$safeusername");	# Now adust top level...
  349: #  system('/bin/chown','-R',"$safeusername:www","/home/$safeusername/public_html"); # And web dir.
  350: # ---------------------------------------------------- Gracefull Apache Restart
  351: my $pidfile;
  352: if (-e '/var/run/httpd.pid') {
  353:     $pidfile = '/var/run/httpd.pid';
  354: } elsif (-e '/var/run/httpd2.pid') {   #Apache 2 on SuSE 10.1 and SLES10 
  355:     $pidfile = '/var/run/httpd2.pid';
  356: } 
  357: 
  358: if ($pidfile) {
  359:     print "lcuseradd Apache restart\n" unless $noprint;
  360:     open(PID,"<$pidfile");
  361:     my $pid=<PID>;
  362:     close(PID);
  363:     $pid=~ /(\D+)/;
  364:     my $safepid = $1;
  365:     if ($pid) {
  366: 	system('kill','-USR1',"$safepid");
  367:     }
  368: }
  369: # -------------------------------------------------------- Exit script
  370: print "lcuseradd Exiting\n" unless $noprint;
  371: &disable_root_capability;
  372: &Exit(0);
  373: 
  374: # ---------------------------------------------- Have setuid script run as root
  375: sub enable_root_capability {
  376:     if ($wwwid==$>) {
  377: 	($<,$>)=($>,0);
  378: 	($(,$))=($),0);
  379:     } else {
  380: 	# root capability is already enabled
  381:     }
  382:     if ($wwwid == $>) {
  383: 	print("Failed to become root\n") unless $noprint;
  384:     } else {
  385: 	print("Became root\n") unless $noprint;
  386:     }
  387:     return $>;
  388: }
  389: 
  390: # ----------------------------------------------- Have setuid script run as www
  391: sub disable_root_capability {
  392:     if ($wwwid==$<) {
  393: 	($<,$>)=($>,$<);
  394: 	($(,$))=($),$();
  395:     } else {
  396: 	# root capability is already disabled
  397:     }
  398: }
  399: 
  400: # ----------------------- Make sure that another lcpasswd process isn't running
  401: sub try_to_lock {
  402:     my ($lockfile)=@_;
  403:     my $currentpid;
  404:     my $lastpid;
  405:     # Do not manipulate lock file as root
  406:     if ($>==0) {
  407: 	return 0;
  408:     }
  409:     # Try to generate lock file.
  410:     # Wait 3 seconds.  If same process id is in
  411:     # lock file, then assume lock file is stale, and
  412:     # go ahead.  If process id's fluctuate, try
  413:     # for a maximum of 10 times.
  414:     for (0..10) {
  415: 	if (-e $lockfile) {
  416: 	    open(LOCK,"<$lockfile");
  417: 	    $currentpid=<LOCK>;
  418: 	    close LOCK;
  419: 	    if ($currentpid==$lastpid) {
  420: 		last;
  421: 	    }
  422: 	    sleep 3;
  423: 	    $lastpid=$currentpid;
  424: 	} else {
  425: 	    last;
  426: 	}
  427: 	if ($_==10) {
  428: 	    return 0;
  429: 	}
  430:     }
  431:     open(LOCK,">$lockfile");
  432:     print LOCK $$;
  433:     close LOCK;
  434:     return 1;
  435: }
  436: #    Called by File::Find::find for each file examined.
  437: #
  438: #     Untaint the file and, if it is a directory,
  439: #     chmod it to 02770
  440: #
  441: sub set_permission {
  442:     $File::Find::name =~ /^(.*)$/;
  443:     my $safe_name = $1;		# Untainted filename...
  444:     
  445:     print "$safe_name" unless $noprint;
  446:     if(-d $safe_name) {
  447: 	print " - directory" unless $noprint;
  448: 	chmod(02770, $safe_name);
  449:     }
  450:     print "\n" unless $noprint;
  451: 
  452: }
  453: #
  454: #    Set up the correct permissions for all files in the 
  455: #    user's public htmldir. We just do a chmod -R 0660 ... for
  456: #    the ordinary files.  The we use File::Find
  457: #    to pop through the directory tree changing directories only
  458: #    to 02770:
  459: #
  460: sub set_public_html_permissions {
  461:     my ($topdir) = @_;
  462: 
  463:     #   Set the top level dir permissions (I'm not sure if find 
  464:     #   will enumerate it specifically), correctly and all
  465:     #   files and dirs to the 'ordinary' file permissions:
  466: 
  467:     system("chmod -R 0660 $topdir");
  468:     chmod(02770, $topdir);
  469: 
  470:     #  Now use find to locate all directories under $topdir
  471:     #  and set their modes to 02770...
  472:     #
  473:     print "Find file\n " unless $noprint;
  474:     File::Find::find({"untaint"         => 1,
  475: 		      "untaint_pattern" => qr(/^(.*)$/),
  476: 		      "untaint_skip"    => 1,
  477: 		      "no_chdir"         => 1,
  478: 		      "wanted"          => \&set_permission }, "$topdir");
  479: 
  480: 
  481: }
  482: 
  483: #-------------------------- Exit...
  484: #
  485: #   Write the file if the error_file is defined.  Regardless
  486: #   Exit with the status code.
  487: #
  488: sub Exit {
  489:     my ($code) = @_;		# Status code.
  490: 
  491:     # TODO: Ensure the error file is owned/deletable by www:www:
  492: 
  493:     &disable_root_capability();	# We run unprivileged to write the error file.
  494: 
  495:     print "Exiting with status $code error file is $error_file\n" unless $noprint;
  496:     if($error_file) {
  497: 	open(FH, ">$error_file");
  498: 	print FH  "$code\n";
  499: 	close(FH);
  500:     }
  501:     exit $code;
  502: }
  503: 
  504: =head1 NAME
  505: 
  506: lcuseradd - LON-CAPA setuid script to coordinate all actions
  507:             with adding a user with filesystem privileges (e.g. author)
  508: 
  509: =head1 DESCRIPTION
  510: 
  511: lcuseradd - LON-CAPA setuid script to coordinate all actions
  512:             with adding a user with filesystem privileges (e.g. author)
  513: 
  514: =head1 README
  515: 
  516: lcuseradd - LON-CAPA setuid script to coordinate all actions
  517:             with adding a user with filesystem privileges (e.g. author)
  518: 
  519: =head1 PREREQUISITES
  520: 
  521: =head1 COREQUISITES
  522: 
  523: =pod OSNAMES
  524: 
  525: linux
  526: 
  527: =pod SCRIPT CATEGORIES
  528: 
  529: LONCAPA/Administrative
  530: 
  531: =cut

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