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

# Network configurator. Designed to be architecture and distribution independent.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Hans Petter Jansson <hpj@ximian.com>
#          Michael Vogt <mvo@debian.org> (Debian Support)
#          Arturo Espinosa <arturo@ximian.com>
#          Grzegorz Golawski <grzegol@pld-linux.org> (PLD Support)
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 = "@scriptsdir@";
  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/network.pl$DOTIN";
}

# --- Tool information --- #

$name = "network";
$version = "@VERSION@";
@platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0", "redhat-7.1",
              "redhat-7.2", "redhat-8.0", "redhat-9",
              "rhel-5", "rhel-6", "rhel-7",
              "openna-1.0",
              "mandrake-7.1", "mandrake-7.2", "mandrake-9.0", "mandrake-9.1", "mandrake-9.2",
              "mandrake-10.0", "mandrake-10.1","mandrake-10.2", "mandriva-2006.0",
              "mandriva-2006.1", "mandriva-2007.0", "mandriva-2007.1",
              "yoper-2.2",
              "blackpanther-4.0",
              "debian-2.2", "debian-3.0", "debian-3.1", "debian-4.0", "debian-5.0", "debian-6.0", "debian-7.0", "debian-8.0", "debian-testing",
              "ubuntu-5.04", "ubuntu-5.10", "ubuntu-6.06", "ubuntu-6.10", "ubuntu-7.04", "ubuntu-7.10", "ubuntu-8.04", "ubuntu-8.10", "ubuntu-9.04", "ubuntu-9.10", "ubuntu-10.04", "ubuntu-10.10", "ubuntu-11.04", "ubuntu-11.10", "ubuntu-12.04", "ubuntu-12.10", "ubuntu-13.04", "ubuntu-13.10", "ubuntu-14.04", "ubuntu-14.10", "ubuntu-15.04",
              "suse-7.0", "suse-9.0", "suse-9.1", "suse-10.0", "suse-10.1", "suse-10.2", "suse-10.3", "suse-10.4", "suse-11.0", "suse-11.1", "suse-11.2", "suse-11.3",
              "turbolinux-7.0", "fedora-1", "fedora-2", "fedora-3", "fedora-4", "fedora-5", "rpath", "fedora-15", "fedora-16", "fedora-17", "fedora-18", "fedora-19", "fedora-20", "fedora-21",
              "pld-1.0", "pld-1.1", "pld-1.99", "pld-2.0", "pld-3.0",
              "conectiva-9", "conectiva-10",
              "vine-3.0", "vine-3.1",
              "ark",
              "slackware-9.1.0", "slackware-10.0.0", "slackware-10.1.0", "slackware-10.2.0",
              "slackware-11.0.0", "slackware-12.0.0", "slackware-12.1.0", "slackware-12.2.0", "slackware-13.0.0", "slackware-13.1.0", "slackware-13.37.0", "slackware-14.0.0",
              "slackware-14.1.0", "gentoo", "vlos-1.2",
              "freebsd-5", "freebsd-6", "freebsd-7", "freebsd-8", "freebsd-9", "freebsd-10");

$description =<<"end_of_description;";
       Configures all network parameters and interfaces.
end_of_description;

$progress_max = 10;

$profile_file = "profiles.xml";


# --- XML parsing ---

# Scan XML from standard input to an internal tree.
sub xml_parse
{
  my ($tree, %hash, $elem);
  # Scan XML to tree.

  $tree = &gst_xml_scan ();

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

  while ($elem = shift @$tree)
  {
    if ($elem eq "network") { &xml_parse_network (shift @$tree, \%hash); }
    else { &gst_report ("xml_unexp_tag", $elem); shift @$tree; }
  }

  return(\%hash);
}

# <network>...</network>

sub push_unique
{
  my ($arr, $val) = @_;
  my $i;

  foreach $i (@$arr)
  {
    return if $i eq $val;
  }

  push @$arr, $val;
}

