#!/usr/bin/env perl
#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-

# Shares configurator. Designed to be architecture and distribution independent.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Hans Petter Jansson <hpj@ximian.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library 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.

# Best viewed with 100 columns of width.

# Configuration files affected:
#
# /etc/resolv.conf
# /etc/host.conf
# /etc/hosts
# /etc/sysconfig/network
# /etc/rc.config
# /etc/smb.conf

# Running programs affected:
#
# smbd
# nmbd
# ifconfig: check current interfaces and activate/deactivate.


BEGIN {
  $SCRIPTSDIR = "/usr/share/setup-tool-backends/scripts";
  if ($SCRIPTSDIR =~ /^___scriptsdir__[_]/)
  {
      $SCRIPTSDIR = ".";
      $DOTIN = ".in";
  }
  
  require "$SCRIPTSDIR/general.pl$DOTIN";
  require "$SCRIPTSDIR/platform.pl$DOTIN";
  require "$SCRIPTSDIR/util.pl$DOTIN";
  require "$SCRIPTSDIR/file.pl$DOTIN";
  require "$SCRIPTSDIR/xml.pl$DOTIN";
  require "$SCRIPTSDIR/filesys.pl$DOTIN";
  require "$SCRIPTSDIR/network.pl$DOTIN";
  require "$SCRIPTSDIR/share.pl$DOTIN";
}


# --- Tool information --- #

$name = "shares";
$version = "0.11.0";
@platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0", "redhat-7.1",
              "redhat-7.2",

              "mandrake-7.2",

              "debian-2.2", "debian-woody",

              "suse-7.0", "turbolinux-7.0");

#              "freebsd-4", "freebsd-5");

$description =<<"end_of_description;";
       Configures network shares for import or export.
end_of_description;

# --- Platform handling --- #

$platmap =
{
  "redhat-5.2"   => "redhat-6.2",
  "redhat-6.0"   => "redhat-6.2",
  "redhat-6.1"   => "redhat-6.2",
  "redhat-6.2"   => "redhat-6.2",
  "redhat-7.0"   => "redhat-7.0",
  "redhat-7.1"   => "redhat-7.0",
  "redhat-7.2"   => "redhat-7.0",
  "debian-2.2"   => "redhat-7.0",
  "debian-woody" => "redhat-7.0",
  "mandrake-7.2" => "redhat-7.0",
  "suse-7.0"     => "redhat-6.2",
  "freebsd-4"    => "freebsd-4",
  "freebsd-5"    => "freebsd-4",
  "turbolinux-7.0"   => "redhat-7.0"
};

$filemap =
{
  "redhat-6.2" =>
  {
    "fstab"    => "/etc/fstab",
    "mtab"     => "/etc/mtab",
    "exports"  => "/etc/exports",
    "smb.conf" => "/etc/smb.conf"
  },

  "redhat-7.0" =>
  {
    "fstab"    => "/etc/fstab",
    "mtab"     => "/etc/mtab",
    "exports"  => "/etc/exports",
    "smb.conf" => "/etc/samba/smb.conf"
  },

  "freebsd-4" =>
  {
    "fstab"    => "/etc/fstab",
    "exports"  => "/etc/exports",
    "smb.conf" => "/usr/local/etc/smb.conf"
  }
};

sub distro_file
{
  my ($file) = @_;
  return $$filemap{$$platmap{$xst_dist}}->{$file};
}

# --- XML parsing ---

# Scan XML from standard input to an internal tree.

sub xml_parse
{
  my $tree;
  my $config = {};
  # Scan XML to tree.

  $tree = &xst_xml_scan;

  # Walk the tree recursively and extract configuration parameters.
  # This is the top level - find and enter the "network" tag.

  while (@$tree)
  {
    if ($$tree[0] eq "shares") { &xml_parse_shares ($$tree[1], $config); }

    shift @$tree;
    shift @$tree;
  }

  return($config);
}

# <shares>...</shares>

