#!/usr/bin/perl -w
use strict;
use Alterator::Backend3;

# set translation domain
$TEXTDOMAIN = "alterator-xinetd";

$DEBUG=0;

# Данные из всех конфигурационных файлов (/etc/xinetd.d/*, /etc/xinetd.conf)
# читаются в общую структуру данных (хэш по id сервиса).
# Чтение происходит по первому запросу list (чтоб успел правильно выставиться
# язык)
#
# id берется из параметра "id", или, если его нет, из названия сервиса.
# Названия сервисов с разными id могут совпадать (и для udp и tcp версий
# INTERNAL services обычно совпадают) - так удивительно сделано в xinetd.
# Названия конф.файлов и то, сколько секций в них содержится - роли не играет
# (раньше было не так). Секция default ищется только в /etc/xinetd.conf.
#
# Секция default записывается в общую структуру под id __common_settings__
# Вообще, default settings требуют дальнейшего изучения...
# (Параметры default settings влияют на те секции, в которых эти параметры не
# установлены. То есть, для секций надо различать понятия "не установлен" и
# "пустая строка". Как это сделать - непонятно, даже на уровне интерфейса.)
#
# Пока сделал так, что пустые строки в конф.файл не записываются. Это больше
# всего соответствует заявленному поведению "common settings используются для
# тех сервисов, для которых соответствующие поля не установлены". Возможно,
# стоит вообще убрать редактирование секции defaults - пользы от нее мало,
# а путаница возможна...
#
# Для каждого сервиса хранится имя файла, в котором находится секция и
# порядковый номер секции с таким именем в этом файле (для различия одноименных
# секций).
#
# Польза от того, что все данные читаются сразу:
# - хочется сделать "лампочки" с состоянием сервиса в списке,
#   а для этого так и так надо разбирать все файлы
# - при чтении списка можно правильно обрабатывать id
# - можно устраивать отдельные команды бакенду commit/reset...
#
# По команде list бекенд, ничего не перечитывая, быстро отдает список.
# По команде read бакенд перечитывает только нужный файл
#  (Возможно, несколько секций!) и отдает соответствующую информацию
# По команде write бакенд сохраняет информацию в структуру и перезаписывает
#  нужную секцию в нужном файле...
# Команды commit/reset пока не делаю...

my $SERVICE_PROG = "/sbin/service";
my $XINETD_DIR   = "/etc/xinetd.d";
my $XINETD_CONF  = "/etc/xinetd.conf";
#my $LOGFILE      = "/var/log/configd.log";
#open LOG, $LOGFILE or warn "can't open $LOGFILE\n";



my @params_returned_by_list =
  ("label", "label2", "pic");

my @params_returned_by_read =
  ("name", "label", "label2", "description", "user", "group", "server", "server_args", "rlimit_as",
   "instances", "per_source", "only_from", "state", "type", "bind");


my @params_for_read =
  ("user", "group", "server", "server_args", "rlimit_as",
   "instances", "per_source", "only_from", "disable", "id", "type", "bind", "interface");

my @params_for_write =
  ("user", "group", "server", "server_args", "rlimit_as",
   "instances", "per_source", "only_from", "disable", "bind", "interface");

my @params_for_write_defaults =
  ("instances", "per_source", "only_from");

my @params_for_read_defaults =
  ("instances", "per_source", "only_from");


# main data table
my %data; # hash of hashes


#sub debug{
#  my $text  = shift;
#  my $errno = shift;
#  printf LOG "%s $TEXTDOMAIN: $text: $errno\n", time;
#}

### reading defaults section and putting data to %data
sub read_defaults{
  my $file = shift;
  my $is_in   = 0;
  my %service_info = ();

  open CF, $file or warn "can't open $file\n";
  foreach my $line (<CF>){

    chomp($line);
    # outside defaults section
    if ($is_in == 0){
      if ($line =~ /^\s*defaults/){
        $is_in = 1;
        $service_info{name}   = "__common_settings__";
        $service_info{id}     = "__common_settings__";
        $service_info{label}  = _("Common settings");
        $service_info{label2} = _("Common settings");
        $service_info{file}   = $file;
        $service_info{description} =
          _("These settings will be applied to services with undefined fields");
        $service_info{state}  = '#t';
        $service_info{type}   = '#f';
        $service_info{pic}    = '';
        next;
      }
    }

    if (($is_in == 1 ) && ($line =~ /^\s*{/)) {
      foreach (@params_for_read_defaults){
        $service_info{$_} = "";
      }
      $is_in = 2;
      next;
    }

    # inside section
    if ($is_in == 2){

      foreach (@params_for_read_defaults){
        if ($line =~ /^\s*$_\s*=\s*(.*)$/){
	  $service_info{$_} = $1;
	}
      }

      if ($line =~ /^\s*}/) { # end of section
        $is_in = 0;
        $data{$service_info{id}} = {%service_info};
	return;
      }
    }
  }
}

