#!/usr/bin/eperl -w
# --------------------------------------------------------------------------
#
# Copyright (c) Ensim Corporation 2000, 2001   All Rights Reserved.
#
# This software is furnished under a license and may be used and copied
# only  in  accordance  with  the  terms  of such  license and with the
# inclusion of the above copyright notice. This software or any other
# copies thereof may not be provided or otherwise made available to any
# other person. No title to and ownership of the software is hereby
# transferred.
#
# The information in this software is subject to change without notice
# and  should  not be  construed  as  a commitment by Ensim Corporation.
# Ensim assumes no responsibility for the use or  reliability  of its
# software on equipment which is not supplied by Ensim.
#
# Exit codes (on failure error message goes to stderr):
#  0 - success
#  1 - failure
# 
# Boolean arguments on the command line are given as 0 or 1. All the
# command line arguments are encoded to avoid problems with escapes.
#
# All functions defined here either return an error message if an
# error occured and "" if everything went well or allways return a
# valid value, but exit (with code 1) printing an error message if an
# error occurs. This second type of functions have _e appended to
# their name. Functions are allowed to print results onto stdout, but
# errors are printed only in the main program.
#
# --------------------------------------------------------------------------
# $Id: setUIDRanges,v 1.3 2003/03/04 23:39:53 amit Exp $
# $Name:  $
# --------------------------------------------------------------------------

use strict;
push @INC, ($ENV{"OCW_SVCPATH"} || "/usr/lib/opcenter")."/virtualhosting";
if ($0 =~ /^(.+)\/([^\/]+)$/) {
  push @INC, $1; #the directory where the script resides
}

use lib ($ENV{OCW_SVCPATH} or "/usr/lib/opcenter") . "/virtualhosting";
use lib ($ENV{OCW_SVCPATH} or "/usr/lib/opcenter") . "/cmdline_common";

use ERRORS;
use Carper;

#Now we set aside some user IDs for the virtual users
my ($err);

if ($err= &initializeUIDRanges) {
  print STDERR "$err";
  exit 1;
}

exit 0;

#
# This function initializes the UID range system
#
sub initializeUIDRanges {
    my (@ranges)=([500,60000]); #the sorted list of the available uid ranges
    open(DEFS,"/etc/login.defs") or 
        $cerr += $E_UNABLEREADLOGINDEFS and
        return "Could not read /etc/login.defs .\n";
    my ($srvmin,$srvmax,$i)=(0,0,0);
    my (@defslines);
    my ($error_message);
    while (<DEFS>) {
	push (@defslines,$_);
	if ($_ =~ /^UID_MIN\s+(\d+)\s*$/) {
	    $srvmin = $i;
	    $ranges[0]->[0]=&MAX($ranges[0]->[0],$1);
	}
	if ($_ =~ /^UID_MAX\s+(\d+)\s*$/) {
	    $srvmax = $i;
	    $ranges[0]->[1]=&MIN($ranges[0]->[1],$1);
	}
	$i++;
    }

    #first, we determine if uidranges has been set. If so, ignore
    if (open(RANGES,"/etc/virtualhosting/uidranges.conf")) {
        while (<RANGES>) {
            if ($_ =~ /^VirtualUIDRange/) {
                return "";
            }
        }
        close(RANGES);
    }

    unless ($ranges[0]->[1]-$ranges[0]->[0] > 10) {
        $cerr += $E_TOOFEWUIDS ;
	return "Too few UIDs allowed by /etc/login.defs .\n";
    }

    #Nope, uidranges has not been set, so, take away uids from login.defs
    #block the main password file
    open (LOCK,">/etc/passwd.lock") or 
        $cerr += $E_UNABLELOCKPASSWD and 
        return "Could not lock /etc/passwd .\n";
    print LOCK "$$\n";
    close (LOCK);
    open (PASSWD,"/etc/passwd") or 
        $cerr += $E_UNABLEREADPASSWD and 
        return "Could not read password file.\n";
    while (<PASSWD>) {
	my (@field) = split (/:/,$_);
	my ($uid) = $field[2];
	next if ($uid < $ranges[0]->[0] or $uid > $ranges[$#ranges]->[1]);
	my ($i);
	for ($i=0;$i<=$#ranges;) {
	    if ($uid > $ranges[$i]->[1]) {
		$i++;
		next;
	    }
	    #We are exiting the inner loop one way or the other
	    if ($uid < $ranges[$i]->[0]) {
		last; #This means we have duplicate uids, but I don't care here
	    } elsif ($uid == $ranges[$i]->[0]) {
		$ranges[$i]->[0]++;
	    } elsif ($uid < $ranges[$i]->[1]) {
		splice (@ranges,$i+1,0,[$uid+1,$ranges[$i]->[1]]);
		$ranges[$i]->[1]=$uid-1;
		last;
	    } else { # $uid == $ranges[$i]->[1]
		$ranges[$i]->[1]--;
	    }
	    if ($ranges[$i]->[1]<$ranges[$i]->[0]) {
		splice (@ranges,$i,1);
	    }
	    last;
	}
    }
    close(PASSWD);
    my ($maxlow,$maxhigh,$maxsize)=(0,0,0);
    foreach (@ranges) {
	if ($_->[1] - $_->[0] >= $maxsize) {
	    $maxsize = $_->[1] - $_->[0];
	    $maxhigh = $_->[1];
	    $maxlow = $_->[0];
	}
    }
    if ($maxsize > 40000) {
	$maxlow = $maxhigh - 40000;
	$maxsize = 40000;
    }
    unless ($maxsize > 10) {
	unlink ("/etc/passwd.lock");
    $cerr += $E_UNABLEFINDUIDRANGE ;
	return "Could not find large enough free uid range.\n";
    }
    #leave some more space for the domain owners;
    $maxlow += int($maxsize/20) +1;
    unless (open (RANGES,">>/etc/virtualhosting/uidranges.conf")) {
	unlink ("/etc/passwd.lock");
    $cerr += $E_UNABLEAPPENDUIDRANGEFILE ;
	return "Could not append to uid range file /etc/virtualhosting/uidranges.conf .\n";
    }
    print RANGES "VirtualUIDRange $maxlow-$maxhigh\n";
    close (RANGES);
    if ($srvmax) {
	$defslines[$srvmax] = "UID_MAX\t\t\t".($maxlow-1)."\n";
    } else {
	push @defslines,"UID_MAX\t\t\t".($maxlow-1)."\n";
    }
    unless (open(DEFS,">/etc/login.defs")) {
	unlink ("/etc/passwd.lock");
    $cerr += $E_UNABLEWRITELOGINDEFS ;
	return "Could not write to /etc/login.defs .\n";
    }
    print DEFS @defslines;
    close(DEFS);
    unlink ("/etc/passwd.lock");
    return "";
}

sub MAX() {return ($_[0]>$_[1]?$_[0]:$_[1]);}
sub MIN() {return ($_[0]<$_[1]?$_[0]:$_[1]);}

