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

# Common functions for exporting network shares (NFS or SMB).
#
# 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.


$SCRIPTSDIR = "/usr/X11R6/share/setup-tool-backends/scripts";
if ($SCRIPTSDIR =~ /^@scriptsdir[@]/)
{
    $SCRIPTSDIR = ".";
    $DOTIN = ".in";
}

require "$SCRIPTSDIR/general.pl$DOTIN";
require "$SCRIPTSDIR/file.pl$DOTIN";
require "$SCRIPTSDIR/parse.pl$DOTIN";
require "$SCRIPTSDIR/replace.pl$DOTIN";
require "$SCRIPTSDIR/network.pl$DOTIN";


# --- share_export_smb_info; information on a particular SMB export --- #

sub gst_share_smb_info_new
{
  my $info = {};
  return $info;
}

sub gst_share_smb_info_set
{
  my ($info, $key, $value) = @_;
  
  if ($value eq "")
  {
    delete $info->{$key};
  }
  else
  {
    $info->{$key} = $value;
  }
}

sub gst_share_smb_info_get_name
{
  return $_[0]->{'name'};
}

sub gst_share_smb_info_set_name
{
  &gst_share_smb_info_set ($_[0], 'name', $_[1]);
}

sub gst_share_smb_info_get_point
{
  return $_[0]->{'point'};
}

sub gst_share_smb_info_set_point
{
  &gst_share_smb_info_set ($_[0], 'point', $_[1]);
}

sub gst_share_smb_info_get_comment
{
  return $_[0]->{'comment'};
}

sub gst_share_smb_info_set_comment
{
  &gst_share_smb_info_set ($_[0], 'comment', $_[1]);
}

sub gst_share_smb_info_get_enabled
{
  return $_[0]->{'enabled'};
}

sub gst_share_smb_info_set_enabled
{
  &gst_share_smb_info_set ($_[0], 'enabled', $_[1]);
}

sub gst_share_smb_info_get_browse
{
  return $_[0]->{'browse'};
}

sub gst_share_smb_info_set_browse
{
  &gst_share_smb_info_set ($_[0], 'browse', $_[1]);
}

sub gst_share_smb_info_get_public
{
  return $_[0]->{'public'};
}

sub gst_share_smb_info_set_public
{
  &gst_share_smb_info_set ($_[0], 'public', $_[1]);
}

sub gst_share_smb_info_get_write
{
  return $_[0]->{'write'};
}

sub gst_share_smb_info_set_write
{
  &gst_share_smb_info_set ($_[0], 'write', $_[1]);
}

# --- share_smb_table; multiple instances of share_smb_info --- #

sub gst_share_smb_table_new
{
  my @array;
  return \@array;
}

sub gst_share_smb_table_add
{
  my ($table, $info) = @_;
  push @$table, $info;
}

sub gst_share_smb_table_find
{
  my ($table, $name) = @_;

  for $i (@$table)
  {
    if (&gst_share_smb_info_get_name ($i) eq $name)
    {
      return $i;
    }
  }

  return undef;
}

# --- share_export_nfs_info; information on a particular NFS export --- #

sub gst_share_nfs_info_new
{
  my $info = {};
  return $info;
}

sub gst_share_nfs_info_dup
{
  my ($orig) = @_;
  my $dup;

  $dup = { %$orig };
  &gst_share_nfs_info_set_client_table ($dup, &gst_share_nfs_client_table_dup (&gst_share_nfs_info_get_client_table ($orig)));
  return $dup;
}

sub gst_share_nfs_info_match_data
{
  my ($info_a, $info_b) = @_;

  if (&gst_share_nfs_info_get_point ($info_a) eq &gst_share_nfs_info_get_point ($info_b) &&
      &gst_share_nfs_info_print_clients ($info_a) eq &gst_share_nfs_info_print_clients ($info_b))
  {
    return 1;
  }

  return 0;
}

sub gst_share_nfs_info_set
{
  my ($info, $key, $value) = @_;
  
  if ($value eq "")
  {
    delete $info->{$key};
  }
  else
  {
    $info->{$key} = $value;
  }
}

sub gst_share_nfs_info_get_point
{
  return $_[0]->{'point'};
}

sub gst_share_nfs_info_set_point
{
  &gst_share_nfs_info_set ($_[0], 'point', $_[1]);
}

sub gst_share_nfs_info_get_client_table
{
  return $_[0]->{'clients'};
}