sub xml_parse_network
{
  my ($tree, $hash) = @_;
  my ($elem);
  my (@searchdomain, @nameserver, @order, %statichost, %interface, %dialing);

  shift @$tree;  # Skip attributes.

  while ($elem = shift @$tree)
  {
    if    ($elem eq "auto")          { $$hash{"auto"} =        &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "hostname")      { $$hash{"hostname"} =    &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "gateway")       { $$hash{"gateway"} =     &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "gatewaydev")    { $$hash{"gatewaydev"} =  &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "domain")        { $$hash{"domain"} =      &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "workgroup")     { $$hash{"workgroup"} =   &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "smbdesc")       { $$hash{"smbdesc"} =     &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "name")          { $$hash{"name"} =        &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "description")   { $$hash{"description"} = &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "winsserver")    { $$hash{"winsserver"} =  &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "winsuse")       { $$hash{"winsuse"} =     &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "smbuse")        { $$hash{"smbuse"} =      &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "hostmatch")     { $$hash{"hostmatch"} =   &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "nameserver")    { &push_unique (\@nameserver, &gst_xml_get_pcdata (shift @$tree)); }
    elsif ($elem eq "searchdomain")  { &push_unique (\@searchdomain, &gst_xml_get_pcdata (shift @$tree)); }
    elsif ($elem eq "order")         { push (@order, &gst_xml_get_pcdata (shift @$tree)); }
    elsif ($elem eq "statichost")    { &xml_parse_statichost (shift @$tree, \%statichost); }
    elsif ($elem eq "interface")     { &gst_network_xml_parse_interface (shift @$tree, \%interface); }
    elsif ($elem eq "dialing")       { &xml_parse_dialing (shift @$tree, \%dialing); }
    elsif ($elem eq "dialinstalled") { shift @$tree; }
    elsif ($elem eq "smbinstalled")  { shift @$tree; }
    elsif ($elem eq "smartdhcpcd")   { shift @$tree; }
    elsif ($elem eq "gwdevunsup")    { shift @$tree; }
    elsif ($elem eq "wireless_device") { shift @$tree; }
    elsif ($elem eq "profiledb")     { &xml_parse_profiledb (shift @$tree, $hash); }
    else                             { &gst_report ("xml_unexp_tag", $elem); shift @$tree; }
  }

  $$hash{"order"} = \@order unless $#order < 0;
  $$hash{"searchdomain"} = \@searchdomain unless $#searchdomain < 0;
  $$hash{"nameserver"} = \@nameserver unless $#nameserver < 0;
  $$hash{"statichost"} = \%statichost unless scalar keys %statichost == 0;
  $$hash{"interface"} = \%interface unless scalar keys %interface == 0;
  $$hash{"dialing"} = \%dialing unless scalar keys %dialing == 0;
}

# <network><statichost>...</statichost></network>
sub xml_parse_statichost
{
  my ($tree, $statichost) = @_;
  my ($ip, @alias, $elem);

  shift @$tree;

  while ($elem = shift @$tree)
  {
    if    ($elem eq "ip")    { $ip = &gst_xml_get_pcdata (shift @$tree); }
    elsif ($elem eq "alias") { push(@alias, &gst_xml_get_pcdata (shift @$tree)); }
    else                     { &gst_report ("xml_unexp_tag", $elem); shift @$tree; }
  }

  # common regexp for IPv4 and IPv6
  if ($ip =~ /([0-9a-fA-F\.:])+/)
  {
    $$statichost{$ip} = \@alias;
  }
}

sub xml_parse_dialing
{
  my ($tree, $dialing) = @_;
  my (%hash, $name, $elem);

  shift @$tree;

  while ($elem = shift @$tree)
  {
    $hash{$elem} = &gst_xml_get_pcdata (shift @$tree);
  }

  $name = $hash{"name"};
  $$dialing{$name} = \%hash;
}

# couple of functions for fixing profiles format
sub fix_profile_modem_iface
{
  my ($configuration, $section, $dialing) = @_;
  my ($s, $key, %h);

  foreach $s (keys %$dialing)
  {
    if ($s eq $section)
    {
      $h = $$dialing{$s};

      foreach $key (keys %$h)
      {
        $$configuration{$key} = $$h{$key};
      }
    }
  }
}

sub fix_profile_interface_format
{
  my ($interface, $dialing) = @_;
  my (%configuration, $key, $section);

  return if (exists $$interface{"configuration"});

  foreach $key (keys %$interface)
  {
    if ($key !~ /^(dev|enabled|hwaddr)$/)
    {
      if ($key eq 'wvsection')
      {
        &fix_profile_modem_iface (\%configuration, $$interface{$key}, $dialing);
        $configuration{"section"} = $$interface{$key};
      }
      else
      {
        $dest_key = $key;
        $configuration{$key} = $$interface{$key};
      }

      delete $$interface{$key};
    }
  }

  $$interface{"type"} = &gst_network_get_interface_type ($$interface{"dev"});

  if (%configuration)
  {
    $$interface{"configuration"} = \%configuration;
  }
}

sub fix_profile_format
{
  my ($hash) = @_;
  my ($interfaces, $dialing, $iface);

  $interfaces = $$hash{"interface"};
  $dialing    = $$hash{"dialing"};

  foreach $iface (keys %$interfaces)
  {
    &fix_profile_interface_format ($$interfaces{$iface}, $dialing);
  }
}

