# -*-perl-*-

# $Id: Inetd.plm,v 1.7 2015/05/04 19:24:05 timc Exp $

# What package to handle inetd.conf/xinetd.conf

# $Log: Inetd.plm,v $
# Revision 1.7  2015/05/04 19:24:05  timc
# updated docs
#
# Revision 1.6  2015/05/04 19:22:16  timc
# made change for rhel7
#
# Revision 1.5  2004/11/12 18:26:46  thomas
# Spew about incomplete entries at debug level 2
# to avoid spewage about entries that contain only a 'disable' attribute.
#
# Revision 1.4  2004/02/10 16:40:11  thomas
# pre-0.1.5 fork
#
# Revision 1.3  2004/01/13 16:48:31  thomas
# catch-up 1/13/04
#
# Revision 1.2  2003/12/15 16:28:35  thomas
# check-in for 0.1.1
#
# Revision 1.1.1.1  2003/12/02 14:47:35  thomas
# Initial version before export to 0.1.1
#

=head1 NAME

What::Node::Inetd - manage internet services

=head1 DESCRIPTION

The What::Node::Inetd package controls the configuration of
the inernet daemon (inetd/xinetd).

Nodes under the B</Inetd> root node correspond to internet service
instances.  The node name is a unique key to allow later modification
of the service instance.  It also supplies the port to which the
service is bound when no port is specified via the B<service>
attribute.  The attributes of each service node are the attributes of
the service.  Depending on the inetnet daemon used on the target host,
it may not be possible to express some of the attributes (or even some
of the services).  The descriptions below are not intented to be a
reference for internet daemons.  Please read the fine manual pages for
your internet daemon for full descriptions of the various attributes.

One required attribute of the root B</Inetd> node, B<output_type>, must have
one of the values B<inetd> B<xinetd> or B<xinetd7>, which will determine the
output file name and type.  The B<xinetd7> type appends an includedir
directive at the bottom of the xinetd.conf file.

An optional root attribute B<minimum_size> provides a sanity-check
minimum size on the resulting configuration file.  A file containing
fewer than B<minimum_size> bytes will not be installed on the target
system.

Any triggers attached to the root node will be run any time the
configuration changes.

The service node attributes that can be expressed for both B<inetd>
and B<xinetd>-based systems include:

=over 4

B<service> -- The name of a valid service listed in the services file,
a numeric service port, or a valid RPC service name or program number.
As indicated above, if this attribute is missing, the configurator
will attempt to use the name of the node instead.

B<socket_type> -- One of stream, dgram, raw, seqpacket, or tli.

B<protocol> -- A valid protocol from /etc/protocols (e.g. tcp, udp,
tcp6, etc.).  For rpc services, the field consists of the string rpc
followed by a '/' (slash) and either a '*' (asterisk), one or more
nettypes, one or more netids, or a combination of nettypes and netids.

B<wait> -- Wait flag.

B<user> -- The user under which to run the service.

B<server> -- The program to execute for this service.

B<server_args> -- The B<inetd.conf>-style arguments (i.e. including
arg0) for this service.  The xinetd applier will remove the first
argument in this list when generating an B<xinetd> configuration.

B<disable> -- When the value of this attribute is non-zero, the service
will be disabled.

=back

In addition, almost any xinetd service attribute not listed above can
be included as a service node attribute and will be passed on to the
target xinetd.conf file.

=head1 EXAMPLES

  <node path='/Inetd' output_type='xinetd' minimum_size='2000' >
    <trigger path='Restart' init='xinetd' />
  </node>

  <node path="/Inetd/finger"
	wait="no"
	server="/s/finger/sbin/fingerd"
	socket_type="stream"	
	protocol="tcp"
	user="nobody" />

  <node path="/Inetd/sgi_fam"
	wait="yes"
	group="nobody"
	rpc_version="2"
	server="/usr/bin/fam"
	socket_type="stream"
	protocol="tcp"
	user="root"
	rpc_number="391002"
	type="RPC UNLISTED"
	bind="127.0.0.1" 
        disable="yes" />

=cut

package What::Node::Inetd;

use strict;
use FileHandle;
use Data::Dumper;

use base qw(What::Node);

our %opts = ();

# Set the priority for this module in $::
$::module_priority{'Inetd'} = 20;

###############################################################################
# Apply the current configuration

sub apply {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;

  my $rootattrs = $class->getattrs ($section);
  if (!defined $rootattrs->{output_type}) {
    $class->err ("No output file type specified! Skipping Inetd configuration.\n");
    return 0;
  }

  exists ($rootattrs->{minsize}) or $rootattrs->{minsize} = 0;

  if ($rootattrs->{output_type} =~ /^xinetd$/i) {
    return $class->apply_xinetd ($section, $what, $sname, $cname, $rootattrs);
  } 
  #xinetd7 output_type for rhel7 -- assume all tweaks go in /etc/xinetd.d/
  if ($rootattrs->{output_type} =~ /^xinetd7$/i) {
    return $class->apply_xinetd7 ($section, $what, $sname, $cname, $rootattrs);
  } else {
    $class->err ("Inetd: don't know how to configure a $rootattrs->{output_type} inet daemon.\n");
    $class->err ("You need to go write that.\n");
    return 0;
  }
}