sub xml_parse_shares
{
  my ($tree, $config) = @_;

  shift @$tree;  # Skip attributes.

  while (@$tree)
  {
    if    ($$tree[0] eq "imports") { &xml_parse_imports ($$tree[1], $config); }
    elsif ($$tree[0] eq "exports") { &xml_parse_exports ($$tree[1], $config); }
    shift @$tree;
    shift @$tree;
  }
}

# <imports>...</imports>

sub xml_parse_imports
{
  my ($tree, $config) = @_;
  my $table;

  $table = &xst_filesys_table_new ();

  shift @$tree;  # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "import") { &xml_parse_import ($$tree[1], $table); }

    shift @$tree;
    shift @$tree;
  }

  $$config{'imports'} = $table;
}

# <exports>...</exports>

sub xml_parse_exports
{
  my ($tree, $config) = @_;
  my ($smb_table, $nfs_table);

  $smb_table = &xst_share_smb_table_new ();
  $nfs_table = &xst_share_nfs_table_new ();

  shift @$tree;  # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "export") { &xml_parse_export ($$tree[1], $smb_table, $nfs_table); }

    shift @$tree;
    shift @$tree;
  }

  $$config{'smb_exports'} = $smb_table;
  $$config{'nfs_exports'} = $nfs_table;
}

# <import>...</import>

sub xml_parse_import
{
  my ($tree, $table) = @_;
  my $type = "", $host = "", $path = "", $comment = "", $user = "",
     $password = "", $mounted = 0, $detected = 0, $point = "", $listed = 0;

  my $info = &xst_filesys_info_new ();

  $type = $$tree[0]->{type};
  shift @$tree;

  while (@$tree)
  {
    if    ($$tree[0] eq "host")     { $host     = &xst_xml_get_word  ($$tree[1]); }
    elsif ($$tree[0] eq "path")     { $path     = &xst_xml_get_word  ($$tree[1]); }
    elsif ($$tree[0] eq "user")     { $user     = &xst_xml_get_text  ($$tree[1]); }
    elsif ($$tree[0] eq "password") { $password = &xst_xml_get_text  ($$tree[1]); }
    elsif ($$tree[0] eq "comment")  { $comment  = &xst_xml_get_text  ($$tree[1]); }
    elsif ($$tree[0] eq "point")    { $point    = &xst_xml_get_word  ($$tree[1]); }
    elsif ($$tree[0] eq "mounted")  { $mounted  = &xst_xml_get_state ($$tree[1]); }
    elsif ($$tree[0] eq "detected") { $detected = &xst_xml_get_state ($$tree[1]); }
    elsif ($$tree[0] eq "listed")   { $listed   = &xst_xml_get_state ($$tree[1]); }

    shift @$tree;
    shift @$tree;
  }

  &xst_filesys_info_set_network_host ($info, $host);
  &xst_filesys_info_set_network_path ($info, $path);
#  &xst_filesys_info_set_comment      ($info, $comment);
  &xst_filesys_info_set_permanent    ($info, $listed);
  &xst_filesys_info_set_mounted      ($info, $mounted);
  &xst_filesys_info_set_detected     ($info, $detected);
  &xst_filesys_info_set_point        ($info, $point);

  if ($type eq "nfs")
  {
    &xst_filesys_info_set_fs         ($info, "nfs");
    &xst_filesys_info_set_option     ($info, "soft", "");
    &xst_filesys_info_remove_option  ($info, "hard");
  }
  else
  {
    &xst_filesys_info_set_fs         ($info, "smbfs");
    &xst_filesys_info_set_option     ($info, "username", ($user eq "")     ? " " : $user);
    &xst_filesys_info_set_option     ($info, "password", ($password eq "") ? " " : $password);
  }

  &xst_filesys_table_add ($table, $info);
}

# <export>...</export>