sub gst_share_nfs_info_set_client_table
{
  &gst_share_nfs_info_set ($_[0], 'clients', $_[1]);
}

# --- share_nfs_table; multiple instances of share_smb_info --- #

sub gst_share_nfs_table_new
{
  my @array;
  return \@array;
}

sub gst_share_nfs_table_add
{
  my ($table, $info) = @_;
  push @$table, $info;
}

sub gst_share_nfs_table_dup
{
  my ($orig) = @_;
  my $dup = &gst_share_nfs_table_new ();
  my $i;

  foreach $i (@$orig)
  {
    &gst_share_nfs_table_add ($dup, &gst_share_nfs_info_dup ($i));
  }

  return $dup;
}

sub gst_share_nfs_table_find
{
  my ($table, $point) = @_;
  my $i;

  for $i (@$table)
  {
    if (&gst_share_nfs_info_get_point ($i) eq $point)
    {
      return $i;
    }
  }

  return undef;
}

sub gst_share_nfs_table_find_info_equivalent
{
  my ($table, $info) = @_;

  return &gst_share_nfs_table_find ($table, &gst_share_nfs_info_get_point ($info));
}

# --- share_export_nfs_client_info; information on a particular NFS export's client --- #

sub gst_share_nfs_client_info_new
{
  my $info = {};
  return $info;
}

sub gst_share_nfs_client_info_dup
{
  my ($orig) = @_;
  my $dup;

  $dup = { %$orig };
  return $dup;
}

sub gst_share_nfs_client_info_set
{
  my ($info, $key, $value) = @_;
  
  if ($value eq "")
  {
    delete $info->{$key};
  }
  else
  {
    $info->{$key} = $value;
  }
}

sub gst_share_nfs_client_info_get_pattern
{
  return $_[0]->{'pattern'};
}

sub gst_share_nfs_client_info_set_pattern
{
  &gst_share_nfs_client_info_set ($_[0], 'pattern', $_[1]);
}

sub gst_share_nfs_client_info_get_write
{
  return $_[0]->{'write'};
}

sub gst_share_nfs_client_info_set_write
{
  &gst_share_nfs_client_info_set ($_[0], 'write', $_[1]);
}

# --- share_nfs_client_table; multiple instances of share_smb_client_info --- #

sub gst_share_nfs_client_table_new
{
  my @array;
  return \@array;
}

sub gst_share_nfs_client_table_dup
{
  my ($orig) = @_;
  my $dup = &gst_share_nfs_client_table_new ();
  my $i;

  foreach $i (@$orig)
  {
    &gst_share_nfs_client_table_add ($dup, &gst_share_nfs_client_info_dup ($i));
  }

  return $dup;
}

sub gst_share_nfs_client_table_add
{
  my ($table, $info) = @_;
  push @$table, $info;
}

# --- Parsing --- #

sub gst_share_parse_smb_conf
{
  my ($smb_conf_name) = @_;
  my (@sections, $table);

  $table = gst_share_smb_table_new ();

  # Get the sections.

  @sections = &gst_parse_ini_sections ($smb_conf_name);

  for $section (@sections)
  {
    next if ($section =~ /^(global)|(homes)|(printers)$/);
    next if (&gst_parse_ini_bool ($smb_conf_name, $section, "printable"));

    my $sesi = &gst_share_smb_info_new ();
    my $point, $comment, $enabled, $browseable, $public, $writable, $printable;

    $point      = &gst_parse_ini      ($smb_conf_name, $section, "path");
    $comment    = &gst_parse_ini      ($smb_conf_name, $section, "comment");
    $enabled    = &gst_parse_ini_bool ($smb_conf_name, $section, "available");
    $browseable = &gst_parse_ini_bool ($smb_conf_name, $section, "browsable")   ||
                  &gst_parse_ini_bool ($smb_conf_name, $section, "browseable");
    $public     = &gst_parse_ini_bool ($smb_conf_name, $section, "public")      ||
                  &gst_parse_ini_bool ($smb_conf_name, $section, "guest");
    $writable   = &gst_parse_ini_bool ($smb_conf_name, $section, "writable")    ||
                  &gst_parse_ini_bool ($smb_conf_name, $section, "writeable");

    &gst_share_smb_info_set_name    ($sesi, $section);
    &gst_share_smb_info_set_point   ($sesi, $point);
    &gst_share_smb_info_set_comment ($sesi, $comment);
    &gst_share_smb_info_set_enabled ($sesi, $enabled);
    &gst_share_smb_info_set_browse  ($sesi, $browseable);
    &gst_share_smb_info_set_public  ($sesi, $public);
    &gst_share_smb_info_set_write   ($sesi, $writable);

    &gst_share_smb_table_add ($table, $sesi);
  }

  return $table;
}