###############################################################################
# apply_xinetd7
# Flesh out an xinetd stanza from the passed node.
# return the stanza, or an empty string on error.
#  this is for rhel7 where there is an /etc/xinetd.d directory
#  write all of the old stuff as before but also tack on the
#    includedir /etc/xinetd.d
#  at the end
###############################################################################
sub apply_xinetd7 {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;
  my $rootattrs = shift;

  my ($children, $newconfig);

  $newconfig = <<__Eof__
########
# xinetd configuration created by What::Node::Inetd
# Do not edit!  Hand-made changes will be lost!
########

__Eof__
  ;

  $children = $class->getchildren ($section);
  if ($children->{defaults}) {
    $newconfig .= $children->{defaults}->xinetd_getstanza ($section, 'defaults');
  }
  foreach (sort keys %$children) {
    $_ eq 'defaults' and next;
    $newconfig .= $children->{$_}->xinetd_getstanza ($section, $_);
  }

  #append the includedir on xinetd.conf
  $newconfig .= "\nincludedir /etc/xinetd.d\n";

  if ($class->check_and_replace_content ('/etc/xinetd.conf',
					 $newconfig,
					 $rootattrs->{minsize},
					 1
					)) {
    $class->run_triggers ($section, $what, $cname);
  }
  return 1;
}



###############################################################################
# apply_xinetd
# Flesh out an xinetd stanza from the passed node.
# return the stanza, or an empty string on error.
#    appropriate for rhel5, rhel6 where there is no xinetd.d inclusion
###############################################################################
sub apply_xinetd {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;
  my $rootattrs = shift;

  my ($children, $newconfig);

  $newconfig = <<__Eof__
########
# xinetd configuration created by What::Node::Inetd
# Do not edit!  Hand-made changes will be lost!
########

__Eof__
  ;

  $children = $class->getchildren ($section);
  if ($children->{defaults}) {
    $newconfig .= $children->{defaults}->xinetd_getstanza ($section, 'defaults');
  }
  foreach (sort keys %$children) {
    $_ eq 'defaults' and next;
    $newconfig .= $children->{$_}->xinetd_getstanza ($section, $_);
  }
  if ($class->check_and_replace_content ('/etc/xinetd.conf',
					 $newconfig,
					 $rootattrs->{minsize},
					 1
					)) {
    $class->run_triggers ($section, $what, $cname);
  }
  return 1;
}


###############################################################################
# Flesh out an xinetd stanza from the passed node.
# return the stanza, or an empty string on error.

sub xinetd_getstanza {
  my $node = shift;
  my $section = shift;
  my $name = shift;

  my $attrs = $node->getattrs ($section);
  exists ($attrs->{service}) or $attrs->{service} = $name;

  my $fail = 0;
  my $stanza = '';
  if ($name eq 'defaults') {
    $stanza = "defaults\n{\n";
  } else {
    $stanza = "service $attrs->{service}\n{\n";
    foreach ('service', 'socket_type', 'wait', 'user', 'server' ) {
      if (!defined $attrs->{$_}) {
	(($_ eq 'user' || $_ eq 'server')
	 && exists ($attrs->{type})
	 && ($attrs->{type} eq 'INTERNAL UNLISTED')) and next;
	$node->debug (2, "Required Inetd attribute $_ missing from service $name.\n");
	$fail++;
      }
    }
  }
  $fail and return '';

  foreach (sort keys %$attrs) {
    # Don't put the service in the body of the stanza
    $_ eq 'service' and next;
    if ($_ eq 'server_args') {
      # Don't include arg0 for xinetd systems
      my $newargs = $section->substitute_vars($attrs->{$_});
      $newargs =~ s/^\S+(\s+|$)//;
      length ($newargs) and $stanza .= "\t$_ = $newargs\n";
    } else {
      $stanza .= "\t$_ = " . $section->substitute_vars($attrs->{$_}) . "\n";
    }
  }
  $stanza .= "}\n\n";
  return $stanza;
}


###############################################################################

sub generate {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $filename = shift;

  # Assume $filenames refers to xinetd config files from filedist
  if (defined $filename) {
    return $class->generate_xinetd ($section, $what, $filename);
  }

  $filename = (defined ($::opts{root}) ? $::opts{root} : '') . '/etc/xinetd.conf';

  if (-f $filename) {
    return $class->generate_xinetd ($section, $what, $filename);
  }

  # Have to put in check (and generate method) for inetd.conf files

  return 0;

}

###############################################################################
#  Ooops.  This doesn't do includes (yet).

sub generate_xinetd {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $filename = shift;

  my ($attrs, $name, $i, %used);
  my $fh = new FileHandle $filename, 'r';
  while (<$fh>) {
    /^\s*\#/ and next;
    if (/^\s*defaults\s*/) {
      $attrs = $class->generate_xinetd_stanza ($fh);
      defined ($attrs) && keys (%$attrs) and 
	$class->setattrs ($section, $attrs, 'defaults', 1);
    } elsif (/\s*service\s+(\S+)\s*$/) {
      $name = $1;
      $attrs = $class->generate_xinetd_stanza ($fh);
      defined ($attrs) && keys (%$attrs) or next;
      if (defined $used{$name}) {
	$i=1;
	while (defined $used{"$name$i"}) {$i++};
	$attrs->{service} = $name;
	$name .= $i;
      }
      $class->setattrs ($section, $attrs, $name, 1);
      $used{$name}=1;
    }
  }
  return 1;
}


###############################################################################

sub generate_xinetd_stanza {
  my $class = shift;
  my $fh = shift;

  my $attrs = {};

  # Look for start stanza
  while (<$fh>) {
    /^\s*\{\s*$/ and last;
  }

  # Get keywords and/or end stanza
  while (<$fh>) {
    /\s*(\S+)\s*\=\s*(.*)$/ and $attrs->{$1} = $2;
    /^\s*\}\s*$/ and last;
  }

  return $attrs;
}

###############################################################################

1;
