#!/usr/bin/perl
#
# addgroup: a utility to add groups to the system
# 	$Id: addgroup,v 0.10 1995/03/02 03:24:38 tedhajek Exp $	

#
# Copyright (C) 1995 Ted Hajek <tedhajek@boombox.micro.umn.edu>
# General scheme of the program adapted by the debian 'adduser'
#  program by Ian A. Murdock <imurdock@gnu.ai.mit.edu>.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# NOTE: addgroup should be used to add system, maintenance and
#       project groups to the system.
#
#       By default, the debian 'adduser' program creates
#       a group for each created user; this group has the same
#       ID number as the associated user.
#
#       Therefore, we shouldn't create any groups with GID equal
#       to or greater than the lowest UID used by adduser.
#       This is given in the file /etc/adduser.conf in an entry
#       such as "FIRST_UID=1000"
#
#       Furthermore, the first 99 GIDs are reserved for the base
#       system maintainer.  Therefore, we should add groups with
#       GIDs greater than or equal to 100.
#
#       If there is no available GID under FIRST_UID, exit with
#       a suitable error message.
#
##
## TODO
##
# 
# - allow users belonging to group to be specified on command line?


# are we debugging?
$debugging = 0;

##
## need to trap signals 1 2 3 and 15 here so user can't kill us
## when things are in some half-baked state.
##

# Set up some important things:
$GROUP = "/etc/group";		# the group file
$GBACK = "/etc/group~";		# the backup group file
$GLOCK = "/etc/gtmp";		# the lock file
                                # I don't know if this is standard,
				# but it should be :-)
				# I'll work out a better locking scheme
				# later.
$DEFAULTS = "/etc/adduser.conf";
$default_first_uid = 1000;	# value to use if we can't find one
				# specified in the adduser.conf file
$MIN_GID = 100;			# leave some room for system stuff

##
## check if certain conditions are met before we start
## plodding around within the group file.
##

# we must be root.
if ($> != 0) {
    print "$0: only root may add groups to the system.\n";
    exit 1;
}
# the user must specify a group name to add
$group_name = shift(@ARGV);
print STDOUT "$group_name\n" if $debugging;
if (! $group_name) {
    print "$0: you must specify a group name to be added.\n";
    print "For example, '$0 junk-maintainers'.\n";
    exit 1;
}
# check if the group to be added already exists
if (&group_exists($group_name)) {
    print "$0: the group you specified already exists.\n";
    exit 1;
}
# check if the group file is locked by some other process
if (-f $GLOCK) {
    print "$0: $GROUP is locked.  Try again later.\n";
    exit 1;
}

##
## lock the group file
##
link $GROUP, $GLOCK;

# read the file of defaults.
$first_uid = &get_first_uid($DEFAULTS, $default_first_uid);

# suck the existing GIDs into an array
@current_gids = &get_current_gids;
# sort 'dem GIDs
@sorted_gids = sort {$a <=> $b} @current_gids;
# find the first available GID
$last_one = shift(@sorted_gids);
while ($this_one = shift(@sorted_gids)) {
    if ($this_one <= $MIN_GID) {
	$last_one = $this_one; # advance if we're under $MIN_GID
	next;
    }
    if ($this_one > $last_one + 1) {
	$lowest_available = $last_one + 1;
	last;
    } else {
	$last_one = $this_one;
    }
}
# if we haven't yet set $lowest_available, there's no hole.
$lowest_available = ($last_one + 1) if (! $lowest_available);
# make sure we're at the minimum.
$lowest_available = $MIN_GID if ($lowest_available < $MIN_GID);

# make sure that we're below the ceiling
if ($lowest_available >= $first_uid) {
    print "$0: no GID available beneath first normal user's ID.\n";
    exit 1;
}

# save the original group file
open (GBACK, ">$GBACK") || die "open: $!";
open (GROUP, "$GROUP") || die "open: $!";
while (<GROUP>) {
    print GBACK;
}
close GROUP;
close GBACK;

# write the new entry at the appropriate place in the group file
open (GROUP, ">$GROUP") || die "open: $!";
open (GBACK, "$GBACK") || die "open: $!";
$added_new_one = 0;
while($line = <GBACK>) {
    chop $line;
    ($name, $passwd, $gid, $members) = split(/:/, $line);
    if ($gid < $lowest_available || $added_new_one) {
	print GROUP $line, "\n";
	print STDOUT "GID: $gid\n" if $debugging;
    } else {			# we haven't added the new one yet
	print GROUP "${group_name}::${lowest_available}:\n";
	$added_new_one = 1;
	print STDOUT "Just added new one\n" if $debugging;
	print GROUP $line, "\n";
	print STDOUT "GID: $gid\n" if $debugging;
    }
}
print GROUP "${group_name}::${lowest_available}:\n" if (! $added_new_one);

close GROUP;
close GBACK;

# we're done!  Unlock the group file
unlink $GLOCK;
print "done.\n";
exit 0;

###################################################
#=================================================#
###################################################

###
### Subroutines
###

############################
# get_current_gids
############################
# iterate through the group file; return an array
# containing all the GIDs
sub get_current_gids {
    local (@gids);
    local ($name, $passwd, $gid, $members); # the return values of "getgrent";

    setgrent;
    while (($name, $passwd, $gid, $members) = getgrent) {
	push @gids, $gid;
    }
    endgrent;

    return @gids;
}

############################
# get_first_uid
############################
# parse the defaults file for the first user UID number.
# if the default file or entry doesn't exist, return the
# SYSTEM default value given in 'adduser'.
sub get_first_uid {
    local($conf_file, $default) = @_;
    local($current_line, $tag, $uid);

    # if there's not a config file, return the default.
    print "Couldn't find $conf_file\n" if ($debugging && (! -f $conf_file));
    return $default if (! -f $conf_file);

    open (CONF, $conf_file) || die "open: $!";
    while ($current_line = <CONF>) {
	chop $current_line;
	if ($current_line =~ /^FIRST_UID=(\d+)$/) {
	    $uid = $1;
	    last;
	}
    }
    close (CONF);

    print "Got UID = $uid from $conf_file\n" if ($uid && $debugging);
    return $uid if ($uid);
    print "Using default UID = $default\n" if $debugging;
    return $default;
}



#############################
# group_exists
#############################
# takes a group name as an argument.  If the group already exists,
# return 1.  If it doesn't, return 0.
sub group_exists {
    local ($group_name) = @_;
    local ($exists);		# does group exist?
    local ($name, $passwd, $gid, $members); # the return values of "getgrent";

    $exists = 0;
    # reset the group lookup function
    setgrent;
    # iterate through the group file.  If the group exists, return 1.
    while (($name, $passwd, $gid, $members) = getgrent) {
	if ($name eq $group_name) {
	    $exists = 1;
	    last;
	}
    }
    # close the group file
    endgrent;

    return $exists;
}