sub gst_share_parse_nfs_exports
{
  my ($nfs_exports_name) = @_;
  my (@sections, $table, $entries);
  my $point;

  $table = gst_share_nfs_table_new ();

  $entries = &gst_parse_split_hash_with_continuation ($nfs_exports_name, "[ \t]+", "[ \t]+");

  foreach $point (keys %$entries)
  {
    my $clients = $$entries{$point};
    my $info = &gst_share_nfs_info_new ();
    my $client_table = &gst_share_nfs_client_table_new ();

    &gst_share_nfs_info_set_point ($info, $point);

    foreach $client (@$clients)
    {
      my $cinfo = &gst_share_nfs_client_info_new ();
      my $pattern;

      $client =~ /^([a-zA-Z0-9.-_*?@\/]+)/;
      $pattern = $1;
      $pattern = "0.0.0.0/0" if $pattern eq "";
      &gst_share_nfs_client_info_set_pattern ($cinfo, $pattern);

      my $option_str = "";
      my @options;

      if ($client =~ /\((.+)\)/) { $option_str = $1; }
      @options = ($option_str =~ /([a-zA-Z0-9_=-]+),?/mg);

      for $option (@options)
      {
        if ($option eq "rw") { &gst_share_nfs_client_info_set_write ($cinfo, 1); }
        # Add supported NFS export options here. Some might have to be split on '='.
      }

      &gst_share_nfs_client_table_add ($client_table, $cinfo);
    }

    &gst_share_nfs_info_set_client_table ($info, $client_table);
    &gst_share_nfs_table_add ($table, $info);
  }

  return $table;
}

# --- Replacing --- #

sub gst_share_replace_smb_conf
{
  my ($file, $table) = @_;
  my (@sections);

  # Get the sections.

  @sections = &gst_parse_ini_sections ($file);

  for $section (@sections)
  {
    next if ($section =~ /^(global)|(homes)|(printers)$/);
    next if (&gst_parse_ini_bool ($file, $section, "printable"));

    if (!&gst_share_smb_table_find ($table, $section))
    {
      &gst_replace_remove_ini_section ($file, $section);
    }
  }

  for $export (@$table)
  {
    my $point, $comment, $enabled, $browseable, $public, $writable, $printable;
    my $name = &gst_share_smb_info_get_name ($export);

    &gst_replace_ini      ($file, $name, "path",       &gst_share_smb_info_get_point      ($export));
    &gst_replace_ini      ($file, $name, "comment",    &gst_share_smb_info_get_comment    ($export));
    &gst_replace_ini_bool ($file, $name, "available",  &gst_share_smb_info_get_enabled    ($export));
    &gst_replace_ini_bool ($file, $name, "browseable", &gst_share_smb_info_get_browse     ($export));
    &gst_replace_ini_bool ($file, $name, "public",     &gst_share_smb_info_get_public     ($export));
    &gst_replace_ini_bool ($file, $name, "writable",   &gst_share_smb_info_get_write      ($export));

    &gst_replace_remove_ini_var ($file, $name, "browsable");
    &gst_replace_remove_ini_var ($file, $name, "guest");
    &gst_replace_remove_ini_var ($file, $name, "writeable");
  }
}

sub gst_share_nfs_exports_get_next_entry_line  # $infd, $outfd
{
  my ($infd, $outfd) = @_;

  while (<$infd>)
  {
    # Each line is in the following format:
    # <point> <clients>
    my @line = split /[ \t]+/, $_;
    if ($line[0] eq "") { shift @line; }
    if (@line < 1 || &gst_ignore_line (@line)) { print $outfd $_; next; }

    return $_;
  }

  return undef;
}

sub gst_share_nfs_exports_get_entry_line_fields  # line
{
  my ($line) = @_;

  # Remove leading spaces.
  $line =~ s/^[ \t]*//;

  # Remove trailing spaces and comments.
  $line =~ s/[ \t]*\#.*//;

  return split /[ \t]+/, $line;
}

sub gst_share_nfs_info_print_clients
{
  my ($info) = @_;
  my $line = "";
  my $clients, $client;

  $clients = &gst_share_nfs_info_get_client_table ($info);

  for $client (@$clients)
  {
    $line .= &gst_share_nfs_client_info_get_pattern ($client);
    $line .= "(rw)" if (&gst_share_nfs_client_info_get_write ($client));
    $line .= " ";
  }

  return $line;
}