sub xml_parse_export
{
  my ($tree, $smb_table, $nfs_table) = @_;
  my $type = "";
  my $info;

  $type = $$tree[0]->{type};
  shift @$tree;
  
  if ($type eq "nfs")
  {
    my $path = "";
    my $client_table = &xst_share_nfs_client_table_new ();

    $info = xst_share_nfs_info_new ();

    while (@$tree)
    {
      if    ($$tree[0] eq "path")       { $path = &xst_xml_get_word ($$tree[1]);       }
      elsif ($$tree[0] eq "allow")      { &xml_parse_allow ($$tree[1], $client_table); }

      shift @$tree;
      shift @$tree;
    }
    
    if ($path ne "")
    {
      &xst_share_nfs_info_set_point        ($info, $path);
      &xst_share_nfs_info_set_client_table ($info, $client_table);
      &xst_share_nfs_table_add             ($nfs_table, $info);
    }
  }
  elsif ($type eq "smb")
  {
    my $name = "", $path = "", $comment = "", $enabled = 0, $browseable = 0,
       $public = 0, $writeable = 0;

    $info = xst_share_smb_info_new ();

    while (@$tree)
    {
      if    ($$tree[0] eq "name")       { $name       = &xst_xml_get_word  ($$tree[1]); }
      elsif ($$tree[0] eq "path")       { $path       = &xst_xml_get_word  ($$tree[1]); }
      elsif ($$tree[0] eq "comment")    { $comment    = &xst_xml_get_text  ($$tree[1]); }
      elsif ($$tree[0] eq "enabled")    { $enabled    = &xst_xml_get_state ($$tree[1]); }
      elsif ($$tree[0] eq "browse")     { $browseable = &xst_xml_get_state ($$tree[1]); }
      elsif ($$tree[0] eq "public")     { $public     = &xst_xml_get_state ($$tree[1]); }
      elsif ($$tree[0] eq "write")      { $writeable  = &xst_xml_get_state ($$tree[1]); }

      shift @$tree;
      shift @$tree;
    }

    if ($path ne "")
    {
      &xst_share_smb_info_set_name    ($info, $name);
      &xst_share_smb_info_set_point   ($info, $path);
      &xst_share_smb_info_set_comment ($info, $comment);
      &xst_share_smb_info_set_enabled ($info, $enabled);
      &xst_share_smb_info_set_browse  ($info, $browseable);
      &xst_share_smb_info_set_public  ($info, $public);
      &xst_share_smb_info_set_write   ($info, $writeable);
      &xst_share_smb_table_add        ($smb_table, $info);
    }
  }
  else
  {
    # Unsupported share type.
    
    return;
  }
}

sub xml_parse_allow
{
  my ($tree, $client_table) = @_;
  my $pattern = "";
  my $write = 0;

  shift @$tree;  # No attributes.

  while (@$tree)
  {
    if    ($$tree[0] eq "pattern") { $pattern = &xst_xml_get_word  ($$tree[1]); }
    elsif ($$tree[0] eq "write")   { $write   = &xst_xml_get_state ($$tree[1]); }

    shift @$tree;
    shift @$tree;
  }

  my $info = &xst_share_nfs_client_info_new ();
  &xst_share_nfs_client_info_set_pattern ($info, $pattern);
  &xst_share_nfs_client_info_set_write   ($info, $write);
  &xst_share_nfs_client_table_add        ($client_table, $info);
}

# --- XML printing --- #

sub xml_print_import_common  # filesys_info
{
  my ($fsinfo) = @_;
  my ($host, $path, $point);

  $point = &xst_filesys_info_get_point        ($fsinfo);
  $host  = &xst_filesys_info_get_network_host ($fsinfo);
  $path  = &xst_filesys_info_get_network_path ($fsinfo);

  &xst_xml_print_pcdata    ("host",     $host);
  &xst_xml_print_pcdata    ("path",     $path);
  &xst_xml_print_pcdata    ("point",    $point);
  &xst_xml_print_state_tag ("listed",   &xst_filesys_info_get_permanent ($fsinfo));
  &xst_xml_print_state_tag ("mounted",  &xst_filesys_info_get_mounted   ($fsinfo));
  &xst_xml_print_state_tag ("detected", &xst_filesys_info_get_detected  ($fsinfo));
}