sub xml_parse_profile
{
  my ($tree, $hash) = @_;
  my (%profile);

  &xml_parse_network ($tree, \%profile);

  # We've got to translate the old profiles format
  &fix_profile_format (\%profile);

  push @{$hash->{"profiledb"}{"profile"}}, \%profile;
}

sub xml_parse_profiledb
{
  my ($tree, $hash) = @_;

  shift @$tree; # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "profile") { &xml_parse_profile ($$tree[1], $hash); }
    else
    {
		  &gst_report ("xml_unexp_tag", $$tree[0]);
    }

    shift @$tree;
    shift @$tree;
  }
}

# --- XML printing --- #

sub xml_print_configuration
{
  my ($h) = @_;
  my @scalar_keys =
      qw(auto hostname gateway gatewaydev gwdevunsup domain
         hostmatch workgroup smbdesc winsserver winsuse smbuse
         smartdhcpcd smbinstalled dialinstalled name);
  my @array_keys =
      qw(nameserver searchdomain order);

  # Hostname, domain, search domains, nameservers.

  &gst_xml_print_scalars ($h, @scalar_keys);
  &gst_xml_print_arrays ($h, @array_keys);
  &network_xml_print_statichost ($h);

  &xml_print_interfaces ($$h{"interface"});
}

sub xml_print_profiledb
{
  my ($h) = @_;

  &gst_xml_print_vspace ();

  &gst_xml_print_line ("<profiledb>\n");
  &gst_xml_enter ();
  foreach $i (@{$$h{"profiledb"}{"profile"}})
  {
    gst_xml_print_line ("<profile>\n");
    gst_xml_enter ();

    &xml_print_configuration ($i);

    gst_xml_leave ();
    gst_xml_print_line ("</profile>\n");
  }
  gst_xml_leave ();
  gst_xml_print_line ("</profiledb>\n");
}

sub xml_print_interfaces
{
  my ($h) = @_;
  my ($dev, $type);

  foreach $dev (keys %$h)
  {
    if ($$h{$dev}{"type"})
    {
      $type = $$h{$dev}{"type"};
      delete $$h{$dev}{"type"};
    }
    else
    {
      $type = &gst_network_get_interface_type ($dev);
    }

    &gst_xml_print_vspace ();
    &gst_xml_print_line ("<interface type='$type'>");
    &gst_xml_enter ();
    &gst_xml_print_hash ($$h{$dev});

    &gst_xml_leave ();
    &gst_xml_print_line ("</interface>");
  }
}

sub xml_print
{
  my ($h) = @_;

  &gst_xml_print_begin ();

  &xml_print_configuration ($h);
  &xml_print_profiledb ($h);

  &gst_xml_print_end ();
}


# Reading profiles
sub read_profiledb
{
  my ($hash) = @_;
  my ($path);
  my ($tree);

  $path = gst_file_get_data_path () . "/" . $main::tool->{"name"} . "/";
  chmod (0755, $path);
  chmod (0644, $path .  $profile_file);

  $tree = &gst_xml_scan ($path . $profile_file, $tool);
  if ($tree && scalar @$tree)
  {
    if ($$tree[0] eq 'profiledb')
    {
      xml_parse_profiledb ($$tree[1], $hash);
    }
    else
    {
      gst_report ('xml_unexp_tag', $$tree[0]);
    }
  }
}

# Writing profiles
sub write_profiledb
{
  my ($hash) = @_;
  my $profiledb = $hash->{'profiledb'};
  my $path = &gst_file_get_data_path () . "/" . $main::tool->{'name'} . "/";
  chmod (0755, $path);

  if ($profiledb) {
    # Write our profiles.
    my $fh = &gst_file_open_write_from_names ($path . $profile_file);
    if ($fh)
    {
      local *STDOUT = $fh;
      &xml_print_profiledb ($hash);
      close ($fh);

      delete $hash->{'profiledb'};
    }
  }
  else
  {
    gst_file_remove ($path . $profile_file);
  }

  chmod (0644, $path . $profile_file);
}


# Top-level actions.
sub get
{
  my $hash;

  # network interface stuff
  $hash = &gst_network_conf_get ();
  &read_profiledb (\%$hash);
  &gst_network_ensure_loopback ($hash);

  &gst_report_end ();
  &xml_print ($hash);
}


sub set
{
  my $hash;

  $hash = &xml_parse ();
  &write_profiledb ($hash);
  &gst_network_conf_set ($hash);
  &gst_report_end ();
}