# this stuff is doing after write action to
# provide correct data for list
sub set_label{
  my $info = shift; # ref to object hash
  my $label_prefix="";
  $label_prefix = "+ " if $info->{disable} eq 'no';
  $label_prefix = "-- " if $info->{disable} eq 'yes';
  $info->{label}  = $label_prefix . $info->{id};
  $info->{label2} = $info->{id};
  $info->{pic}    = ($info->{disable} eq 'no')? 'on':'off';
}

### reading service sections and putting data to %data
sub read_file{
  my $file = shift;

  my $is_desc = 0;
  my $is_in   = 0;
  my %service_info = ();
  my %counts = (); # numbers of sections with same name

  open CF, $file or warn "can't open $file\n";
  foreach my $line (<CF>){
    chomp($line);

    # outside service section
    if ($is_in == 0){

      # reading description
      if ($line =~ /^#\s*description:\s*([^\\]*)(\\?)$/) {
        $service_info{description}=$1;
	  if (defined $2){ $is_desc = 1; } # to be continued...
	  else {$is_desc = 0; }
        next;
      }
      if (($is_desc == 1) && ($line =~ /^#\s*([^\\]*)(\\?)$/)) {
        $service_info{description}.=" $1";
        if (defined $2){ $is_desc = 1; } # to be continued...
        else {$is_desc = 0; }
        next;
      }

      if ($line =~ /^\s*service\s+(\S+)\s*$/){
        foreach (@params_for_read){
          $service_info{$_} = '';
        }
	$service_info{name}  = $1;
	$service_info{id}    = $1;
	if (!exists($counts{$1})) {$counts{$1}=1;}
	else {$counts{$1}++;}
	$service_info{num}   = $counts{$1};

        $is_in = 1;
        next;
      }
    }

    if (($is_in == 1 ) && ($line =~ /^\s*{/)) {
      $is_in = 2;
      next;
    }

    # inside service section
    if ($is_in == 2){

      foreach (@params_for_read){
        if ($line =~ /^\s*$_\s*=\s*(.*)$/){
	  $service_info{$_} = $1;
	}
      }

      if ($line =~ /^\s*}/) { # end of section
        $is_in = 0;
        $service_info{file} = $file;
        set_label(\%service_info);
        $service_info{state} = ($service_info{disable} eq 'no')? '#t':'#f';
	$service_info{description} = '' unless exists($service_info{description});
	my $dt="/etc/alterator/xinetd/$service_info{id}.desktop";
	$service_info{description} =
	  `alterator-dump-desktop -v lang="$LANGUAGE" -v out="Comment" "$dt"` if -f $dt;
	$service_info{type}  = ($service_info{type} eq "INTERNAL")? '#t':'#f';
	$service_info{bind} = $service_info{interface} if ($service_info{bind} eq '');
        $data{$service_info{id}} = {%service_info};
	%service_info = ();
      }
    }
  }
  close CF;
}


###

sub write_section{
  my $id = shift;
  my $temp = `mktemp`; chomp($temp);
  my $file = $data{$id}->{file};
  my $name = $data{$id}->{name};
  my $num  = $data{$id}->{num};
  my $is_in=0;

  open CF, $file or write_error(_("Can't open file"), $file);
  open TEMP, "> $temp" or write_error(_("Can't open temporary file"), $temp);

  $data{$id}->{disable} = (defined($data{$id}->{state}) && ($data{$id}->{state} eq '#t'))? 
                            'no':'yes';

  # It can be more then one section with the same name
  # Let's find $num-th section with
  # name $name in $file

  foreach my $line (<CF>){

    # outside service section
    if ($is_in == 0){
      print TEMP $line;
      if ($line =~ /^\s*service\s*$name\s*$/){
	$num--;
        $is_in = 1 if ($num == 0);
      }
    }

    if (($is_in == 1 ) && ($line =~ /^\s*{/)) {
      print TEMP $line;
      # writing down all changable params
      foreach (@params_for_write){
        next if !exists($data{$id}->{$_}) ||
                !defined($data{$id}->{$_}) ||
                ($data{$id}->{$_} eq "");
        print TEMP "\t$_\t=\t$data{$id}->{$_}\n";
      }
      $is_in = 2;
      next;
    }

    # inside service section
    if ($is_in == 2){

      # deleting old values
      my $del=0;
      foreach (@params_for_write){
        if ($line =~ /^\s*$_\s*=/){
          $del=1;
          last;
        }
      }
      print TEMP $line unless $del;

      if ($line =~ /^\s*}/) { # end of section
        $is_in = 0;
      }
    }
  }
  close (CF);
  close (TEMP);
  # do not use rename!
  `mv $temp $file`;
}



sub write_defaults{
  my $id = '__common_settings__';
  my $temp = `mktemp`; chomp($temp);
  my $file = $XINETD_CONF;
  my $name = $data{$id}->{name};
  my $num  = $data{$id}->{num};
  my $is_in=0;

  open CF, $file or write_error(_("Can't open file"), $file);
  open TEMP, "> $temp" or write_error(_("Can't open temporary file"), $temp);

  # It can be more then one section with the same name
  # Let's find $num-th section with
  # name $name in $file

  foreach my $line (<CF>){

    # outside service section
    if ($is_in == 0){
      print TEMP $line;
      if ($line =~ /^\s*defaults/){
	$num--;
        $is_in = 1 if ($num <= 0);
        next;
      }
    }

    if (($is_in == 1 ) && ($line =~ /^\s*{/)) {
      print TEMP $line;

      # writing down all changable params
      foreach (@params_for_write_defaults){
        next if !exists($data{$id}->{$_}) ||
                !defined($data{$id}->{$_}) ||
                ($data{$id}->{$_} eq "");
        print TEMP "\t$_\t=\t$data{$id}->{$_}\n";
      }
      $is_in = 2;
      next;
    }

    # inside service section
    if ($is_in == 2){

      # deleting old values
      my $del=0;
      foreach (@params_for_write_defaults){
        if ($line =~ /^\s*$_\s*=/){
          $del=1;
          last;
        }
      }
      print TEMP $line unless $del;

      if ($line =~ /^\s*}/) { # end of section
        $is_in = 0;
      }
    }
  }
  close (CF);
  close (TEMP);
  # do not use rename!
  `mv $temp $file`;

}


sub read_all_the_list{
  # reading "default" section
  read_defaults($XINETD_CONF);
  # other enties - from /etc/xinetd.d/*
  open LS, "ls -1 $XINETD_DIR |" or warn "can't execute ls $XINETD_DIR\n";
  foreach my $file (<LS>){
    next if $file !~ /\/?(\S+)/;
    next if $file =~ /\.rpm/;
    read_file("$XINETD_DIR/$1");
  }
  close LS;
  read_file($XINETD_CONF);
}

sub action_constraints{
  write_plain('state (default #f)');
}


sub action_list{
  foreach my $id (sort keys %data){

    my @alist;
    foreach (@params_returned_by_list){
      push @alist, $_;
      push @alist, exists($data{$id}->{$_})? $data{$id}->{$_}:'';
    }
    write_auto_named_list($id, @alist);
  }
}


sub action_read{
  my $params = shift;
  my $id = $params->{name};
  if (exists $data{$id}){

    #re-read data (only one file!)
    if ($id eq "__common_settings__"){ read_defaults($XINETD_CONF);}
    else {read_file($data{$id}->{file});}

    foreach (@params_returned_by_read){
      my $v = exists($data{$id}->{$_})? $data{$id}->{$_}:'';

      if ($_ eq 'name') { $v=$data{$id}->{id}; }
      write_auto_param($_, $v);
    }
  }
}

sub action_write{
  my $params = shift;
  my $id = $params->{name};

  if (exists $data{$id}){

    foreach (@params_for_write){
      $data{$id}->{$_} = exists($params->{$_})? $params->{$_}:'';
    }
    $data{$id}->{state} = $params->{state};

    if ($id eq "__common_settings__"){ write_defaults();}
    else {
      write_section($id);
      set_label($data{$id});
    }
    `service xinetd reload`;
  } else {
    write_error(_("Unknown service"), $id);
  }
}

sub action_type{
  write_string_param("only_from",  "hostname-list");
  write_string_param("bind",       "ipv4-address");
}


my $have_list=0;
sub on_message{
  my $params = shift;

  read_all_the_list() unless $have_list;
  $have_list=1;

  return if (! defined $params->{action});

  if    ($params->{action} eq 'list')  { action_list();}
  elsif ($params->{action} eq 'read')  { action_read($params);}
  elsif ($params->{action} eq 'write') { action_write($params);}
  elsif ($params->{action} eq 'type')  { action_type();}
}

message_loop(\&on_message);
