File:  [LON-CAPA] / loncom / Attic / lchtmldir
Revision 1.16: download - view: text, annotated - select for diffs
Thu Apr 7 22:27:52 2005 UTC (19 years, 1 month ago) by albertel
Branches: MAIN
CVS tags: version_1_99_1_tmcc, version_1_99_0_tmcc, HEAD
- commiting Martin Siegert's updates to work on systems that don't create user groups by default

    1: #!/usr/bin/perl
    2: 
    3: # The Learning Online Network with CAPA
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: #
    9: # LON-CAPA is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2 of the License, or
   12: # (at your option) any later version.
   13: #
   14: # LON-CAPA is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with LON-CAPA; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22: #
   23: # /home/httpd/html/adm/gpl.txt
   24: #
   25: # http://www.lon-capa.org/
   26: #
   27: #  lchtmldir - LONC-CAPA setuid script to:
   28: #              o If necessary, add a public_html directory 
   29: #                to the specified user home directory.
   30: #              o Set the permissions according to the authentication type.
   31: #
   32: #  Motivations:
   33: #     Originally, account creation would create a public_html
   34: #     directory for unix authorized people only.  It is possible to have
   35: #     Kerberos, internal and locally authorized 'users' which may be authors
   36: #     and hence need a properly owned an protected public_html directory
   37: #     to serve as their construction space.
   38: #
   39: #  Author:
   40: #    Ron Fox
   41: #    NSCL
   42: #    Michigan State University8
   43: #    East Lansing, MI 48824-1321
   44: 
   45: #   General flow of control:
   46: #   1. Validate process state (must be run as www).
   47: #   2. Validate parameters:  Need two parameters:
   48: #         o Homedir  - Home diretory of user 
   49: #         o Username - Name of the user.
   50: #         o AuthMode - Authentication mode, can be:
   51: #                      - unix
   52: #                      - internal
   53: #                      - krb4
   54: #                      - localauth
   55: #  3. Untaint the usename and home directory
   56: #
   57: #  4. As root if necessary, create $Homedir/public_html
   58: #  5. Set ownership/permissions according to authentication mode (AuthMode)
   59: #       - unix - ~owner:www/2775
   60: #       - krb4 - ~owner:www/2775
   61: #       - internal - www:www/2775
   62: #       - local    - www:www/2775
   63: #
   64: 
   65: #
   66: #   Take a few precautions to be sure that we're not vulnerable to trojan
   67: #   horses and other fine issues:
   68: #
   69: use strict; 
   70: use Fcntl qw(:mode);
   71: use DirHandle;
   72: use POSIX;
   73: 
   74: $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/sbin:/home/httpd/perl';
   75: delete @ENV{qw{IFS CDPATH ENV BASH_ENV}};
   76: 
   77: my $DEBUG = 1;                         # .nonzero -> Debug printing enabled.
   78: my $path_sep = "/";		# Unix like operating systems.
   79: 
   80: 
   81: # If the UID of the running process is not www exit with error.
   82: 
   83: if ($DEBUG) {
   84:     print("Checking uid...\n");
   85: }
   86: my $wwwid = getpwnam('www');
   87: &DisableRoot;
   88: if($wwwid != $>) {
   89:     if ($DEBUG) {
   90: 	print("User ID incorrect.  This program must be run as user 'www'\n");
   91:     }
   92:     exit 1;			# Exit with error status.
   93: }
   94: 
   95: # There must be three 'command line' parameters.  The first
   96: # is the home directory of the user.
   97: # The second is the name of the user.  This is only referenced
   98: # in code branches dealing with unix mode authentication.
   99: # The last is the authentication mode which must be one of unix, internal
  100: # krb4 or localauth.
  101: #   If there is an error in the argument count or countents, we exit with an
  102: # error.
  103: 
  104: if ($DEBUG) {
  105:     print("Checking parameters: \n");
  106: }
  107: if(@ARGV != 3) {
  108:     if($DEBUG) {
  109: 	print("Error: lchtmldir need 3 parameters \n");
  110:     }
  111:     exit 2;
  112: }
  113: my ($dir,$username,$authentication) = @ARGV;
  114: 
  115: if($DEBUG) {
  116:     print ("Directory = $dir \n");
  117:     print ("User      = $username \n");
  118:     print ("Authmode  = $authentication \n");
  119: 
  120: }
  121: 
  122: if( $authentication ne "unix:"     &&
  123:     $authentication ne "internal:" &&
  124:     $authentication !~ /^krb(4|5):(.*)/ &&
  125:     $authentication ne "localauth:") {
  126:     if($DEBUG) {
  127: 	print("Invalid authentication parameter: ".$authentication."\n");
  128: 	print("Should be one of: unix, internal, krb4, localauth\n");
  129:     }
  130:     exit 3;
  131: }
  132: 
  133: # Untaint the username.
  134: 
  135: my $match = $username =~ /^(\w+)$/;
  136: my $patt  = $1;
  137:  
  138: if($DEBUG) {
  139:    print("Username word match flag = ".$match."\n");
  140:     print("Match value = ".$patt."\n");
  141: }
  142: 
  143: my $safeuser = $patt;
  144: if($DEBUG) {
  145:     print("Save username = $safeuser \n");
  146: }
  147: if(($username ne $safeuser) or ($safeuser!~/^[A-z]/)) {
  148:     if($DEBUG) {
  149: 	print("User name $username had illegal characters\n");
  150:     }
  151:     exit 4;
  152: }
  153: 
  154: #untaint the base directory require that the dir contain only 
  155: # alphas, / numbers or underscores, and end in /$safeuser
  156: 
  157: $dir =~ /(^([\w\/]+))/;
  158: 
  159: my $dirtry1 = $1;
  160: 
  161: $dir =~ /$\/$safeuser/;
  162: my $dirtry2 = $1;
  163: 
  164: if(($dirtry1 ne $dir) or ($dirtry2 ne $dir)) {
  165:     if ($DEBUG) {
  166: 	print("Directory $dir is not a valid home for $safeuser\n");
  167:     }
  168:     exit 5;
  169: }
  170: 
  171: 
  172: # As root, create the directory.
  173: 
  174: my $homedir = $dirtry1;
  175: my $fulldir = $homedir."/public_html";
  176: 
  177: if($DEBUG) {
  178:     print("Full directory path is: $fulldir \n");
  179: }
  180: if(!( -e $dirtry1)) {
  181:     if($DEBUG) {
  182: 	print("User's home directory $dirtry1 does not exist\n");
  183:     }
  184:     if ($authentication eq "unix:") {
  185:         exit 6;
  186:     }
  187: }
  188: if ($authentication eq "unix:") {
  189:     # check whether group $safeuser exists.
  190:     my $usergroups = `id -nG $safeuser`;
  191:     if (! grep /^$safeuser$/, split(/\s+/,$usergroups)) { 
  192:         if($DEBUG) {
  193:             print("Group \"$safeuser\" does not exist or $safeuser is not a member of that group.\n");
  194:         }
  195:         exit 7;
  196:     }
  197: }
  198: 
  199: &EnableRoot;
  200: 
  201: &System("/bin/mkdir -p $fulldir")   unless (-e $fulldir);
  202:     unless(-e $fulldir."/index.html") {
  203: 	open OUT,">".$fulldir."/index.html";
  204: 	print OUT<<END;
  205: 	<html>
  206: 	<head>
  207: 	<title>$safeuser</title>
  208:         </head>
  209:         <body bgcolor="#ccffdd">
  210:         <h1>$safeuser Construction Space</h1>
  211:           <h2>
  212:             The Learning<i>Online</i> Network with Computer-Assisted Personalized Approach
  213:           </h2>
  214:           <p>
  215: This is your construction space within LON-CAPA, where you would construct resources which are meant to be
  216: used across courses and institutions.
  217:           </p>
  218:           <p>
  219: Material within this area can only be seen and edited by $safeuser and designated co-authors. To make
  220: it available to students and other instructors, the material needs to be published.
  221:           </p>
  222:         </body>
  223:        </html>
  224: END
  225:     close OUT;
  226:     }
  227: 
  228: &System("/bin/chmod  02770  $fulldir");
  229: &System("/bin/chmod  0770  $fulldir"."/index.html");
  230: 
  231: 
  232: # Based on the authentiation mode, set the ownership of the directory.
  233: 
  234: if($authentication eq "unix:") {	# Unix mode authentication...
  235:     print "Unix auth\n";
  236:     &System("/bin/chown -R   $safeuser:$safeuser"." ".$fulldir);
  237:     &JoinGroup($safeuser);
  238: } else {
  239:     # Internal, Kerberos, and Local authentication are for users
  240:     # who do not have unix accounts on the system.  Therefore we
  241:     # will give ownership of their public_html directories to www:www
  242:     # If the user is an internal auth user, the rest of the directory tree
  243:     # gets owned by root.  This chown is needed in case what's really happening
  244:     # is that a file system user is being demoted to internal user...
  245: 
  246:     if($authentication eq "internal:") {
  247: 	#  In case the user was a unix/filesystem authenticated user,
  248: 	#  we'll take a bit of time here to write  a script in the
  249: 	#  user's home directory that can reset ownerships and permissions
  250: 	#  back the way the used to be.
  251: 
  252: 	# This can take long enough for lond to time out, so we'll do it
  253: 	# in a separate process that we'll not wait for.
  254: 	#
  255: 	my $fpid = fork;
  256: 	if($fpid) {
  257: 	    &DisableRoot;
  258: 	    exit 0;
  259: 	} else {
  260: 	    print "Forked\n";
  261: 	    POSIX::setsid();	# Disassociate from parent.
  262: 	    print "Separate session\n";
  263: 	    &write_restore_script($homedir);
  264: 	    print "Restore script written\n";
  265: 	    &System("/bin/chown -R root:root ".$homedir);
  266: 	    &System("/bin/chown -R www:www  ".$fulldir);
  267: 	    print "Exiting\n";
  268: 	    exit 0;
  269: 	}
  270:     } else {
  271: 	&System("/bin/chown -R www:www  ".$fulldir);
  272:     }
  273: 
  274: }
  275: &DisableRoot;
  276: 
  277: exit 0;
  278: 
  279: #----------------------------------------------------------------------
  280: #
  281: #  Local utility procedures.
  282: #  These include:
  283: #     EnableRoot - Start running as root.
  284: #     DisableRoot- Stop running as root.
  285: #     JoinGroup  - Join www to the specified group.
  286: 
  287: # Turn on as root:
  288: 
  289: sub EnableRoot {
  290:     if ($wwwid==$>) {
  291: 	($<,$>)=($>,$<);
  292: 	($(,$))=($),$();
  293:     }
  294:     else {
  295: 	# root capability is already enabled
  296:     }
  297:     if($DEBUG) {
  298: 	print("Enable Root - id =  $> $<\n");
  299:     }
  300:     return $>;  
  301: }
  302: 
  303: sub DisableRoot {
  304:     if ($wwwid==$<) {
  305: 	($<,$>)=($>,$<);
  306: 	($(,$))=($),$();
  307:     }
  308:     else {
  309: 	# root capability is already disabled
  310:     }
  311:     if($DEBUG) {
  312: 	print("Disable root: id = ".$>."\n");
  313:     }
  314: }
  315: #
  316: #  Join the www user to the user's group.
  317: #  we must be running with euid as root at this time.
  318: #
  319: sub JoinGroup {
  320:     my $usergroup = shift;
  321: 
  322:     my $groups = `/usr/bin/groups www`;
  323:     # untaint
  324:     my ($safegroups)=($groups=~/:\s+([\s\w]+)/);
  325:     $groups=$safegroups;
  326:     chomp $groups; $groups=~s/^\S+\s+\:\s+//;
  327:     my @grouplist=split(/\s+/,$groups);
  328:     my @ugrouplist=grep {!/www|$usergroup/} @grouplist;
  329:     my $gl=join(',',(@ugrouplist,$usergroup));
  330:     if (&System('/usr/sbin/usermod','-G',$gl,'www')) {
  331: 	if($DEBUG) {
  332: 	    print "Error. Could not make www a member of the group ".
  333: 		"\"$usergroup\".\n";
  334: 	}
  335: 	exit 6;
  336:     }
  337:     if (-e '/var/run/httpd.pid') {
  338: 	open(PID,'/var/run/httpd.pid');
  339: 	my $pid=<PID>;
  340: 	close(PID);
  341: 	my ($safepid) = $pid=~ /(\d+)/;
  342: 	$pid = $safepid;
  343: 	if ($pid) {
  344: 	    my $status = system("kill -USR1 $safepid");
  345: 	}
  346:     }
  347: }
  348: 
  349: 
  350: 
  351: sub System {
  352:     my ($command,@args) = @_;
  353:     if($DEBUG) {
  354: 	print("system: $command with args ".join(' ',@args)."\n");
  355:     }
  356:     system($command,@args);
  357: }
  358: 
  359: 
  360: 
  361: 
  362: 
  363: #
  364: #   This file contains code to recursively process
  365: #   a Directory.  This is a bit more powerful
  366: #   than File::Find in that we pass the full
  367: #   stat info to the processing function.
  368: #     For each file in the specified directory subtree, 
  369: #   The user's Code reference is invoked for all files, regular and otherwise
  370: #   except:
  371: #      ., ..
  372: #
  373: #  Parameters:
  374: #     code_ref    - Code reference, invoked for each file in the tree.
  375: #                   as follows:  CodeRef(directory, name, statinfo)
  376: #                   directory the path to the directory holding the file.
  377: #                   name      the name of the file within Directory.
  378: #                   statinfo  a reference to the stat of the file.
  379: #     start_dir   - The starting point of the directory walk.
  380: #
  381: # NOTE:
  382: #   Yes, we could have just used File::Find, but since we have to get the
  383: #   stat anyway, this is actually simpler, as File::Find would have gotten
  384: #   the stat to figure out the file type and then we would have gotten it
  385: #   again.
  386: #
  387: 
  388: sub process_tree {
  389:     my ($code_ref, $start_dir)  = @_;
  390: 
  391:     my $dir = new DirHandle $start_dir; 
  392:     if (!defined($dir)) {
  393:         print "Failed to  open dirhandle: $start_dir\n";
  394:     }
  395: 
  396:     # Now iterate through this level of the tree:
  397: 
  398:     while (defined (my $name = $dir->read)) {
  399: 	next if $name =~/^\.\.?$/;       # Skip ., .. (see cookbook pg 319)
  400: 	
  401: 	my $full_name   = $start_dir.$path_sep.$name; # Full filename path.
  402: 	my @stat_info  = lstat($full_name);
  403: 	my $mode       = $stat_info[2];
  404: 	my $type       = $mode & 0170000; #  File type.
  405: 
  406: 	# Unless the file type is a symlink, call the user code:
  407: 
  408: 	unless ($type == S_IFLNK) {
  409: 	    &$code_ref($start_dir, $name, \@stat_info);
  410: 	}
  411: 
  412: 	# If the entry is a directory, we need to recurse:
  413: 
  414: 
  415: 	if (($type ==  S_IFDIR) != 0) {
  416: 	    &process_tree($code_ref, $full_name);
  417: 	}
  418:     }
  419: 
  420: }
  421: #
  422: #   Callback from process_tree to write the script lines
  423: #   requried to restore files to current ownership and permission.
  424: # Parameters:
  425: #    dir         - Name of the directory the file lives in.
  426: #    name        - Name of the file itself.
  427: #    statinfo    - Array from lstat called on the file.
  428: #
  429: #
  430: sub write_script {
  431:     my ($dir, $name, $statinfo) = @_;
  432: 
  433:     my $fullname = $dir.$path_sep.$name;
  434: 
  435:     #  We're going to '' the name, but we need to deal with embedded
  436:     #  ' characters.  Using " is much worse as we'd then have to
  437:     #  escape all the shell escapes too.  This way all we need
  438:     #  to do is replace ' with '\''
  439: 
  440:     $fullname =~ s/\'/\'\\\'\'/g;
  441: 
  442:     my $perms    = $statinfo->[2] & 0777; # Just permissions.
  443:     printf CHMODSCRIPT "chmod 0%o '%s'\n", $perms, $fullname;
  444:     printf CHMODSCRIPT "chown %d:%d '%s'\n", $statinfo->[4], $statinfo->[5], 
  445:                                          $fullname
  446: 
  447: 
  448: }
  449: # 
  450: #    Write a script in the user's home directory that can restore
  451: #    the permissions and ownerhips of all the files in the directory
  452: #    tree to their current ownerships and permissions.  This is done
  453: #    prior to making the user into an internally authenticated user
  454: #    in case they were previously file system authenticated and
  455: #    need to go back.
  456: #      The file we will create will be of the form
  457: #        restore_n.sh  Where n is a number that we will keep
  458: #   incrementing as needed until there isn't a file by that name.
  459: #   
  460: # Parameters:
  461: #    dir      - Path to the user's home directory.
  462: #
  463: sub write_restore_script {
  464:     my ($dir)   = @_;
  465: 
  466:     #   Create a unique file:
  467: 
  468:     my $version_number     = 0;
  469:     my $filename           = 'restore_'.$version_number.'.sh';
  470:     my $full_name           = $dir.$path_sep.$filename;
  471: 
  472:     while(-e $full_name) {
  473: 	$version_number++;
  474: 	$filename         = 'restore_'.$version_number.'.sh';
  475: 	$full_name        = $dir.$path_sep.$filename;
  476:     }
  477:     # $full_name is the full path of a file that does not yet exist
  478:     # of the form we want:
  479: 
  480:     open(CHMODSCRIPT, "> $full_name");
  481: 
  482:     &process_tree(\&write_script, $dir);
  483: 
  484:     close(CHMODSCRIPT);
  485: 
  486:     chmod(0750, $full_name);
  487: 
  488: }
  489: 
  490: 
  491: 
  492: 

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