sub gst_share_nfs_info_print_entry
{
  my ($info) = @_;
  my $line;

  # <point>

  $line = sprintf ("%-15s ", &gst_share_nfs_info_get_point ($info));

  # <clients>

  $line .= &gst_share_nfs_info_print_clients ($info);

  return $line;
}

sub gst_share_nfs_client_print_option_hash
{
  my ($opthash) = @_;
  my $string = "";

  for $key (keys %$opthash)
  {
    my $value = $$opthash{$key};

    $string .= "," if $string ne "";
    $string .= $key;

    if ($value eq " ")
    {
      $string .= "=";
    }
    elsif ($value ne "")
    {
      $string .= "=" . $value;
    }
  }

  return $string;
}

sub gst_share_nfs_info_print_synthesized_entry
{
  my ($info, $line) = @_;
  my $outline;
  my $ctable = &gst_share_nfs_info_get_client_table ($info);

  # <point>

  $outline = sprintf ("%-15s", &gst_share_nfs_info_get_point ($info));

  # <clients>

  chomp $line;
  my @clients = split /[ \t]+/, $line;
  shift @clients;

  # Make client hash based on line.

  my $chash = { };

  for $client (@clients)
  {
    my $opthash = { };

    $client =~ /^([a-zA-Z0-9.-_*?@\/]+)/;
    my $pattern = $1;
    $$chash{$pattern} = $opthash;

    my $option_str = "";
    if ($client =~ /\((.+)\)/) { $option_str = $1; }
    @options = ($option_str =~ /([a-zA-Z0-9_=-]+),?/mg);

    for $option (@options)
    {
      my ($key, $value) = split /[=]/, $option;
      next if ($key eq "");

      if ($value eq "" && $option =~ /=/) { $value = " "; }
      $$opthash{$key} = $value;
    }
  }

  # @clients contains client(options) entries.

  for $cinfo (@$ctable)
  {
    my $pattern = &gst_share_nfs_client_info_get_pattern ($cinfo);
    my $opthash = $$chash{$pattern};

    if (&gst_share_nfs_client_info_get_write ($cinfo))
    {
      $$opthash{'rw'} = "";
    }
    else
    {
      delete $$opthash{'rw'};
    }

    $outline .= " " . &gst_share_nfs_client_info_get_pattern ($cinfo);
    my $client_string = &gst_share_nfs_client_print_option_hash ($opthash);
    if ($client_string ne "")
    {
      $outline .= "(" . $client_string . ")";
    }
  }

  return $outline;
}

sub gst_share_nfs_exports_add_entry
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while (<$infd>) { print $outfd $_; }
  &gst_file_close ($infd);

  print $outfd &gst_share_nfs_info_print_entry ($info) . "\n";
  &gst_file_close ($outfd);
}

sub gst_share_nfs_exports_update_entry  # filename, filesys_info
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);
  my $replaced = 0;

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while ($line = &gst_share_nfs_exports_get_next_entry_line ($infd, $outfd))
  {
    my $point = &gst_share_nfs_exports_get_entry_line_point ($line);

    if (!$replaced && $point eq &gst_share_nfs_info_get_point ($info))
    {
      print $outfd &gst_share_nfs_info_print_synthesized_entry ($info, $line) . "\n";
      $replaced = 1;
    }
    else
    {
      print $outfd $line;
    }
  }

  &gst_file_close ($infd);
  &gst_file_close ($outfd);
}

sub gst_share_nfs_exports_remove_entry
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while ($line = &gst_share_nfs_exports_get_next_entry_line ($infd, $outfd))
  {
    my $point = &gst_share_nfs_exports_get_entry_line_point ($line);

    if ($point ne &gst_share_nfs_info_get_point ($info))
    {
      print $outfd $line;
    }
  }

  &gst_file_close ($infd);
  &gst_file_close ($outfd);
}

sub gst_share_nfs_exports_get_entry_line_point
{
  my ($line) = @_;
  my $point;

  ($point) = split /[ \t]+/, $line;
  return $point;
}