sub xml_print_import_nfs
{
  my ($fsinfo) = @_;

  &xst_xml_print_vspace ();
  &xst_xml_print_line ("<import type='nfs'>");
  &xst_xml_enter ();

  &xml_print_import_common ($fsinfo);

  &xst_xml_leave ();
  &xst_xml_print_line ("</import>");
  &xst_xml_print_vspace ();
}

sub xml_print_import_smb
{
  my ($fsinfo) = @_;
  my ($user, $password);

  $user     = &xst_filesys_info_get_option ($fsinfo, "username");
  $user     = "" if ($user =~ /^ +$/);
  $password = &xst_filesys_info_get_option ($fsinfo, "password");
  $password = "" if ($password =~ /^ +$/);

  &xst_xml_print_vspace ();
  &xst_xml_print_line ("<import type='smb'>");
  &xst_xml_enter ();

  &xml_print_import_common ($fsinfo);
  if ($user ne "")     { &xst_xml_print_pcdata ("user",     $user); }
  if ($password ne "") { &xst_xml_print_pcdata ("password", $password); }

  &xst_xml_leave ();
  &xst_xml_print_line ("</import>");
  &xst_xml_print_vspace ();
}

sub xml_print_export_nfs
{
  my ($info) = @_;
  my $client_table;

  $client_table = &xst_share_nfs_info_get_client_table ($info);

  &xst_xml_print_vspace ();
  &xst_xml_print_line ("<export type='nfs'>");
  &xst_xml_enter ();

  &xst_xml_print_pcdata ("name", "unknown");
  &xst_xml_print_pcdata ("path", &xst_share_nfs_info_get_point ($info));

  for $client (@$client_table)
  {
    &xst_xml_container_enter ("allow");
    &xst_xml_print_pcdata    ("pattern", &xst_share_nfs_client_info_get_pattern ($client));
    &xst_xml_print_state_tag ("write",   &xst_share_nfs_client_info_get_write   ($client));
    &xst_xml_container_leave ();
  }

  &xst_xml_leave ();
  &xst_xml_print_line ("</export>");
  &xst_xml_print_vspace ();
}

sub xml_print_export_smb
{
  my ($info) = @_;

  &xst_xml_print_vspace ();
  &xst_xml_print_line ("<export type='smb'>");
  &xst_xml_enter ();

  &xst_xml_print_pcdata    ("name",       &xst_share_smb_info_get_name    ($info));
  &xst_xml_print_pcdata    ("path",       &xst_share_smb_info_get_point   ($info));
  &xst_xml_print_pcdata    ("comment",    &xst_share_smb_info_get_comment ($info));
  &xst_xml_print_state_tag ("enabled",    &xst_share_smb_info_get_enabled ($info));
  &xst_xml_print_state_tag ("browse",     &xst_share_smb_info_get_browse  ($info));
  &xst_xml_print_state_tag ("public",     &xst_share_smb_info_get_public  ($info));
  &xst_xml_print_state_tag ("write",      &xst_share_smb_info_get_write   ($info));

  &xst_xml_leave ();
  &xst_xml_print_line ("</export>");
  &xst_xml_print_vspace ();
}

sub xml_print
{
  my ($config) = @_;
  my ($import_table, $smb_export_table, $nfs_export_table);

  $import_table     = $$config{"imports"};
  $smb_export_table = $$config{"smb_exports"};
  $nfs_export_table = $$config{"nfs_exports"};

  &xst_xml_print_begin ();

  if (scalar @$import_table)
  {
    &xst_xml_container_enter ("imports");

    foreach $i (@$import_table)
    {
      my ($fs);
      
      $fs = &xst_filesys_info_get_fs ($i);
      
      if    ($fs eq "nfs")   { &xml_print_import_nfs ($i); }
      elsif ($fs eq "smbfs") { &xml_print_import_smb ($i); }
      else                   { next; }
    }
    
    &xst_xml_container_leave ();
    &xst_xml_print_vspace ();
  }

  if (scalar @$smb_export_table || scalar @$nfs_export_table)
  {
    &xst_xml_container_enter ("exports");
    
    foreach $i (@$smb_export_table)
    {
      &xml_print_export_smb ($i);
    }

    foreach $i (@$nfs_export_table)
    {
      &xml_print_export_nfs ($i);
    }

    &xst_xml_container_leave ();
  }

  &xst_xml_print_end ();
}

