# -*- perl -*-
#
# Net::NIS::Tied - interface to YP^H^HNIS
#
# $Id: NIS.pm,v 1.9 2002/08/30 21:42:08 esm Exp $
#
package Net::NIS;

use strict;
# use warnings;			# Sigh, only available in 5.6 and above
use Carp;

###############################################################################
# BEGIN user-configurable section

# Linux and Solaris seem to have this file.  It contains a number of
# lines, each with a key/value pair (separated by spaces).
my $Nicknames_File = '/var/yp/nicknames';

# For those systems who don't have a nicknames file, here are some
# reasonable defaults.
my %Nicknames_Default =
  (
   passwd    => 'passwd.byname',
   group     => 'group.byname',
   networks  => 'networks.byaddr',
   hosts     => 'hosts.byname',
   protocols => 'protocols.bynumber',
   services  => 'services.byname',
   aliases   => 'mail.aliases',
   ethers    => 'ethers.byname',
  );


# Ouch.  It really hurts to enumerate these here, manually, instead of
# somehow relying on the autogenerated list made by h2xs.  But at least
# we have a test (t/yperr_num.t) that should catch inconsistencies.
#
# Please be sure to keep these in numerical order, starting with 0.  If
# There are ever gaps in the YPERR_xxx sequence, or duplicates, we will
# have to rethink this approach.  But until then, let's not worry.
use vars qw(@YPERRS);
@YPERRS = map { "YPERR_$_" }
  qw(
     SUCCESS
     BADARGS
     RPC
     DOMAIN
     MAP
     KEY
     YPERR
     RESRC
     NOMORE
     PMAP
     YPBIND
     YPSERV
     NODOM
     BADDB
     VERS
     ACCESS
     BUSY
    );

# Magic!  This variable is magically tied to a global in our .xs which
# keeps track of the status returned from the last yp_xxx() function.
#
# This variable is exported by default.  I'm not too happy with its
# name, but it seems like the best out of all the possibilities I
# considered.  The primary benefit is that, given the fixed nature
# of the YPERR_xxx constant names, '$yperr' will be easier for someone
# to remember than $yp_status, $ypstatus, $yp_err, or anything like that.
#
# Any other suggestions, before it's too late to change it?
use vars qw($yperr);

# END   user-configurable section
###############################################################################

use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $PKG);

require Exporter;
require DynaLoader;
require AutoLoader;

@ISA = qw(Exporter DynaLoader);

%EXPORT_TAGS = ( all => [ '$yperr', @YPERRS ] );
@EXPORT_OK   = (          '$yperr', @YPERRS   );
@EXPORT      = (          '$yperr'            );

$VERSION = '0.33';

$PKG = __PACKAGE__;		# For interpolating into error messages

#############
#  DESTROY  #  Not really used, but needed so AUTOLOAD doesn't trap it
#############
sub DESTROY {}

##############
#  AUTOLOAD  #  from h2xs
##############
sub AUTOLOAD {
    # This AUTOLOAD is used to 'autoload' constants from the constant()
    # XS function.  If a constant is not found then control is passed
    # to the AUTOLOAD in AutoLoader.

    my $constname;
    use vars qw($AUTOLOAD);
    ($constname = $AUTOLOAD) =~ s/.*:://;
    croak "& not defined" if $constname eq 'constant';
    my $val = constant($constname, @_ ? $_[0] : 0);
    if ($! != 0) {
	if ($! =~ /Invalid/) {
	  if ($constname =~ /^YP/) {
	    croak "No such constant, ${PKG}::$constname";
	  } else {
	    croak "No such function, ${PKG}::$constname()";
	  }
	}
	else {
		croak "Your vendor has not defined Net::NIS macro $constname";
	}
    }

    {
	no strict 'refs';
	*$AUTOLOAD = sub { $val };
    }
    goto &$AUTOLOAD;
}

bootstrap Net::NIS $VERSION;

# Magic: The $yperr variable will now have the YP status, int & string form
_yp_tie_status ($yperr);