sub gst_share_replace_nfs_exports  # filename, table
{
  my ($file, $table) = @_;
  my ($new_table, $old_table);

  $old_table = &gst_share_parse_nfs_exports ($file);
  $new_table = &gst_share_nfs_table_dup ($table);

  for $info (@$new_table)
  {
    my $old_info = &gst_share_nfs_table_find_info_equivalent ($old_table, $info);

#    print "Looking for entry - ";
    if (!$old_info)
    {
#      print "adding.\n";
      &gst_share_nfs_exports_add_entry ($file, $info);
    }
    elsif ($old_info && !&gst_share_nfs_info_match_data ($old_info, $info))
    {
#      print "updating.\n";
      &gst_share_nfs_exports_update_entry ($file, $info);
    }
    else
    {
#      print "found.\n";
    }
  }

  for $old_info (@$old_table)
  {
#    print "Looking for entry - ";
    if (!&gst_share_nfs_table_find_info_equivalent ($new_table, $old_info))
    {
#      print "removing.\n";
      &gst_share_nfs_exports_remove_entry ($file, $old_info);
    }
    else
    {
#      print "found.\n";
    }
  } 
}


sub gst_share_get_smbclient_list_cmd
{
  my ($host) = @_;
  my ($tool_smbclient);

  $tool_smbclient = &gst_file_locate_tool ("smbclient");

  return "$tool_smbclient -N -U % -L -I $host";
}


sub gst_share_scan_smb_host_call
{
  my ($host) = @_;
  my ($cmd, $dirs);
  local *SMBCLIENT;

  $cmd = &gst_share_get_smbclient_list_cmd ($host);
  open (SMBCLIENT, "/bin/sh -c \"$cmd\" 2> /dev/null |");

  while (<SMBCLIENT>)
  {
    last if /^[ \t]+Sharename[ \t]+Type[ \t]+Comment/;
  }
  
  while (<SMBCLIENT>)
  {
    chomp;
    last if /^$/;
    # Looking for sth like " 	fredsdir       Disk      Fred's Service"
    if (/^[ \t]*([^ \t]+)[ \t]+Disk/)
    {
      $dirs .= ":" . $1;
    }
  }
  close SMBCLIENT;

  return "$host:$dirs";
}


sub gst_share_scan_smb_start
{
  my (@hosts) = @_;
  my ($host, @procs);

  foreach $host (@hosts)
  {
    push @procs, &gst_process_fork (\&gst_share_scan_smb_host_call, $host);
  }

  return \@procs;
}


sub gst_share_scan_nfs_host_call
{
  my ($host) = @_;
  my ($cmd, $dirs);
  local *SHOWMOUNT;

  $cmd = &gst_share_get_showmount_cmd ($host);
  open (SHOWMOUNT, "/bin/sh -c \"$cmd\" 2> /dev/null |");
  while (<SHOWMOUNT>)
  {
    chomp;
    $dirs .= ":$_";
  }
  close SHOWMOUNT;

  return "$host$dirs";
}


sub gst_share_scan_nfs_start
{
  my (@hosts) = @_;
  my ($host, @procs);

  foreach $host (@hosts)
  {
    push @procs, &gst_process_fork (\&gst_share_scan_nfs_host_call, $host);
  }

  return \@procs;
}


sub gst_share_scan_share_collect
{
  my ($procs, $host2name) = @_;
  my ($proc, $str, %hash, $host, @dirs);

  foreach $proc (@$procs)
  {
    if ($$proc{"ready"})
    {
      sysread ($$proc{"fd"}, $str, 4096);
      @dirs = split (':', $str);
      $host = shift @dirs;
      $hash{$$host2name{$host}} = [ @dirs ];
    }
    &gst_process_kill ($proc);
  }

  return \%hash;
}


sub gst_share_scan_network
{
  my ($host, @hosts);
  my ($proc, @procs, $lookup_procs, $nfs_procs, $smb_procs);
  my ($i, @names, %host2name, $nfs_table, $smb_table);

  @hosts = &gst_network_find_hosts ();
  
  $lookup_procs = &gst_network_lookup_address_start (@hosts);
  $nfs_procs = &gst_share_scan_nfs_start (@hosts);
  $smb_procs = &gst_share_scan_smb_start (@hosts);

  &gst_process_list_check_ready (10, [@$lookup_procs, @$nfs_procs, @$smb_procs]);

  @names = &gst_network_lookup_address_collect ($lookup_procs);
  # http://tlc.perlarchive.com/articles/perl/ug0001.shtml
  @host2name{@hosts} = @names;

  return &gst_share_scan_collect ([@$nfs_procs, @$smb_procs], \%host2name);
}

1;
