package Debian::Adduser::Backend::Group;

# Handle actions in local files.

# Copyright (C) 2006-2008 Davor Ocelic <docelic@spinlocksolutions.com> (rewrite)
# Copyright (C) 2000 Roland Bauerschmidt <rb@debian.org>
# Most of the functions are adopted from the original adduser
# Copyright (C) 1997, 1998, 1999 Guy Maor <maor@debian.org>
# Copyright (C) 1995 Ted Hajek <tedhajek@boombox.micro.umn.edu>
#                     Ian A. Murdock <imurdock@gnu.ai.mit.edu>

use strict;
use warnings;

require Exporter;

use Debian::Adduser::Init qw/:gettext :exprs/;
use Debian::Adduser::Common qw/invalidate_nscd checkname :exec :check/;
use Debian::Adduser::Common qw/user_is_member :reporting/;
use Debian::Adduser::Common qw/:find_ids ch_gecos/;
use Debian::Adduser::Config qw/:cfg/;

our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw( );

sub new {
	my $class = shift;
	my $self  = {};

	bless $self, $class;
}


###########################################################################
# Add group
#
sub addgroup_check {
	WU 'new_group', R('ARGV0');

	printv q{Going to add Unix group `%s' (GID `%s') ...},
        R('new_group'), R('new_gid') || 'AUTOSELECT';

	if (defined getgrnam(R 'new_group')){
		dief q{The Unix group `%s' already exists.}, R('new_group')
	}

	if (defined(R 'new_gid') and defined(getgrgid(R 'new_gid'))){
		dief q{The Unix GID `%s' is already in use.}, R('new_gid')
	}

	if (!defined(R 'new_gid')) {
		# When EQUAL_IDS is enabled, then first_avail_uid() function
		# will return a number that is both valid UID and GID.
		# And when that is the case, we need to immediately save
		# the UID to new_uid, or the group will get created, and when
		# the adduser calls first_avail_uid, it will appear occupied.
		# By defining it now, we prevent the call to first_avail_uid
		# later.
		# (The action =~ /user/ part is added because for only
		# group additions, we don't require sync with UID).
		my $id;
		if ( R 'equal_ids' and R('action') =~ /user/ ) {
			$id = first_avail_uid;
		} else {
			$id = first_avail_gid;
		}

		if ( $id == -1 ) {
			warnf q{No free Unix GID available in range `%s' - `%s'.},
				R 'first_gid', R 'last_gid';
			dief q{The Unix group `%s' cannot be created.}, R('new_group'), {status=>23}
		} else {
			W 'new_gid', $id;
			if ( R 'equal_ids' and R('action') =~ /user/ ) {
				W 'new_uid', R('new_gid');
			}
		}
	}

  1
}

sub addgroup_exec_check {
}

sub addgroup_execute {

    invoke
     [ q{Adding Unix group `%s' (GID `%d') ...}, R('new_group'), R('new_gid')],
     [ 'groupadd', '-g', R('new_gid'), R('new_group')                        ],
		 [ 'groupdel', R('new_group')                                            ],
     { invalidate_nscd => 'group', sudo => 1                                   };

    if ( R('action') =~ /add(sys)?user$/ ) {
        W 'ingroup_name', R('new_group');
    }

	1
}


###########################################################################
# Del group
#
sub delgroup_check {
	local $_;
	WU 'existing_group', R 'ARGV0';
	if (R('existing_group') =~ /^\d/) {
		WU 'existing_gid', R 'existing_group';
		W 'existing_group', undef;
	}

	printv q{Going to delete group or GID `%s' ...},
        R('existing_group') || R('existing_gid');

    # It only makes sense to specify one, grp or gid
    if ( R('existing_group') and defined R('existing_gid') ) {
        printd q{The Unix group name will override GID to remove.};
        
        if ( R('existing_group') =~ /^\d+$/ and
            R('existing_group') != R('existing_gid') ) {

            dief q{Specifying both --gid and group name is weird, specifying} .
             q{ --group and then group again is even more so, and specifying} .
             q{ two group IDs which are different is just too much, buddi ;)};
        }
    }

    # Find out name/gid first. And retrieve group password
    # and members.
    if ( R('existing_group') ) {
        printd q{Retrieving data for group name `%s'.}, R('existing_group');

        # Read group information based on group name
        my @arr = (getgrnam( R('existing_group') ))[0,1,2];
        #W 'existing_group', $arr[0];
        W 'existing_grpw',  $arr[1];
        W 'existing_gid',   $arr[2];
        if ( R 'only_if_empty' ) {
					if ( $_ = (getgrnam( R('existing_group') ))[3]) {
            W 'existing_members', $_
					}
        }

    } elsif ( defined R('existing_gid') ) {
        printd q{Retrieving data for GID `%s'.}, R('existing_gid');

        # Read group information based on group GID
        my @arr = (getgrgid( R('existing_gid') ))[0,1,2];
        W 'existing_group', $arr[0];
        W 'existing_grpw',  $arr[1];
        #W 'existing_gid',   $arr[2];
        if ( R 'only_if_empty' ) {
					if ( local $_ = (getgrgid( R('existing_gid') ))[3] ) {
            W 'existing_members', $_
					}
        }
    }

    # Split members into @members
    my @members;
    local $_ = R 'existing_members' and @members = split /\s+/;

	unless ( R('existing_group') and defined R('existing_gid') ) {
		dief q{The group or GID `%s' does not exist.},
            R 'existing_group' || R 'existing_gid';
	}

    if ( R 'only_if_empty' ) {
        printd q{Group `%s' (GID `%s') has `%d' members.},
            R('existing_group'), R('existing_gid'), scalar @members;

    # We don't any more honor --system setting to del[sys]group. Who cares.
    # I moved exit status 3 that was reserved for this, to the block below.
    
        if (R 'existing_members') {
            dief q{Group `%s' (GID `%s') not empty, not deleting.},
                R('existing_group'), R('existing_gid'), { status => 3 };
        }

        setpwent;
        while ((my $acct, my $prigrp) = (getpwent)[0,3]) {
            if( $prigrp eq R('existing_gid') ) {
             dief q{User `%s' still has `%s' (GID `%s') as their} .
                 q{ primary group, not deleting.},
                 $acct, R('existing_group'), R('existing_gid'),
                 { status => 4 }; # XXX see if 4 is ok
            }
        }
        endpwent;
    }

    1
}

sub delgroup_exec_check {
}

sub delgroup_execute {

    # XXX if existing_members is initialized, on undo action re-add all
    # those users.
    invoke
        [ q{Deleting Unix group `%s' (GID `%d') ...},
            R('existing_group'), R('existing_gid')                  ],
        [ 'groupdel', R('existing_group')                           ],
        [ 'groupadd', '-g', R('existing_gid'), R('existing_group')  ],
        { invalidate_nscd => 'group', sudo => 1                       };
}

*addsysgroup_check = \*addgroup_check;
*addsysgroup_exec_check = \*addgroup_exec_check;
*addsysgroup_execute = \*addgroup_execute;
*delsysgroup_check = \*delgroup_check;
*delsysgroup_exec_check = \*delgroup_exec_check;
*delsysgroup_execute = \*delgroup_execute;


###########################################################################
# Add user to group
#
sub addusergroup_check {
    
	WU 'existing_user',  R 'ARGV0';
	WU 'existing_group', R 'ARGV1';

	printv q{Going to add Unix user `%s' to group `%s' ...},
        R 'existing_user', R 'existing_group';

	if ( !getpwnam( R 'existing_user' ) ) {
		dief q{The user `%s' does not exist.}, R 'existing_user'
	}

    # XXX support group by name and GID
	if ( !getgrnam( R 'existing_group' ) ) {
		dief q{The group `%s' does not exist.}, R 'existing_group'
	}

	# XXX doesn't catch situation where the target group is user's
	# primary group already
	if ( user_is_member( RM qw/existing_user existing_group/ ) ) {
		dief q{The user `%s' is already a member of `%s'.},
            R 'existing_user', R 'existing_group';
		#exit 0;
	}

}

sub addusergroup_exec_check {
}

sub addusergroup_execute {

    invoke
	    [ q{Adding Unix user `%s' to group `%s' ...},
            R 'existing_user', R 'existing_group'                   ],
        [ 'gpasswd', '-M',
            join(',',get_group_members(R 'existing_group'),R 'existing_user'),
             R 'existing_group'                                     ],
	    [ 'gpasswd', '-d', R 'existing_user', R 'existing_group'    ],
        { invalidate_nscd => 1, sudo => 1                             };

	1
}


###########################################################################
# Del user from group
#
sub delusergroup_check {
    
	WU 'existing_user',  R 'ARGV0';
	WU 'existing_group', R 'ARGV1';

	printv q{Going to remove Unix user `%s' from group `%s' ...},
        R 'existing_user', R 'existing_group';

	if ( !getpwnam( R 'existing_user' ) ) {
		dief q{The user `%s' does not exist.}, R 'existing_user'
	}

    # XXX support group by name and GID
	if ( !getgrnam( R 'existing_group' ) ) {
		dief q{The group `%s' does not exist.}, R 'existing_group'
	}

	# XXX doesn't catch situation where the target group is user's
	# primary group already
	if (! user_is_member( RM qw/existing_user existing_group/ ) ) {
		dief q{The user `%s' is not a member of `%s'.},
            R 'existing_user', R 'existing_group';
		#exit 0;
	}

}

sub delusergroup_exec_check {
}

sub delusergroup_execute {

    invoke
	    [ q{Removing Unix user `%s' from group `%s' ...},
            R 'existing_user', R 'existing_group'                   ],
	    [ 'gpasswd', '-d', R 'existing_user', R 'existing_group'    ],
        [ 'gpasswd', '-M',
            join(',', get_group_members(R 'existing_group')),
             R 'existing_group'                                     ],
        { invalidate_nscd => 1, sudo => 1                           };

	1
}


###########################################################################
# Add group as part of creating new user with own group
#

sub adduser_check {
	return if R 'ingroup_name';
	addgroup_check()
}
sub adduser_exec_check {
	return if R 'ingroup_name';
	addgroup_exec_check()
}
sub adduser_execute {
	return if R 'ingroup_name';
	addgroup_execute()
}

*addsysuser_check = \*adduser_check;
*addsysuser_exec_check = \*adduser_exec_check;
*addsysuser_execute = \*adduser_execute;
*addsysusergroup_check = \*addusergroup_check;
*addsysusergroup_exec_check = \*addusergroup_exec_check;
*addsysusergroup_execute = \*addusergroup_execute;

#
# Theoretically, we should remove a group when removing the user, 
# if it was his group and... But we don't have to, because the userdel
# script does take care of removing the group automatically.
#
sub nop {}
*deluser_check = \*nop;
*deluser_exec_check = \*nop;
*delsysuser_check = \*nop;
*delsysuser_exec_check = \*nop;
*deluser_execute = \*nop;
*delsysuser_execute = \*nop;

# Local Variables:
# mode:cperl
# End:
# vim:set ai et ts=4 sts=4 sw=4 tw=0:
1