sub set_profile
{
  my ($tool, $profile_name) = @_;
  my ($hash, $profiles, $profile);

  &read_profiledb (\%$hash);
  $profiles = $$hash{"profiledb"}{"profile"};

  foreach $profile (@$profiles)
  {
    if ($$profile{"name"} eq $profile_name)
    {
      &gst_network_conf_set ($profile);
    }
  }

  &gst_report_end ();
}

sub save_profiles
{
  my $hash;

  $hash = &xml_parse ();
  &write_profiledb ($hash);
  &gst_report_end ();
}


# --- Filter config: XML in, XML out --- #


sub filter
{
  my $hash;

  $hash = &xml_parse ();
  &gst_report_end ();
  &xml_print ($hash);
}

sub enable_iface
{
  my ($tool, $iface, $enabled) = @_;
  my (%dist_attrib, $iface_set);
  my %hash = ("configuration" => {"file" => $iface});

  %dist_attrib = &gst_network_get_interface_replace_table ();
  $iface_set = $dist_attrib{"iface_set"};
  &$iface_set (\%hash, undef, $enabled, 1);

  # small hack for ensuring that the interface is really down
  # when messing ifup/ifdown/ifconfig calls
  if ($enabled == 0)
  {
    gst_file_run ("ifconfig $iface down");
    &drop_dhcp_connection ($iface);
    &drop_pppd_connection ($iface);
  }

  # Don't forget to do gst_end when the reports are over!
  &gst_report_end ();
  # XML output would come here, but this directive returns no XML.
}

sub enable_iface_with_config
{
  my ($tool) = @_;
  my ($tree, $hash, $ret, $str);

  # Scan XML to tree.
  $tree = &gst_xml_scan ();

  if (shift @$tree eq "interface")
  {
    $hash = &gst_network_xml_parse_interface (shift @$tree);
  }

  $ret = &gst_network_enable_iface_with_config ($hash);
  &gst_report_end ();

  &gst_xml_print_begin  ("enable-iface");
  &gst_xml_print_pcdata ("success", ($ret == 0) ? "1" : "0");
  &gst_xml_print_end    ("enable-iface");
}

sub list_ifaces
{
  my ($tool) = @_;
  my ($ifaces, $iface, @arr);

  $ifaces = &gst_network_interfaces_get_info ();

  foreach $iface (keys %$ifaces)
  {
    push @arr, $$ifaces{$iface};
  }

  &gst_report_end ();
  &gst_xml_print_begin ("network-ifaces");
  &gst_xml_print_structure (\@arr, "interface");
  &gst_xml_print_end ("network-ifaces");
}

sub detect_modem
{
  my ($tool) = @_;
  my ($device);

  $device = &gst_network_autodetect_modem ();
  &gst_report_end ();

  &gst_xml_print_begin  ("network-modem-device");
  &gst_xml_print_pcdata ("device", $device) if ($device ne undef);
  &gst_xml_print_end    ("network-modem-device");
}

sub detect_essids
{
  my ($tool, $iface) = @_;
  my (@essids);

  $essids = &gst_network_detect_essids ($iface);
  &gst_report_end ();

  &gst_xml_print_begin ("essids");
  &gst_xml_print_structure ($essids, "network");
  &gst_xml_print_end   ("essids");
}

sub set_gateway
{
  my ($tool, $iface, $address) = @_;

  &gst_network_route_set_default_gw ($iface, $address);
  &gst_report_end ();
}

# --- Main --- #

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

$directives = {
  "get"                => [ \&get,          [], "" ],
  "set"                => [ \&set,          [], "" ],
  "filter"             => [ \&filter,       [], "" ],
  "list_ifaces"        => [ \&list_ifaces,  [],
                            "List interfaces and active/inactive status." ],
  "save_profiles"      => [ \&save_profiles, [], "Save the profiles list" ],
  "set_profile"        => [ \&set_profile,  [ "profile_name" ],
                            "Set a profile as the default configuration" ],
  "enable_iface"       => [ \&enable_iface, [ "interface", "enabled" ],
                            "Immediatly enable or disable a given interface. " .
                            "interface is the file tag value, enabled is 1 or 0." ],
  "enable_iface_with_config" => [ \&enable_iface_with_config, [],
                                  "Enable an interface with a given configuration" ],
  "detect_modem"       => [ \&detect_modem, [], "Detects the modem device." ],
  "detect_essids"      => [ \&detect_essids, [ "interface" ], "Detects active wireless networks" ],
  "set_gateway"        => [ \&set_gateway,  [ "interface", "address" ],  "Sets the default gateway" ]
};

$tool = &gst_init ($name, $version, $description, $directives, @ARGV);
&gst_platform_ensure_supported ($tool, @platforms);
&gst_run ($tool);
