File:  [LON-CAPA] / loncom / Attic / lcuseradd
Revision 1.38: download - view: text, annotated - select for diffs
Fri Jul 29 17:33:18 2005 UTC (18 years, 9 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_2_0, version_2_1_X, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_0_X, version_2_0_99_1, version_2_0_2, version_2_0_1, version_2_0_0, version_1_99_3, HEAD
Changes to accommodate suse, where new users are put into default group rather than into new group with same name as username.  Solve this by creating group first, before adding user. Also create user's home directory if not created automatically.

    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.38 2005/07/29 17:33:18 raeburn 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: #  Error file:
  125: 
  126: my $error_file;			# This is either the error file name or undef.
  127: 
  128: print "In lcuseradd\n" unless $noprint;
  129: 
  130: # ----------------------------- Make sure this process is running from user=www
  131: my $wwwid=getpwnam('www');
  132: &disable_root_capability;
  133: if ($wwwid!=$>) {
  134:     print("User ID mismatch.  This program must be run as user 'www'\n")
  135: 	unless $noprint;
  136:     &Exit(1);
  137: }
  138: 
  139: # ----------------------------------- Start running script with www permissions
  140: &disable_root_capability;
  141: 
  142: # --------------------------- Handle case of another lcpasswd process (locking)
  143: unless (&try_to_lock("/tmp/lock_lcpasswd")) {
  144:     print "Error. Too many other simultaneous password change requests being ".
  145: 	"made.\n" unless $noprint;
  146:     &Exit(4);
  147: }
  148: 
  149: # ------- Error-check input, need 3 values (user name, password 1, password 2).
  150: my @input;
  151: if (@ARGV>=3) {
  152:     @input=@ARGV;
  153: } elsif (@ARGV) {
  154:     print("Error. This program needs at least 3 command-line arguments (username, ".
  155: 	  "password 1, password 2 [errorfile]).\n") unless $noprint;
  156:     unlink('/tmp/lock_lcpasswd');
  157:     &Exit(2);
  158: } else {
  159:     @input=<>;
  160:     if (@input < 3) {
  161: 	print("Error. At least three lines should be entered into standard input.\n")
  162: 	    unless $noprint;
  163: 	unlink('/tmp/lock_lcpasswd');
  164: 	&Exit(3);
  165:     }
  166:     foreach (@input) {chomp;}
  167: }
  168: 
  169: my ($username,$password1,$password2, $error_file)=@input;
  170: print "Username = ".$username."\n" unless $noprint;
  171: $username=~/^(\w+)$/;
  172: print "Username after substitution - ".$username unless $noprint;
  173: my $safeusername=$1;
  174: print "Safe username = $safeusername \n" unless $noprint;
  175: 
  176: if (($username ne $safeusername) or ($safeusername!~/^[A-Za-z]/)) {
  177:     print "Error. The user name specified $username $safeusername  has invalid characters.\n"
  178: 	unless $noprint;
  179:     unlink('/tmp/lock_lcpasswd');
  180:     &Exit(9);
  181: }
  182: my $pbad=0;
  183: foreach (split(//,$password1)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
  184: foreach (split(//,$password2)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
  185: if ($pbad) {
  186:     print "Error. A password entry had an invalid character.\n" unless $noprint;
  187:     unlink('/tmp/lock_lcpasswd');
  188:     &Exit(10);
  189: }
  190: 
  191: #
  192: #   Safe the filename.  For our case, it must only have alpha, numeric, period
  193: #   and path sparators..
  194: #
  195: 
  196: print "Error file is $error_file \n" unless $noprint;
  197: 
  198: if($error_file) {
  199:     if($error_file =~ /^([(\w)(\d)\.\/]+)$/) {
  200: 	print "Error file matched pattern $error_file : $1\n" unless $noprint;
  201: 	my $safe_error_file = $1;	# Untainted I think.
  202: 	print "Error file after transform $safe_error_file\n"
  203: 	    unless $noprint;
  204: 	if($error_file == $safe_error_file) {
  205: 	    $error_file = $safe_error_file; # untainted error_file.
  206: 	} else {
  207: 	    $error_file ="";
  208: 	    print "Invalid error filename\n" unless $noprint;
  209: 	    Exit(14);
  210: 	}
  211: 
  212:     } else {
  213: 	$error_file="";
  214: 	print "Invalid error filename\n" unless $noprint;
  215: 	Exit(14);
  216:     }
  217: }
  218: 
  219: 
  220: # -- Only add the user if they are >not< in /etc/passwd.
  221: #    Used to look for the ability to create a new directory for the
  222: #    user, however that disallows authentication changes from i
  223: #    internal->fs.. so just check the passwd file instead.
  224: #
  225: my $not_found = system("cut -d: -f1 /etc/passwd | grep -q \"^$safeusername\$\" ");
  226: if (!$not_found) {
  227:     print "Error user already exists\n" unless $noprint;
  228:     unlink('/tmp/lock_lcpasswd');
  229:     &Exit(11);
  230: }
  231: 
  232: 
  233: 
  234: # -- Only add user if the two password arguments match.
  235: 
  236: if ($password1 ne $password2) {
  237:     print "Error. Password mismatch.\n" unless $noprint;
  238:     unlink('/tmp/lock_lcpasswd');
  239:     &Exit(13);
  240: }
  241: print "enabling root\n" unless $noprint;
  242: # ---------------------------------- Start running script with root permissions
  243: &enable_root_capability;
  244: 
  245: # ------------------- Add group and user, and make www a member of the group
  246: # -- Add group
  247: 
  248: print "adding group: $safeusername \n" unless $noprint;
  249: my $status = system('/usr/sbin/groupadd', $safeusername);
  250: if ($status) {
  251:     print "Error.  Something went wrong with the addition of group ".
  252:           "\"$safeusername\".\n" unless $noprint;
  253:     print "Final status of groupadd = $status\n";
  254:     unlink('/tmp/lock_lcpasswd');
  255:     &Exit(12);
  256: }
  257: my $gid = getgrnam($safeusername);
  258:                                                                                 
  259: # -- Add user
  260: 
  261: print "adding user: $safeusername \n" unless $noprint;
  262: my $status = system('/usr/sbin/useradd','-c','LON-CAPA user','-g',$gid,$safeusername);
  263: if ($status) {
  264:     print "Error.  Something went wrong with the addition of user ".
  265: 	  "\"$safeusername\".\n" unless $noprint;
  266:     system("/usr/sbin/groupdel $safeusername");
  267:     print "Final status of useradd = $status\n";
  268:     unlink('/tmp/lock_lcpasswd');
  269:     &Exit(12);
  270: }
  271: 
  272: print "Done adding user\n" unless $noprint;
  273: # Make www a member of that user group.
  274: my $groups=`/usr/bin/groups www` or &Exit(6);
  275: # untaint
  276: my ($safegroups)=($groups=~/:\s*([\s\w]+)/);
  277: $groups=$safegroups;
  278: chomp $groups; $groups=~s/^\S+\s+\:\s+//;
  279: my @grouplist=split(/\s+/,$groups);
  280: my @ugrouplist=grep {!/www|$safeusername/} @grouplist;
  281: my $gl=join(',',(@ugrouplist,$safeusername));
  282: print "Putting www in user's group\n" unless $noprint;
  283: if (system('/usr/sbin/usermod','-G',$gl,'www')) {
  284:     print "Error. Could not make www a member of the group ".
  285: 	  "\"$safeusername\".\n" unless $noprint;
  286:     unlink('/tmp/lock_lcpasswd');
  287:     &Exit(6);
  288: }
  289: 
  290: # ---------------------------------------------------------------- Set password
  291: # Set password with lcpasswd (which creates smbpasswd entry).
  292: 
  293: unlink('/tmp/lock_lcpasswd');
  294: &disable_root_capability;
  295: ($>,$<)=($wwwid,$wwwid);
  296: print "Opening lcpasswd pipeline\n" unless $noprint;
  297: open OUT,"|/home/httpd/perl/lcpasswd";
  298: print OUT $safeusername;
  299: print OUT "\n";
  300: print OUT $password1;
  301: print OUT "\n";
  302: print OUT $password1;
  303: print OUT "\n";
  304: close OUT;
  305: if ($?) {
  306:     print "abnormal Exit from close lcpasswd\n" unless $noprint;
  307:     &Exit(8);
  308: }
  309: ($>,$<)=($wwwid,0);
  310: &enable_root_capability;
  311: 
  312: # Check if home directory exists for user
  313: # If not, create one.
  314: if (!-e "/home/$safeusername") {
  315:     if (!mkdir("/home/$safeusername",0710)) {
  316:         print "Error. Could not add home directory for ".
  317:           "\"$safeusername\".\n" unless $noprint;
  318:         unlink('/tmp/lock_lcpasswd');
  319:         &Exit(15);
  320:     }
  321: }
  322: 
  323: # ------------------------------ Make final modifications to the user directory
  324: # -- Add a public_html file with a stand-in index.html file
  325: 
  326: if (-d "/home/$safeusername") {
  327:     system('/bin/chmod','-R','0660',"/home/$safeusername");
  328:     system('/bin/chmod','0710',"/home/$safeusername");
  329:     mkdir "/home/$safeusername/public_html",0755;
  330:     open OUT,">/home/$safeusername/public_html/index.html";
  331:     print OUT<<END;
  332: <html>
  333: <head>
  334: <title>$safeusername</title>
  335: </head>
  336: <body>
  337: <h1>Construction Space</h1>
  338: <h3>$safeusername</h3>
  339: </body>
  340: </html>
  341: END
  342: close OUT;
  343: }
  344: 
  345: #
  346: #   In order to allow the loncapa daemons appropriate access
  347: #   to public_html, Top level and public_html directories should
  348: #   be owned by safeusername:safeusername as should the smaple index.html..
  349: print "lcuseradd ownership\n" unless $noprint;
  350: system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername"); # First set std ownership on everything.
  351: &set_public_html_permissions("/home/$safeusername/public_html");
  352: #  system('/bin/chown',"$safeusername:www","/home/$safeusername");	# Now adust top level...
  353: #  system('/bin/chown','-R',"$safeusername:www","/home/$safeusername/public_html"); # And web dir.
  354: # ---------------------------------------------------- Gracefull Apache Restart
  355: if (-e '/var/run/httpd.pid') {
  356:     print "lcuseradd Apache restart\n" unless $noprint;
  357:     open(PID,'/var/run/httpd.pid');
  358:     my $pid=<PID>;
  359:     close(PID);
  360:     my  $pid=~ /(\D+)/;
  361:     my $safepid = $1;
  362:     if ($pid) {
  363: 	system('kill','-USR1',"$safepid");
  364:     }
  365: }
  366: # -------------------------------------------------------- Exit script
  367: print "lcuseradd Exiting\n" unless $noprint;
  368: &disable_root_capability;
  369: &Exit(0);
  370: 
  371: # ---------------------------------------------- Have setuid script run as root
  372: sub enable_root_capability {
  373:     if ($wwwid==$>) {
  374: 	($<,$>)=($>,0);
  375: 	($(,$))=($),0);
  376:     } else {
  377: 	# root capability is already enabled
  378:     }
  379:     return $>;
  380: }
  381: 
  382: # ----------------------------------------------- Have setuid script run as www
  383: sub disable_root_capability {
  384:     if ($wwwid==$<) {
  385: 	($<,$>)=($>,$<);
  386: 	($(,$))=($),$();
  387:     } else {
  388: 	# root capability is already disabled
  389:     }
  390: }
  391: 
  392: # ----------------------- Make sure that another lcpasswd process isn't running
  393: sub try_to_lock {
  394:     my ($lockfile)=@_;
  395:     my $currentpid;
  396:     my $lastpid;
  397:     # Do not manipulate lock file as root
  398:     if ($>==0) {
  399: 	return 0;
  400:     }
  401:     # Try to generate lock file.
  402:     # Wait 3 seconds.  If same process id is in
  403:     # lock file, then assume lock file is stale, and
  404:     # go ahead.  If process id's fluctuate, try
  405:     # for a maximum of 10 times.
  406:     for (0..10) {
  407: 	if (-e $lockfile) {
  408: 	    open(LOCK,"<$lockfile");
  409: 	    $currentpid=<LOCK>;
  410: 	    close LOCK;
  411: 	    if ($currentpid==$lastpid) {
  412: 		last;
  413: 	    }
  414: 	    sleep 3;
  415: 	    $lastpid=$currentpid;
  416: 	} else {
  417: 	    last;
  418: 	}
  419: 	if ($_==10) {
  420: 	    return 0;
  421: 	}
  422:     }
  423:     open(LOCK,">$lockfile");
  424:     print LOCK $$;
  425:     close LOCK;
  426:     return 1;
  427: }
  428: #    Called by File::Find::find for each file examined.
  429: #
  430: #     Untaint the file and, if it is a directory,
  431: #     chmod it to 02770
  432: #
  433: sub set_permission {
  434:     $File::Find::name =~ /^(.*)$/;
  435:     my $safe_name = $1;		# Untainted filename...
  436:     
  437:     print "$safe_name" unless $noprint;
  438:     if(-d $safe_name) {
  439: 	print " - directory" unless $noprint;
  440: 	chmod(02770, $safe_name);
  441:     }
  442:     print "\n" unless $noprint;
  443: 
  444: }
  445: #
  446: #    Set up the correct permissions for all files in the 
  447: #    user's public htmldir. We just do a chmod -R 0660 ... for
  448: #    the ordinary files.  The we use File::Find
  449: #    to pop through the directory tree changing directories only
  450: #    to 02770:
  451: #
  452: sub set_public_html_permissions {
  453:     my ($topdir) = @_;
  454: 
  455:     #   Set the top level dir permissions (I'm not sure if find 
  456:     #   will enumerate it specifically), correctly and all
  457:     #   files and dirs to the 'ordinary' file permissions:
  458: 
  459:     system("chmod -R 0660 $topdir");
  460:     chmod(02770, $topdir);
  461: 
  462:     #  Now use find to locate all directories under $topdir
  463:     #  and set their modes to 02770...
  464:     #
  465:     print "Find file\n " unless $noprint;
  466:     File::Find::find({"untaint"         => 1,
  467: 		      "untaint_pattern" => qr(/^(.*)$/),
  468: 		      "untaint_skip"    => 1,
  469: 		      "no_chdir"         => 1,
  470: 		      "wanted"          => \&set_permission }, "$topdir");
  471: 
  472: 
  473: }
  474: 
  475: #-------------------------- Exit...
  476: #
  477: #   Write the file if the error_file is defined.  Regardless
  478: #   Exit with the status code.
  479: #
  480: sub Exit {
  481:     my ($code) = @_;		# Status code.
  482: 
  483:     # TODO: Ensure the error file is owned/deletable by www:www:
  484: 
  485:     &disable_root_capability();	# We run unprivileged to write the error file.
  486: 
  487:     print "Exiting with status $code error file is $error_file\n" unless $noprint;
  488:     if($error_file) {
  489: 	open(FH, ">$error_file");
  490: 	print FH  "$code\n";
  491: 	close(FH);
  492:     }
  493:     exit $code;
  494: }
  495: 
  496: =head1 NAME
  497: 
  498: lcuseradd - LON-CAPA setuid script to coordinate all actions
  499:             with adding a user with filesystem privileges (e.g. author)
  500: 
  501: =head1 DESCRIPTION
  502: 
  503: lcuseradd - LON-CAPA setuid script to coordinate all actions
  504:             with adding a user with filesystem privileges (e.g. author)
  505: 
  506: =head1 README
  507: 
  508: lcuseradd - LON-CAPA setuid script to coordinate all actions
  509:             with adding a user with filesystem privileges (e.g. author)
  510: 
  511: =head1 PREREQUISITES
  512: 
  513: =head1 COREQUISITES
  514: 
  515: =pod OSNAMES
  516: 
  517: linux
  518: 
  519: =pod SCRIPT CATEGORIES
  520: 
  521: LONCAPA/Administrative
  522: 
  523: =cut

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