# Misc

sub remove_shares_from_filesys_table
{
  my ($table) = @_;
  my @ltable = @$table;  # We need a shallow copy.

  for $info (@ltable)
  {
    my $fs = &xst_filesys_info_get_fs ($info);

    if ($fs eq "smbfs" || $fs eq "nfs")
    {
      &xst_filesys_table_remove ($table, $info);
    }
  }
}

# Configuration handling.

sub get_configured_imports
{
  my ($imports, $listed_imports, $mounted_imports);

  $listed_imports  = &xst_filesys_fstab_parse (&distro_file ("fstab"));
  $mounted_imports = &xst_filesys_mtab_parse  (&distro_file ("mtab"));

  &xst_filesys_table_set_permanent_true ($listed_imports);
  &xst_filesys_table_set_mounted_true   ($mounted_imports);

  $imports = &xst_filesys_table_merge_superset ($mounted_imports, $listed_imports);
  return $imports;
}

sub get_configured_exports
{
  my ($smb_exports, $nfs_exports);

  $smb_exports = &xst_share_parse_smb_conf    (&distro_file ("smb.conf"));
  $nfs_exports = &xst_share_parse_nfs_exports (&distro_file ("exports"));

  return ($smb_exports, $nfs_exports);
}

# Top-level actions.

sub get
{
  my ($imports, $smb_exports, $nfs_exports);
  my $config = {};

  $imports = &get_configured_imports ();
  ($smb_exports, $nfs_exports) = &get_configured_exports ();

  $$config{"imports"}     = $imports;
  $$config{"smb_exports"} = $smb_exports;
  $$config{"nfs_exports"} = $nfs_exports;

  &xst_report_end ();
  &xml_print ($config);
}

sub set
{
  my $config;
  my ($fs, $fs_no_shares, $fs_new_shares);
  my $imports;

  $config = &xml_parse ();

  $imports = $$config{"imports"};
  for $import (@$imports)
  {
    &xst_filesys_info_settings_to_options ($import);
  }

  $fs = &xst_filesys_fstab_parse (&distro_file ("fstab"));
  &xst_filesys_table_set_permanent_true ($fs);

  $fs_no_shares = &xst_filesys_table_dup ($fs);
  &remove_shares_from_filesys_table ($fs_no_shares);

  $fs_new_shares = &xst_filesys_table_merge_superset ($$config{"imports"}, $fs_no_shares);
  $fs_new_shares = &xst_filesys_table_merge_subset ($fs_new_shares, $fs);

  &xst_filesys_fstab_replace     (&distro_file ("fstab"),    $fs_new_shares);
  &xst_share_replace_smb_conf    (&distro_file ("smb.conf"), $$config{"smb_exports"});
  &xst_share_replace_nfs_exports (&distro_file ("exports"),  $$config{"nfs_exports"});

  &xst_filesys_mount_sync_all (&distro_file ("fstab"), &distro_file ("mtab"), $$config{"imports"});

  &xst_report_end ();
}

sub filter
{
  my $config;

  $config = &xml_parse ();
  &xst_report_end ();
  &xml_print ($config);
}

sub scan
{
  my %config;

  $config{"imports"} = &xst_share_scan_network_imports ();
  &xst_report_end ();
  &xml_print (\%config);
}

# --- Main --- #

# get, set and filter are special cases that don't need more parameters than a ref to their function.
# Read general.pl.in:xst_run_directive to know about the format of this hash.

$directives = {
  "get"    =>       [ \&get,    [], "" ],
  "set"    =>       [ \&set,    [], "" ],
  "filter" =>       [ \&filter, [], "" ],
  "scan_network" => [ \&scan,   [], "Looks for smb or nfs shares in the network." ]
    };

$tool = &xst_init ($name, $version, $description, $directives, @ARGV);
&xst_platform_ensure_supported ($tool, @platforms);
&xst_run ($tool);