######################
#  _expand_nickname  #  Look for a string in the /var/yp/nicknames file
######################
sub _expand_nickname($) {
  my $map = shift;

  use vars '%nickname';

  # First time through?  Read the nicknames file, or initialize to a
  # reasonable default (hardcoded above).
  if (keys %nickname == 0) {
    if (open NICKNAMES, $Nicknames_File) {
      while (defined (my $line = <NICKNAMES>)) {
	$line =~ /^\s*(\S+)\s+(\S+)$/
	  or next;
	$nickname{$1} = $2;
      }
      close NICKNAMES;
    } else {
      %nickname = %Nicknames_Default;
    }
  }

  # If there's a nickname defined for this map, return it... otherwise,
  # the map name itself.
  $nickname{$map} || $map;
}


#############
#  TIEHASH  #  establish the relationship between a hash and a YP map.
#############
sub TIEHASH {
  my $class = shift;

  # Second argument must be a map name (passwd, mail.aliases, etc)
  my $map = shift
    or croak "Usage: tie \%hash, $PKG, 'MAP NAME' [, 'DOMAIN' ]\n";

  # Third argument (optional) is the NIS domain.  If unset, bail out
  # now, setting error to NODOM ("Local domain name not set").  Otherwise,
  # if we try the yp_match, it fails with the less-than-helpful BADARGS.
  my $domain = shift || yp_get_default_domain()
    or do {
      $yperr = YPERR_NODOM();
      return undef;
    };

  # Check validity of map name.
  #
  # We can't do "ypwhich -m", because that seems to be magic... so let's
  # just try to look up a bogus key in this map.  If we get "no such key"
  # (or, improbably, success!), it means this map exists.  Anything else,
  # and we return failure.
  $map = _expand_nickname($map);
  yp_match ($domain, $map, '__SlArTiBaRtFaSt?');
  $yperr == YPERR_SUCCESS() || $yperr == YPERR_KEY()
    or return undef;

  # All OK.  Force $yperr to OK, and return a blessed object
  $yperr = YPERR_SUCCESS();
  bless { map => $map, domain => $domain }, $class;
}


###########
#  FETCH  #  read-only access to a key.
###########
sub FETCH {
  my $self = shift;
  my $key  = shift;

  # Have we slurped in all keys using yp_all() ?  Look up our key therein.
  if (exists $self->{_alldata} && exists $self->{_alldata}->{$key}) {
    return $self->{_alldata}->{$key};
  }

  # Haven't called yp_all(), or key not found there.  Do a real YP lookup.
  my $val = yp_match($self->{domain}, $self->{map}, $key);
  defined $val
    and return $val;

  # Error... is it 'no such key in map'?  That's OK
  $yperr == YPERR_KEY()
    and return undef;

  # Any other error: fatal
  croak sprintf("Unable to find '%s' in %s.  Reason: %s",
		$key, $self->{map}, $yperr);
}


############
#  EXISTS  #  Does a key exist?  This isn't cheap, it still incurs a yp_match
############
sub EXISTS {
  my $self = shift;

  defined $self->FETCH (@_);
}


##############
#  FIRSTKEY  #  For iterating with each() or keys()
##############
#
# Important note: this uses the yp_all() mechanism to slurp in a complete
# hash containing all the key/value pairs.  It is delayed until here,
# because our caller could simply want to perform lookups (via FETCH)
# without iterating over all keys.
#
sub FIRSTKEY {
  my $self = shift;

  # Each time we get called, slurp across again... just in case any
  # values have changed.  This is suboptimal: in effect, we're keeping
  # a cache around for who-knows-how-long.  Suggestions welcome for
  # improving it (perhaps keeping a {_last_updated} time??)
  $self->{_alldata} = yp_all ($self->{domain}, $self->{map});

  # Returned value must be a hash.  If it isn't, something very bad happened.
  if (ref $self->{_alldata} ne 'HASH') {
    croak sprintf("No such map '%s'.  Reason: %s",
		  $self->{map}, $yperr);
  }

  # Reset the each() operator, and let it do the rest.
  my $trashme = keys %{ $self->{_alldata} };
  return scalar each %{ $self->{_alldata} };
}

#############
#  NEXTKEY  #  no-brainer, just lets each() do the work on our internal hash
#############
sub NEXTKEY {
  my $self    = shift;

  return each %{ $self->{_alldata} };
}


# ------NO WRITE ACCESS ALLOWED------
sub _read_only(@) {
  croak "$PKG provides read-only access";
}

sub STORE  { _read_only(@_); }
sub DELETE { _read_only(@_); }


1;

__END__
