#!/usr/local/bin/perl
#
#  Listgroup module
#
#  $Id: Listgroup.pm,v 1.16 2003-01-21 12:24:09-05 mprewitt Exp $
#
#  -----

=head1 NAME 

B<Listgroup.pm> - Lists hosts/users in a netgroup group.

=head1 SYNOPSIS

    use Listgroup;

    $array_ref_groups = listgroup();
    $array_ref_groups = listgroups();

    $array_ref_users_or_groups = listgroup({groupname});

    $array_ref_users_or_groups = listgroup_user({groupname1}, 
            [ [-]{groupname2}, [-]{gropuname3} ]);

    $array_ref_users_or_groups = listgroup_host({groupname1}, 
            [ [-]{groupname2}, [-]{gropuname3} ]);

=head1 DESCRIPTION

A library used to get groups or members of a netgroup NIS map.  
B<listgroup()> without any parameters or B<listgroups()> lists all 
the available netgroup groups.

With groupname parameters B<listgroup, listgroup_user, listgroup_host> will 
recusively list the members of the named groups.  If the groupname is preceded with
a B<-> members of that group will be excluded from the returned list.  Each member 
in a group is a triplet of (host,user,domain).  The host portion or user portion 
of the members is returned by B<listgroup_host()> and B<listgroup()>, 
the user portion of the members is returned by B<listgroup_user()>.

=head1 REQUIRES

Net::NIS

=head1 SEE ALSO

L<netgroup(4)>, L<listgroup(1)>, L<Net::NIS(3)>

=head1 AUTHOR

Original unknown

Major rewrite by Marc Prewitt <mprewitt@chelsea.net>

Copyright (C) 2003 Chelsea Networks, under the GNU GPL.
listgroup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
welcome to redistribute it under certain conditions; see the COPYING file 
for details.

listgroup 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.

listgroup 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.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

=head1 PUBLIC METHODS

=cut

package Net::NIS::Listgroup;
require Exporter;
@ISA    = qw(Exporter);
@EXPORT = qw(listgroup);
use strict;

use Net::NIS qw( :all );
use vars qw( $VERSION $DOMAIN );

$DOMAIN = Net::NIS::yp_get_default_domain();

my $YPCAT = '/usr/bin/ypcat';

$VERSION = (qw $Revision: 1.16 $)[1];

=head2 listgroups

    $array_ref_groups = listgroups();

Returns a reference to an array of groups from the netgroup nis map.

=cut
sub listgroups {
    my %netgroup;
    tie %netgroup, 'Net::NIS', 'netgroup';
    return [ sort keys %netgroup ];
}

=head2 listgroup_host, listgroup

    $array_ref_users_or_groups = listgroup({groupname1}, 
            [ [-]{groupname2}, [-]{gropuname3} ]);

    $array_ref_users_or_groups = listgroup_host({groupname1}, 
            [ [-]{groupname2}, [-]{gropuname3} ]);

Returns a reference to an array of the host portion of the members of the provided groups.  
Members of groupnames preceded by a B<-> will be excluded from the returned list.

Groups are processed in the order they appear in the parameter list.

If the NIS map 'netgroup' does not exist or another fatal NIS error
occurs, die will be called.  Wrap this call in an eval if you want 
to catch that type of error.

=cut
sub listgroup_host {
    my $r = Net::NIS::Listgroup::Request->new();
    $r->setHost();
    return $r->_listgroup(@_);
}

sub listgroup {
    my $r = Net::NIS::Listgroup::Request->new();
    $r->setHost();
    return $r->_listgroup(@_);
}

=head2 listgroup_user

    $array_ref_users_or_groups = listgroup_user({groupname1}, 
            [ [-]{groupname2}, [-]{gropuname3} ]);

Returns a reference to an array of the user portion of the members of the provided groups.  
Members of groupnames preceded by a B<-> will be excluded from the returned list.

Groups are processed in the order they appear in the parameter list.

If the NIS map 'netgroup' does not exist or another fatal NIS error
occurs, die will be called.  Wrap this call in an eval if you want 
to catch that type of error.

=cut
sub listgroup_user {
    my $r = Net::NIS::Listgroup::Request->new();
    $r->setUser();
    return $r->_listgroup(@_);
}

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

package Net::NIS::Listgroup::Request;

use Net::NIS qw( :all );

my $YPMATCH = '/usr/bin/ypmatch';

#
#  new returns an object used to encapsulate options passed in the
#  original request.  
#
sub new {
    my $type = shift;
    $type = ref($type) if ref($type);
    return bless {}, $type;
}

#
#  $request->setHost()
#
#  This requset will return host information
#
sub setHost {
    my $self = shift;
    $self->{user} = 0;
    return $self->{host} = 1;
}

#
#  $request->setUser()
#
#  This request will return user information
#
sub setUser {
    my $self = shift;
    $self->{host} = 0;
    return $self->{user} = 1;
}

#
#  $want_user = $request->getUser()
#
#  Whether the user field is wanted in the request.
#
sub getUser {
    my $self = shift;
    return $self->{user};
}

#
#  $request->_listgroup( @groups )
#
#  Returns a arrayref of members contained or not contained
#  in @groups.  If a group starts with a '-' it's members
#  will be excluded from the list.
#
sub _listgroup {
    my $r = shift;
    my @args = @_;

    my ( %returns );

    foreach my $netgroup (@args) {
        my $subtract;
        if ( $netgroup =~ s/^-// ) {
            $subtract = 1;
        }
        my ($status, $members) = Net::NIS::yp_match($Net::NIS::Listgroup::DOMAIN, 'netgroup', $netgroup);
        die "Unknown netgroup: $netgroup [$Net::NIS::yperr]\n" unless $status == YPERR_SUCCESS;

        $members =~ s/#.*//;   # remove comments

        foreach my $member ( split(/\s+/, $members) ) {
            if ($member =~ s/^\(//) {
                $member =~ s/\)$//;
                my ($host, $user, $domain) = split(/,/, $member);
                if ($r->getUser()) {
                    if ($subtract) {
                        delete $returns{$user};
                    } else {
                        $returns{$user} = $user if $user;
                    }
                } else {
                    if ($subtract) {
                        delete $returns{$host};
                    } else {
                        $returns{$host} = $host if $host;
                    }
                }
            } else {
                foreach my $thing (@{$r->_listgroup($member) || []}) {
                    if ($subtract) {
                        delete $returns{$thing};
                    } else {
                        $returns{$thing} = $thing if $thing;
                    }
                }
            }
        }
    }
    return [sort keys %returns];
}

1;