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

# Users account mannager. Designed to be architecture and distribution independent.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Hans Petter Jansson <hpj@ximian.com>,
#          Arturo Espinosa <arturo@ximian.com>,
#          Tambet Ingo <tambet@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

# Best viewed with 100 columns of width.

# Configuration files affected:
#
# /etc/passwd
# /etc/group
# /etc/shadow
# /etc/login.defs
# /etc/shells
# /etc/skel/

# NIS support will come later.

# Running programs affected/used:
#
# adduser: creating users.
# usermod: modifying user data.
# passwd: assigning or changing passwords. (Un)locking users.
# chfn: modifying finger information - Name, Office, Office phone, Home phone.
# pw: modifying users/groups and user/group data on FreeBSD.


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/replace.pl$DOTIN";
}


# --- Tool information --- #

$name = "users";
$version = "1.4.2";
@platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0", "redhat-7.1",
              "redhat-7.2", "redhat-7.3", "redhat-8.0", "redhat-9",
              "openna-1.0",
              "mandrake-7.1", "mandrake-7.2", "mandrake-9.0", "mandrake-9.1", "mandrake-9.2",
              "mandrake-10.0", "mandrake-10.1",
              "debian-3.0", "debian-3.1", "debian-4.0", "debian-testing",
              "suse-7.0", "suse-9.0", "suse-9.1", "turbolinux-7.0",
              "slackware-8.0.0", "slackware-8.1", "slackware-9.0.0", "slackware-9.1.0", "slackware-10.0.0", "slackware-10.1.0", "slackware-10.2.0",
              "freebsd-4", "freebsd-5", "freebsd-6",
              "gentoo", "vlos-1.2",
              "archlinux",
              "pld-1.0", "pld-1.1", "pld-1.99", "fedora-1", "fedora-2", "fedora-3", "rpath", "vine-3.0", "vine-3.1");

$description =<<"end_of_description;";
       Manages system users.
end_of_description;

# --- System config file locations --- #

# We list each config file type with as many alternate locations as possible.
# They are tried in array order. First found = used.

@passwd_names =     ( "/etc/passwd" );
@shadow_names =     ( "/etc/shadow", "/etc/master.passwd" );
@group_names =      ( "/etc/group" );
@login_defs_names = ( "/etc/login.defs", "/etc/adduser.conf" );
@shell_names =      ( "/etc/shells" );
@skel_dir =         ( "/usr/share/skel", "/etc/skel" );

$profile_file =     "profiles.xml";


# Where are the tools?

$cmd_usermod  = &gst_file_locate_tool ("usermod");
$cmd_userdel  = &gst_file_locate_tool ("userdel");
$cmd_useradd  = &gst_file_locate_tool ("useradd");	
$cmd_groupdel = &gst_file_locate_tool ("groupdel");
$cmd_groupadd = &gst_file_locate_tool ("groupadd");
$cmd_groupmod = &gst_file_locate_tool ("groupmod");
$cmd_gpasswd  = &gst_file_locate_tool ("gpasswd");	
$cmd_chfn     = &gst_file_locate_tool ("chfn");
$cmd_pw       = &gst_file_locate_tool ("pw");

# --- Mapping constants --- #

%users_prop_map = ();
@users_prop_array = ();

if ($$tool{"platform"} eq "Linux")
{
  @users_prop_array = (
    "key", 0,
    "login", 1,
    "password", 2,
    "uid", 3,
    "gid", 4,
    "comment", 5,
    "home", 6,
    "shell", 7,
    "last_mod", 8, # Read shadow (5) for these.
    "passwd_min_life", 9,
    "passwd_max_life", 10,
    "passwd_exp_warn", 11,
    "passwd_exp_disable", 12,
    "passwd_disable", 13,
    "reserved", 14,
    "is_shadow", 15,
    "", "");
}
else
{
  @users_prop_array = (
    "key", 0,
    "login", 1,
    "password", 2,
    "uid", 3,
    "gid", 4,
    "comment", 5,
    "home", 6,
    "shell", 7,
    "", "");
}

for ($i = 0; $users_prop_array[$i] ne ""; $i += 2)
{
  $users_prop_map {$users_prop_array[$i]} = $users_prop_array[$i + 1];
  $users_prop_map {$users_prop_array[$i + 1]} = $users_prop_array[$i];
}

%groups_prop_map = ();
@groups_prop_array = (
  "key", 0,
  "name", 1,
	"password", 2,
	"gid", 3,
	"users", 4,
	"", "");

for ($i = 0; $groups_prop_array[$i] ne ""; $i += 2)
{
  $groups_prop_map {$groups_prop_array[$i]} = $groups_prop_array[$i + 1];
  $groups_prop_map {$groups_prop_array[$i + 1]} = $groups_prop_array[$i];
}

# Please, keep this list sorted
%groups_desc_map = (
  # TRANSLATORS: this is a list of infinitive actions
  "adm"       => _("Monitor system logs"),
  "admin"     => _("Administer the system"),
  "audio"     => _("Use audio devices"),
  "cdrom"     => _("Use CD-ROM drives"),
  "dialout"   => _("Use modems"),
  "dip"       => _("Connect to Internet using a modem"),
  "fax"       => _("Send and receive faxes"),
  "floppy"    => _("Use floppy drives"),
  "plugdev"   => _("Enable access to external storage devices automatically"),
  "scanner"   => _("Use scanners"),
  "tape"      => _("Use tape drives"),
  "wheel"     => _("Be able to get administrator privileges"),
);

%login_defs_prop_map = ();
%profiles_prop_map = ();

sub get_login_defs_prop_array
{
  my @prop_array;
  my @login_defs_prop_array_default =
    (
     "QMAIL_DIR",      "qmail_dir",
     "MAIL_DIR",       "mailbox_dir",
     "MAIL_FILE",      "mailbox_file",
     "PASS_MAX_DAYS",  "pwd_maxdays",
     "PASS_MIN_DAYS",  "pwd_mindays",
     "PASS_MIN_LEN",   "pwd_min_length",
     "PASS_WARN_AGE",  "pwd_warndays",
     "UID_MIN",        "umin",
     "UID_MAX",        "umax",
     "GID_MIN",        "gmin",
     "GID_MAX",        "gmax",
     "USERDEL_CMD",    "del_user_additional_command",
     "CREATE_HOME",    "create_home",
     "", "");

  my @login_defs_prop_array_suse =
    (
     "QMAIL_DIR",      "qmail_dir",
     "MAIL_DIR",       "mailbox_dir",
     "MAIL_FILE",      "mailbox_file",
     "PASS_MAX_DAYS",  "pwd_maxdays",
     "PASS_MIN_DAYS",  "pwd_mindays",
     "PASS_MIN_LEN",   "pwd_min_length",
     "PASS_WARN_AGE",  "pwd_warndays",
     "UID_MIN",        "umin",
     "UID_MAX",        "umax",
     "SYSTEM_GID_MIN", "gmin",
     "GID_MAX",        "gmax",
     "USERDEL_CMD",    "del_user_additional_command",
     "CREATE_HOME",    "create_home",
     "", "");
  
  if ($gst_dist =~ /^suse/)
  {
    @prop_array = @login_defs_prop_array_suse;
  }
  else
  {
    @prop_array = @login_defs_prop_array_default;
  }

  for ($i = 0; $prop_array [$i] ne ""; $i += 2)
  {
    $login_defs_prop_map {$prop_array [$i]}     = $prop_array [$i + 1];
    $login_defs_prop_map {$prop_array [$i + 1]} = $prop_array [$i];
  }
}

sub get_profiles_prop_array
{
  my @prop_array;
  my @profiles_prop_array_default =
    (
     "NAME" ,          "name",
     "COMMENT",        "comment",
     "LOGINDEFS",      "login_defs",
     "HOME_PREFFIX",   "home_prefix",
     "SHELL",          "shell",
     "GROUP",          "group",
     "SKEL_DIR",       "skel_dir",
     "QMAIL_DIR" ,     "qmail_dir",
     "MAIL_DIR" ,      "mailbox_dir",
     "MAIL_FILE" ,     "mailbox_file",
     "PASS_RANDOM",    "pwd_random",
     "PASS_MAX_DAYS" , "pwd_maxdays",
     "PASS_MIN_DAYS" , "pwd_mindays",
     "PASS_MIN_LEN" ,  "pwd_min_length",
     "PASS_WARN_AGE" , "pwd_warndays",
     "UID_MIN" ,       "umin",
     "UID_MAX" ,       "umax",
     "GID_MIN" ,       "gmin",
     "GID_MAX" ,       "gmax",
     "USERDEL_CMD" ,   "del_user_additional_command",
     "CREATE_HOME" ,   "create_home",
     "", "");

  my @profiles_prop_array_suse =
    (
     "NAME" ,          "name",
     "COMMENT",        "comment",
     "LOGINDEFS",      "login_defs",
     "HOME_PREFFIX",   "home_prefix",
     "SHELL",          "shell",
     "GROUP",          "group",
     "SKEL_DIR",       "skel_dir",
     "QMAIL_DIR" ,     "qmail_dir",
     "MAIL_DIR" ,      "mailbox_dir",
     "MAIL_FILE" ,     "mailbox_file",
     "PASS_RANDOM",    "pwd_random",
     "PASS_MAX_DAYS" , "pwd_maxdays",
     "PASS_MIN_DAYS" , "pwd_mindays",
     "PASS_MIN_LEN" ,  "pwd_min_length",
     "PASS_WARN_AGE" , "pwd_warndays",
     "UID_MIN" ,       "umin",
     "UID_MAX" ,       "umax",
     "GID_MIN" ,       "gmin",
     "GID_MAX" ,       "gmax",
     "USERDEL_CMD" ,   "del_user_additional_command",
     "CREATE_HOME" ,   "create_home",
     "", "");

  if ($gst_dist =~ /suse/)
  {
    @prop_array = @profiles_prop_array_suse;
  }
  else
  {
    @prop_array = @profiles_prop_array_default;
  }

  for ($i = 0; $prop_array[$i] ne ""; $i += 2)
  {
    $profiles_prop_map {$prop_array [$i]}     = $prop_array [$i + 1];
    $profiles_prop_map {$prop_array [$i + 1]} = $prop_array [$i];
  }
}

my $rh_logindefs_defaults = {
  'shell'       => '/bin/bash',
  'group'       => '$user',
  'skel_dir'    => '/etc/skel/',
};

my $gentoo_logindefs_defaults = {
  'shell'       => '/bin/bash',
  'group'       => 'users',
  'skel_dir'    => '/etc/skel/',
};

my $freebsd_logindefs_defaults = {
  'shell'       => '/bin/sh',
  'group'       => '$user',
  'skel_dir'    => '/etc/skel/',
};

my $logindefs_dist_map = {
  'redhat-5.2'      => $rh_logindefs_defaults,
  'redhat-6.0'      => $rh_logindefs_defaults,
  'redhat-6.1'      => $rh_logindefs_defaults,
  'redhat-6.2'      => $rh_logindefs_defaults,
  'redhat-7.0'      => $rh_logindefs_defaults,
  'redhat-7.1'      => $rh_logindefs_defaults,
  'redhat-7.2'      => $rh_logindefs_defaults,
  'redhat-7.3'      => $rh_logindefs_defaults,
  'redhat-8.0'      => $rh_logindefs_defaults,
  'redhat-9'        => $rh_logindefs_defaults,
  'openna-1.0'      => $rh_logindefs_defaults,
  'mandrake-7.1'    => $rh_logindefs_defaults,
  'mandrake-7.2'    => $rh_logindefs_defaults,
  'mandrake-9.0'    => $rh_logindefs_defaults,
  'mandrake-9.1'    => $rh_logindefs_defaults,
  'mandrake-9.2'    => $rh_logindefs_defaults,
  'mandrake-10.0'   => $rh_logindefs_defaults,
  'mandrake-10.1'   => $rh_logindefs_defaults,
  'pld-1.0'         => $rh_logindefs_defaults,
  'pld-1.1'         => $rh_logindefs_defaults,
  'pld-1.99'        => $rh_logindefs_defaults,
  'fedora-1'        => $rh_logindefs_defaults,
  'fedora-2'        => $rh_logindefs_defaults,
  'fedora-3'        => $rh_logindefs_defaults,
  'rpath'           => $rh_logindefs_defaults,
  'debian-3.0'      => $rh_logindefs_defaults,
  'debian-3.1'      => $rh_logindefs_defaults,
  'debian-4.0'      => $rh_logindefs_defaults,
  'debian-testing'  => $rh_logindefs_defaults,
  'vine-3.0'        => $rh_logindefs_defaults,
  'vine-3.1'        => $rh_logindefs_defaults,
  'gentoo'	        => $gentoo_logindefs_defaults,
  'vlos-1.2'	      => $gentoo_logindefs_defaults,
  'archlinux'       => $gentoo_logindefs_defaults,
  'slackware-9.1.0' => $gentoo_logindefs_defaults,
  'slackware-10.0.0' => $gentoo_logindefs_defaults,
  'slackware-10.1.0' => $gentoo_logindefs_defaults,
  'slackware-10.2.0' => $gentoo_logindefs_defaults,
  'freebsd-4'       => $freebsd_logindefs_defaults,
  'freebsd-5'       => $freebsd_logindefs_defaults,
  'freebsd-6'       => $freebsd_logindefs_defaults,
  'suse-7.0'        => $gentoo_logindefs_defaults,
  'suse-9.0'        => $gentoo_logindefs_defaults,
  'suse-9.1'        => $gentoo_logindefs_defaults,

  # FIXME: I don't know about those, so using RH values for now.
  'turbolinux-7.0'  => $rh_logindefs_defaults,
  'slackware-8.0.0' => $rh_logindefs_defaults,
  'slackware-8.1'   => $rh_logindefs_defaults,
  'slackware-9.0.0' => $rh_logindefs_defaults,
};


# Add reporting table.

&gst_report_table ({
  'users_read_profiledb_success' => ['info', 'Profiles read successfully.'],
  'users_read_profiledb_fail'    => ['warn', 'Profiles read failed.'],
  'users_read_users_success'     => ['info', 'Users read successfully.'],
  'users_read_users_fail'        => ['warn', 'Users read failed.'],
  'users_read_groups_success'    => ['info', 'Groups read successfully.'],
  'users_read_groups_fail'       => ['warn', 'Groups read failed.'],
  'users_read_shells_success'    => ['info', 'Shells read successfully.'],
  'users_read_shells_fail'       => ['warn', 'Reading shells failed.'],

  'users_write_profiledb_success' => ['info', 'Profiles written successfully.'],
  'users_write_profiledb_fail'    => ['warn', 'Writing profiles failed.'],
  'users_write_users_success'     => ['info', 'Users written successfully.'],
  'users_write_users_fail'        => ['warn', 'Writing users failed.'],
  'users_write_groups_success'    => ['info', 'Groups written successfully.'],
  'users_write_groups_fail'       => ['warn', 'Writing groups failed.'],
});


# --- Utility stuff --- #

sub max
{
  return $_[0] > $_[1]? $_[0]: $_[1];
}

sub arr_cmp_recurse
{
	my ($a1, $a2) = @_;
	my $i;
	
	return -1 if ($#$a1 != $#$a2);
	
	for ($i = 0; $i <= $#$a1; $i++) {
	  if (ref ($$a1[$i]) eq "ARRAY") { # see if this is a reference.
		  return -1 if &arr_cmp_recurse ($$a1[$i], $$a2[$i]); # we assume it is a ref to an array.
		} elsif ($$a1[$i] ne $$a2[$i]) {
		  return -1;
		}
	}
	
	return 0;
}

sub get_logindefs
{
  my $profiledb = shift;
  return unless $profiledb;

  foreach my $profile (@$profiledb)
  {
    return $profile if (exists ($profile->{'login_defs'}));
  }
}

# --- Configuration manipulation --- #

sub read
{
  my (%hash);

  &read_group         (\%hash);
  &read_passwd_shadow (\%hash);
  &read_profiledb     (\%hash);
  &read_shells        (\%hash);

  return \%hash;
}

sub check_use_md5
{
  my ($file) = @_;
  my ($fh, @line, $i, $use_md5);

  my $fh = &gst_file_open_read_from_names ("/etc/pam.d/$file");
  return 0 if (!$fh);

  $use_md5 = 0;

  while (<$fh>)
  {
    next if &gst_ignore_line ($_);
    chomp;
    @line = split /[ \t]+/;

    if ($line[0] eq "\@include")
    {
      $use_md5 = &check_use_md5 ($line[1]);
    }
    elsif ($line[0] eq "password")
    {
      foreach $i (@line)
      {
        $use_md5 = 1 if ($i eq "md5");
      }
    }
  }

  close $fh;
  return $use_md5;
}

sub logindefs_add_defaults
{
  # Common for all distros
  my $logindefs = {
    'name'        => _("Default"),
    'comment'     => _("Default profile"),
    'default'     => 1,
    'login_defs'  => 1,
    'home_prefix' => '/home/$user',
  };

  # Distro specific
  my $dist_specific = $logindefs_dist_map->{$gst_dist};

  # Just to be 100% sure SOMETHING gets filled:
  unless ($dist_specific)
  {
    $dist_specific = $rh_logindefs_defaults;
    &gst_debug_print_line ("logindefs_add_defaults: Couldn't find distro specific parameters.");
  }

  foreach my $key (keys %$dist_specific)
  {
    # Make sure there's no crappy entries
    if (exists ($profiles_prop_map{$key}) || $key eq "groups")
    {
      $logindefs->{$key} = $dist_specific->{$key};
    }
  }
  return $logindefs;
}

sub read_logindefs
{
  my $profiledb = shift;
  my $logindefs =  &get_logindefs ($profiledb);

  unless ($logindefs)
  {
    $logindefs = &logindefs_add_defaults ();
    push @$profiledb, $logindefs;
  }

  # Get new data in case someone has changed login_defs manually.
  my $fh = &gst_file_open_read_from_names (@login_defs_names);

  if ($fh)
  {
    while (<$fh>)
    {
      next if &gst_ignore_line ($_);
      chomp;
      my @line = split /[ \t]+/;
      if (exists $login_defs_prop_map{$line[0]})
      {
        $logindefs->{$login_defs_prop_map{$line[0]}} = $line[1];
      }
    }

    close $fh;
  }
  else
  {
    # Put safe defaults for distros/OS that don't have any defaults file
    $logindefs->{"umin"} = '1000';
    $logindefs->{"umax"} = '60000';
    $logindefs->{"gmin"} = '1000';
    $logindefs->{"gmax"} = '60000';
  }
}

sub read_profiledb
{
  my ($hash) = @_;
  my $path;
  my $profiles = [];

  $$hash{'profiledb'} = $profiles;

  $path = &gst_file_get_data_path () . "/" . $main::tool->{'name'} . "/" . $profile_file;
  my $tree = &gst_xml_scan ($path, $tool);

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

  &read_logindefs ($profiles);

  if (scalar @$profiles)
  {
    &gst_report ('users_read_profiledb_success');
  }
  else
  {
    &gst_report ('users_read_profiledb_fail');
  }
}

sub read_passwd_shadow
{
  my ($hash) = @_;
  my ($ifh, @users, %users_hash, $passwd_last_modified);
  my (@line, $copy, %tmphash);
  my $login_pos = $users_prop_map{"login"};
  my $i = 0;

  # Find the passwd file.

  $ifh = &gst_file_open_read_from_names(@passwd_names);
  unless ($ifh)
  {
    &gst_report ('users_read_users_fail');
    return;
  }
  $passwd_last_modified = (stat ($ifh))[9]; # &get the mtime.

  # Parse the file.

  @users = ();
  %users_hash = ();

  while (<$ifh>)
  {
    chomp;
    # FreeBSD allows comments in the passwd file.
    next if &gst_ignore_line ($_);
    $_ = &gst_xml_quote ($_);

    @line = split ':', $_, -1;
    unshift @line, sprintf ("%06d", $i);
    $copy = [@line];
    $users_hash{sprintf ("%06d", $i)} = $copy;
    $tmphash{$line[$login_pos]} = $copy;
    push (@users, $copy);
    $i ++;
  }
  &gst_file_close ($ifh);
	
  # Find the shadow file.

  $ifh = &gst_file_open_read_from_names(@shadow_names);
  if ($ifh) {
    my ($login, $passwd);
    my $passwd_pos = $users_prop_map{"password"};

    while (<$ifh>)
    {
      chomp;
      # FreeBSD allows comments in the shadow passwd file.
      next if &gst_ignore_line ($_);
      $_ = &gst_xml_quote ($_);

      @line = split ':', $_, -1;
      push @line, 1;
      $login = shift @line;
      $passwd = shift @line;
      push @{$tmphash{$login}}, @line;
      @{$tmphash{$login}}[$passwd_pos] = $passwd;
    }

    &gst_file_close ($ifh);
  }

  $$hash{"users"}      = \@users;
  $$hash{"users_hash"} = \%users_hash;
  $$hash{"passwd_last_modified"} = $passwd_last_modified;
  $$hash{"use_md5"} = &check_use_md5 ("passwd");

  if (scalar @users)
  {
    &gst_report ('users_read_users_success');
  }
  else
  {
    &gst_report ('users_read_users_fail');
  }
}

sub read_group
{
  my ($hash) = @_;
  my ($ifh, @groups, %groups_hash, $group_last_modified);
  my (@line, $copy, @a);
  my $i = 0;

  # Find the file.

  $ifh = &gst_file_open_read_from_names(@group_names);
  unless ($ifh)
  {
    &gst_report ('users_read_groups_fail');
    return;
  }
  $group_last_modified = (stat ($ifh))[9]; # &get the mtime.

  # Parse the file.
	
  @groups = ();
  %groups_hash = ();

  while (<$ifh>)
  {
    chomp;

    # FreeBSD allows comments in the group file. */
    next if &gst_ignore_line ($_);
    $_ = &gst_xml_unquote ($_);

    @line = split ':', $_, -1;
    unshift @line, sprintf ("%06d", $i);
    @a = split ',', pop @line;
    push @line, [@a];
    $copy = [@line];
    $groups_hash{sprintf ("%06d", $i)} = $copy;
    push (@groups, $copy);
    $i ++;
  }
  &gst_file_close ($ifh);

  $$hash{"groups"}      = \@groups;
  $$hash{"groups_hash"} = \%groups_hash;
  $$hash{"group_last_modified"} = $group_last_modified;

  if (scalar @groups)
  {
    &gst_report ('users_read_groups_success');
  }
  else
  {
    &gst_report ('users_read_groups_fail');
  }
}

sub read_shells
{
  my ($hash) = @_;
  my ($ifh, @shells);

  # Init @shells, I think every *nix has /bin/false.
  push (@shells, "/bin/false") if (stat ("/bin/false"));
  
  $ifh = &gst_file_open_read_from_names(@shell_names);
  return unless $ifh;

  while (<$ifh>)
  {
    next if &gst_ignore_line ($_);
    chomp;
    push (@shells, $_) if (stat ($_) ne "");
  }
  &gst_file_close ($ifh);

  $$hash{"shelldb"} = \@shells;
  &gst_report ('users_read_shells_success');
}


sub write_group_passwd
{
  my ($hash) = @_;
  my ($users, $users_hash, $groups, $groups_hash);
  my ($passwd_last_modified, $group_last_modified);
  my ($i, $j, $k);
  my (%old_hash);
  my (%users_all, $parse_users_hash, $parse_users, $parse_passwd_last_modified);
  my (%groups_all, $parse_groups_hash, $parse_groups, $parse_group_last_modified);

  $parse_users = $$hash{"users"};
  $parse_users_hash = $$hash{"users_hash"};
  $parse_passwd_last_modified = $$hash{"passwd_last_modified"};
  $parse_groups = $$hash{"groups"};
  $parse_groups_hash = $$hash{"groups_hash"};
  $parse_group_last_modified = $$hash{"group_last_modified"};

  &read_passwd_shadow (\%old_hash);
  &read_group (\%old_hash);

  $users = $old_hash{"users"};
  $users_hash = $old_hash{"users_hash"};
  $passwd_last_modified = $old_hash{"passwd_last_modified"};
  $groups = $old_hash{"groups"};
  $groups_hash = $old_hash{"groups_hash"};
  $group_last_modified = $old_hash{"group_last_modified"};

#	if ($passwd_last_modified > $parse_passwd_last_modified) 
#	{
#	  print STDERR "Password file may be inconsistent! No changes made.\n";
#		return;
#	}

  foreach $i (keys (%$users_hash)) 
	{
		$users_all{$i} |= 1;
	}	
	
	foreach $i (keys (%$parse_users_hash))
	{
	  $users_all{$i} |= 2;
	}
	
  foreach $i (keys (%$groups_hash)) 
	{
		$groups_all{$i} |= 1;
	}	
	
	foreach $i (keys (%$parse_groups_hash))
	{
	  $groups_all{$i} |= 2;
	}
	
	foreach $i (sort (keys (%users_all)))
	{
	  &del_user ($$users_hash{$i}) if ($users_all{$i} == 1);
	}

  foreach $i (sort (keys (%groups_all)))
	{
	  &del_group ($$groups_hash{$i}) if ($groups_all{$i} == 1);
	}
	
	foreach $i (sort (keys (%groups_all)))
	{
	  &add_group ($$parse_groups_hash{$i}) if ($groups_all{$i} == 2);
	}
	
	foreach $i (sort (keys (%users_all)))
	{
	  &add_user ($$parse_users_hash{$i}) if ($users_all{$i} == 2);
	}

	foreach $i (sort (keys (%groups_all)))
	{
	  if ($groups_all{$i} == 3 && &arr_cmp_recurse ($$groups_hash{$i}, $$parse_groups_hash{$i}))
		{
		  &change_group ($$groups_hash{$i}, $$parse_groups_hash{$i});
		}
	}
	
	foreach $i (sort (keys (%users_all)))
	{
	  if ($users_all{$i} == 3 && &arr_cmp_recurse ($$users_hash{$i}, $$parse_users_hash{$i}))
		{
		  &change_user ($$users_hash{$i}, $$parse_users_hash{$i});
		}
	}

  &gst_report ('users_write_users_success');
  &gst_report ('users_write_groups_success');
}

sub del_user
{
	my ($data) = @_;
  my ($command);
	
  if ($gst_dist =~ /^freebsd/) {
    $command = "$cmd_pw userdel -n \'" . $$data[$users_prop_map{"login"}] . "\' ";
  } else {
    $command = "$cmd_userdel \'" . $$data[$users_prop_map{"login"}] . "\'";
  }
  &gst_file_run ($command);
}

sub change_user_chfn
{
  my ($old_comment, $comment, $username) = @_;
  my ($fname, $office, $office_phone, $home_phone);
  my ($command, @line, @old_line);

  return if !$username;

  @line     = split /\,/, $comment;
  @old_line = split /\,/, $old_comment;

  # Compare old and new data
  return if (!&arr_cmp_recurse (\@line, \@old_line));

  if ($gst_dist =~ /^freebsd/)
  {
    $command = "$cmd_pw usermod -n " . $username . " -c \'" . $comment . "\'";
  }
  else
  {
    ($fname, $office, $office_phone, $home_phone) = @line;

    $fname = "-f \'" . $fname . "\'";
    $home_phone = "-h \'" . $home_phone . "\'";

    if ($gst_dist =~ /^debian/  || $gst_dist =~ /^archlinux/)
    {
      $office = "-r \'" . $office . "\'";
      $office_phone = "-w \'" . $office_phone . "\'";
    }
    else
    {
      $office = "-o \'" . $office . "\'";
      $office_phone = "-p \'" . $office_phone . "\'";
    }  
  
    $command = "$cmd_chfn $fname $office $office_phone $home_phone $username";
  }

  &gst_file_run ($command);
}

sub add_user
{
	my ($data) = @_;
	my ($home_parents, $tool_mkdir);
  
  $log = $$data[$users_prop_map{"login"}];
  $tool_mkdir = &gst_file_locate_tool ("mkdir");

  if ($gst_dist =~ /^freebsd/)
  {
    my $pwdpipe;
    my $home;

    # FreeBSD doesn't create the home directory
    $home = $$data[$users_prop_map{"home"}];
    &gst_file_run ("$tool_mkdir -p $home");

    $command = "$cmd_pw useradd " .
     " -n \'" . $$data[$users_prop_map{"login"}] . "\'" .
     " -u \'" . $$data[$users_prop_map{"uid"}] . "\'" .
     " -d \'" . $$data[$users_prop_map{"home"}] . "\'" .
     " -g \'" . $$data[$users_prop_map{"gid"}] . "\'" .
     " -s \'" . $$data[$users_prop_map{"shell"}] . "\'" .
     " -H 0"; # pw(8) reads password from STDIN

    $pwdpipe = &gst_file_run_pipe($command, $GST_FILE_WRITE);
    print $pwdpipe $$data[$users_prop_map{"password"}];
    &gst_file_close ($pwdpipe);
  }
  else
  {
    $home_parents = $$data[$users_prop_map{"home"}];
    $home_parents =~ s/\/+[^\/]+\/*$//;
    &gst_file_run ("$tool_mkdir -p $home_parents");

    $command = "$cmd_useradd" . " -d \'" . $$data[$users_prop_map{"home"}] .
     "\' -g \'"    . $$data[$users_prop_map{"gid"}] .
     "\' -m -p \'" . $$data[$users_prop_map{"password"}] .
     "\' -s \'"    . $$data[$users_prop_map{"shell"}] .
     "\' -u \'"    . $$data[$users_prop_map{"uid"}] .
     "\' \'"       . $$data[$users_prop_map{"login"}] . "\'";
    &gst_file_run ($command);
  }

  &change_user_chfn (undef, $$data[$users_prop_map{"comment"}], $$data[$users_prop_map{"login"}]);
}

sub change_user
{
  my ($old_data, $new_data) = @_;
	
  if ($gst_dist =~ /^freebsd/)
  {
    my $pwdpipe;

    $command = "$cmd_pw usermod \'" . $$old_data[$users_prop_map{"login"}] . "\'" .
     " -l \'" . $$new_data[$users_prop_map{"login"}] . "\'" .
     " -u \'" . $$new_data[$users_prop_map{"uid"}]   . "\'" .
     " -d \'" . $$new_data[$users_prop_map{"home"}]  . "\'" .
     " -g \'" . $$new_data[$users_prop_map{"gid"}]   . "\'" .
     " -s \'" . $$new_data[$users_prop_map{"shell"}] . "\'" .
     " -H 0"; # pw(8) reads password from STDIN

    $pwdpipe = &gst_file_run_pipe($command, $GST_FILE_WRITE);
    print $pwdpipe $$data[$users_prop_map{"password"}];
    &gst_file_close ($pwdpipe);
  }
  else
  {
    $command = "$cmd_usermod" . " -d \'" . $$new_data[$users_prop_map{"home"}] .
     "\' -g \'" . $$new_data[$users_prop_map{"gid"}] .
     "\' -l \'" . $$new_data[$users_prop_map{"login"}] .
     "\' -p \'" . $$new_data[$users_prop_map{"password"}] .
     "\' -s \'" . $$new_data[$users_prop_map{"shell"}] .
     "\' -u \'" . $$new_data[$users_prop_map{"uid"}] .
     "\' \'" . $$old_data[$users_prop_map{"login"}] . "\'";
    &gst_file_run ($command);
  }

  &change_user_chfn ($$old_data[$users_prop_map{"comment"}],
                     $$new_data[$users_prop_map{"comment"}],
                     $$new_data[$users_prop_map{"login"}]);
}

sub del_group
{
  my ($data) = @_;

  if ($gst_dist =~ /^freebsd/)
  {
    $command = "$cmd_pw groupdel -n \'" . $$data[$groups_prop_map{"name"}] . "\'";
  }
  else
  {
    $command = "$cmd_groupdel \'" . $$data[$groups_prop_map{"name"}] . "\'";
  }
  &gst_file_run ($command);
}

sub add_group
{
  my ($data) = @_;
  my ($u, $user, $users);

  $u = [ @{$$data[$groups_prop_map{"users"}]} ]; sort @$u;
	
  if ($gst_dist =~ /^freebsd/)
  {
    $users = join (",", @$u);
      
    $command = "$cmd_pw groupadd -n \'" . $$data[$groups_prop_map{"name"}] .
      "\' -g \'" . $$data[$groups_prop_map{"gid"}] .
      "\' -M \'" . $users . "\'";
    &gst_file_run ($command);
  }
  else
  {
    $command = "$cmd_groupadd -g \'" . $$data[$groups_prop_map{"gid"}] .
      "\' " . $$data[$groups_prop_map{"name"}];
    &gst_file_run ($command);

    foreach $user (@$u)
    {
      $command = "$cmd_gpasswd -a \'" . $user .
        "\' " . $$data[$groups_prop_map{"name"}];
      &gst_file_run ($command);
    }
  }
}

sub change_group
{
	my ($old_data, $new_data) = @_;
	my ($n, $o, $users, $i, $j, $max_n, $max_o, $r, @tmp); # for iterations

  if ($gst_dist =~ /^freebsd/)
  {
    $n = [ @{$$new_data[$groups_prop_map{"users"}]} ]; sort @$n;
    $users = join (",", @$n);

    $command = "$cmd_pw groupmod -n \'" . $$old_data[$groups_prop_map{"name"}] .
        "\' -g \'" . $$new_data[$groups_prop_map{"gid"}] .
        "\' -l \'" . $$new_data[$groups_prop_map{"name"}] .
        "\' -M \'" . $users . "\'";

    &gst_file_run ($command);
  }
  else
  {
    $command = "$cmd_groupmod -g \'" . $$new_data[$groups_prop_map{"gid"}] .
      "\' -n \'" . $$new_data[$groups_prop_map{"name"}] . "\' " .
      "\'" . $$old_data[$groups_prop_map{"name"}] . "\'";
  
    &gst_file_run ($command);
	
    # Let's see if the users that compose the group have changed.
    if (&arr_cmp_recurse ($$new_data[$groups_prop_map{"users"}],
                          $$old_data[$groups_prop_map{"users"}])) {

      $n = [ @{$$new_data[$groups_prop_map{"users"}]} ]; sort @$n;
      $o = [ @{$$old_data[$groups_prop_map{"users"}]} ]; sort @$o;
		
      $max_n = $#$n;
      $max_o = $#$o;
      for ($i = 0, $j = 0; $i <= &max ($max_n, $max_o); ) {
        $r = $$n[$i] cmp $$o[$j];
        $r *= -1 if (($$o[$j] eq "") || ($$n[$i] eq ""));

        if ($r < 0) { # add this user to the group.
          $command = "$cmd_gpasswd -a \'" . $$n[$i] . "\' \'" . 
            $$new_data[$groups_prop_map{"name"}] . "\'";
          $i ++;
				
          &gst_file_run ($command);
			  } elsif ($r > 0) { # delete the user from the group.
          $command = "$cmd_gpasswd -d \'" . $$o[$j] . "\' \'" . 
            $$new_data[$groups_prop_map{"name"}] . "\'";
          $j ++;
				
          &gst_file_run ($command);
			  } else { # The information is the same. Go to next tuple.
          $i ++; $j ++;
			  }	
		  }	
	  }
  }
}


sub write_logindefs
{
  my ($login_defs) = @_;
  my ($key);
  my $file;

  return unless $login_defs;

  foreach $key (@login_defs_names)
  {
    if (-e $key)
    {
      &gst_debug_print_line ("write_logindefs:$key");
      $file = $key;
      last;
    }
  }

  unless ($file) 
  {
    &gst_report ("file_open_read_failed", join (", ", @login_defs_names));
    return;
  }

  foreach $key (keys (%$login_defs))
  {
    # Write ONLY login.defs values.
    if (exists ($login_defs_prop_map{$key}))
    {
      &gst_replace_split ($file, $login_defs_prop_map{$key}, "[ \t]+", $$login_defs{$key});
    }
  }
}


sub write_profiledb
{
  my ($hash) = @_;
  my $profiledb = $hash->{'profiledb'};

  unless ($profiledb)
  {
    &gst_report ('users_write_profiledb_fail');
    return;
  }

  # Update login.defs file.  
  &write_logindefs (&get_logindefs ($profiledb));

  # Write our profiles.
  my $path = &gst_file_get_data_path () . "/" . $main::tool->{'name'} . "/" . $profile_file;
  my $fh = &gst_file_open_write_from_names ($path);
  if ($fh)
  {
    local *STDOUT = $fh;
    &xml_print_profiledb ($hash);
    close ($fh);
    &gst_report ('users_write_profiledb_success');
  }
  else
  {
    &gst_report ('users_write_profiledb_fail');
  }
}


# --- XML parsing --- #

# Scan XML from standard input to an internal tree.

sub xml_parse
{
  my ($tool) = @_;
  my ($tree, %hash);
	
  # Scan XML to tree.

  $tree = &gst_xml_scan (undef, $tool);

  $hash{"users"} = [];
  $hash{"users_hash"} = {};
  $hash{"groups"} = [];
  $hash{"groups_hash"} = {};
  $hash{"profiledb"} = [];

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

  while (@$tree)
  {
    if ($$tree[0] eq "users") { &xml_parse_users($$tree[1], \%hash); }

    shift @$tree;
    shift @$tree;
  }

  return (\%hash);
}

sub xml_parse_users
{
  my ($tree, $hash) = @_;
	
  shift @$tree;  # Skip attributes.

  while (@$tree)
	{
	  if ($$tree[0] eq "logindefs") { &xml_parse_login_defs ($$tree[1], $hash); }
		elsif ($$tree[0] eq "passwd_last_modified") { &xml_parse_passwd_last_modified ($$tree[1], $hash); }
		elsif ($$tree[0] eq "group_last_modified") { &xml_parse_group_last_modified ($$tree[1], $hash); }
		elsif ($$tree[0] eq "userdb") { &xml_parse_userdb ($$tree[1], $hash); }
		elsif ($$tree[0] eq "groupdb") { &xml_parse_groupdb ($$tree[1], $hash); }
		elsif ($$tree[0] eq "shelldb")  { }
		elsif ($$tree[0] eq "profiledb")  { &xml_parse_profiledb ($$tree[1], $hash); }
		else
		{
		  &gst_report ("xml_unexp_tag", $$tree[0]);
		}

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

sub xml_parse_passwd_last_modified
{
  my ($tree, $hash) = @_;
	
  shift @$tree;  # Skip attributes.
	
	&gst_report ("xml_unexp_arg", "", "passwd_last_modified") if ($$tree[0] ne "0");
	$$hash{"passwd_last_modified"} = $$tree[1];
}

sub xml_parse_group_last_modified
{
  my ($tree, $hash) = @_;
	
  shift @$tree;  # Skip attributes.
	
	&gst_report ("xml_unexp_arg", "", "group_last_modified") if ($$tree[0] ne "0");
	$$hash{"group_last_modified"} = $$tree[1];
}	

sub xml_parse_userdb
{
  my ($tree, $hash) = @_;
	
  shift @$tree;  # Skip attributes.

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

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

sub xml_parse_user
{
  my ($tree, $hash) = @_;
  my ($users, $users_hash);
  my @line = ();

  $users = $$hash{"users"};
  $users_hash = $$hash{"users_hash"};
	
  shift @$tree;  # Skip attributes.

	while (@$tree)
	{
		if ($users_prop_map{$$tree[0]} ne undef)
		{
		  $line[$users_prop_map{$$tree[0]}] = &gst_xml_unquote($$tree[1][2]);
		}
		else
		{
		  &gst_report ("xml_unexp_tag", $$tree[0]);
		}
		
		shift @$tree;
		shift @$tree;
	}

  $$users_hash{sprintf ("%06d", $line[0])} = [@line];
  push (@$users, [@line]);
}	
	
sub xml_parse_groupdb
{
  my ($tree, $hash) = @_;
  my $tree = $_[0];
	
  shift @$tree;  # Skip attributes.

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

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

sub xml_parse_group
{
  my ($tree, $hash) = @_;
	my (@line, $copy, $a, @u);
  my ($groups, $users_hash);
	
  $groups = $$hash{"groups"};
  $groups_hash = $$hash{"groups_hash"};
	
  shift @$tree;  # Skip attributes.

	while (@$tree)
	{
		if ($groups_prop_map{$$tree[0]} ne undef)
		{
		  if ($$tree[0] eq "users") { $line[$groups_prop_map{$$tree[0]}] = $$tree[1]; }
			else { $line[$groups_prop_map{$$tree[0]}] = $$tree[1][2]; }
		}
		else
		{
		  &gst_report ("xml_unexp_tag", $$tree[0]);
		}
		
		shift @$tree;
		shift @$tree;
	}
	
	# @$a should be a parse tree of the array of users.
	$a = pop @line;
	shift @$a;
	while (@$a) {
	  if ($$a[0] eq "user") {
		  push @u, $$a[1][2];
		}
		else
		{
		  &gst_report ("xml_unexp_tag", $$tree[0]);
		}
		shift @$a;
		shift @$a;
	}
	
	push @line, [@u];
	$copy = [@line];
	$$groups_hash{sprintf ("%06d", $line[0])} = $copy;
	push (@$groups, $copy);
}

sub xml_parse_profile_groups
{
  my ($tree) = @_;
  my ($arr);

  shift @$tree; # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "group")
    {
      push @$arr, $$tree[1][2];
    }
    else
    {
      &gst_report ("xml_unexp_tag", $$tree[0]);
    }

    shift @$tree;
    shift @$tree;
  }

  return $arr;
}

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

  shift @$tree;  # Skip attributes.

  while (@$tree)
	{
    # The "default" tag is not in the map, but we need to parse it
		if ($profiles_prop_map{$$tree[0]} || $$tree[0] eq "default")
		{
		  $profile{$$tree[0]} = $$tree[1][2];
		}
    elsif ($$tree[0] eq "groups")
    {
      $profile{$$tree[0]} = &xml_parse_profile_groups ($$tree[1]);
    }
		elsif ($$tree[0] ne "files") # files tag is ignored for parsing. # FIXME!
    {
		  &gst_report ("xml_unexp_tag", $$tree[0]);
		}

    shift @$tree;
		shift @$tree;
	}

  push @{$hash->{'profiledb'}}, \%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_profiledb
{
  my ($hash) = @_;

  my $profiledb = $$hash{"profiledb"};

  return unless scalar @$profiledb;

  &gst_xml_container_enter ('profiledb');

  foreach my $profile (@$profiledb)
  {
    my $key;
    &gst_xml_container_enter ('profile');
    foreach $key (keys %$profile)
    {
      if ($key eq "groups")
      {
        &gst_xml_container_enter ('groups');
        &gst_xml_print_array ($profile->{$key}, "group");
        &gst_xml_container_leave ('groups');
      }
      else
      {
        &gst_xml_print_pcdata ($key, $profile->{$key});
      }
    }
    &gst_xml_container_leave ();
  }

  &gst_xml_container_leave ();
	&gst_xml_print_vspace ();
}

sub xml_print_shells
{
  my ($hash) = @_;
  my ($i, $shells);

  $shells = $$hash{"shelldb"};
  return unless scalar @$shells;

  &gst_xml_container_enter ('shelldb');

  foreach $i (@$shells) {
    &gst_xml_print_pcdata ('shell', $i);
  }

  &gst_xml_container_leave ();
	&gst_xml_print_vspace ();
}

sub xml_print
{
  my ($hash) = @_;
  my ($key, $value, $i, $j, $k);
  my ($passwd_last_modified, $users, $desc);

  $passwd_last_modified = $$hash{"passwd_last_modified"};
  $users = $$hash{"users"};
  $group_last_modified = $$hash{"group_last_modified"};
  $groups = $$hash{"groups"};

  &gst_xml_print_begin ();

  &gst_xml_print_pcdata ("use_md5", $$hash{"use_md5"});

  &gst_xml_print_vspace ();
  &gst_xml_print_comment ('Profiles configuration starts here');
  &gst_xml_print_vspace ();

  &xml_print_profiledb ($hash);
  &xml_print_shells ($hash);

  &gst_xml_print_comment ('Now the users');
  &gst_xml_print_vspace ();

  &gst_xml_print_comment ('When was the passwd file last modified (since the epoch)?');
  &gst_xml_print_vspace ();
  &gst_xml_print_pcdata ('passwd_last_modified', $passwd_last_modified);
  &gst_xml_print_vspace ();

  &gst_xml_container_enter ('userdb');
	foreach $i (@$users)
	{
    &gst_xml_print_vspace ();
	  &gst_xml_container_enter ('user');
		for ($j = 0; $j < ($#users_prop_array - 1) / 2; $j++)
    {
      &gst_xml_print_pcdata ($users_prop_map{$j}, $$i[$j]);
		}
		&gst_xml_container_leave ();
	}
	&gst_xml_container_leave ();
  &gst_xml_print_vspace ();
	
  &gst_xml_print_comment ('Now the groups');
  &gst_xml_print_vspace ();
	
  &gst_xml_print_comment ('When was the group file last modified (since the epoch)?');
  &gst_xml_print_vspace ();
  &gst_xml_print_pcdata ('group_last_modified', $group_last_modified);
  &gst_xml_print_vspace ();
	
	&gst_xml_container_enter ('groupdb');
	foreach $i (@$groups)
	{
    &gst_xml_print_vspace ();
	  &gst_xml_container_enter ('group');
		for ($j = 0; $j < ($#groups_prop_array - 1) / 2 - 1; $j++)
    {
      &gst_xml_print_pcdata ($groups_prop_map{$j}, $$i[$j]);
		}

		# Add the description based on the group name
		$desc = $groups_desc_map{$$i[1]};
		&gst_xml_print_pcdata ("allows_to", $desc) if ($desc ne undef);

		&gst_xml_container_enter ('users');
		$k = $$i[$groups_prop_map{"users"}];
		foreach $j (@$k)
		{
			&gst_xml_print_pcdata ('user', $j);
		}
		&gst_xml_container_leave ();

		&gst_xml_container_leave ();
	}
	&gst_xml_container_leave ();
  &gst_xml_print_vspace ();

  &gst_xml_print_end ();
}


# --- Get (read) config --- #

sub get
{
  my ($tool) = @_;
  my ($hash);
  
  $hash = &read ();
  &gst_report_end ();
  &xml_print ($hash);
}

sub set
{
  my ($tool) = @_;
  my ($hash);

  $hash = &xml_parse ($tool);

  if ($hash)
  {
    # Make backup manually, otherwise they don't get backed up.
    &gst_file_backup ($_) foreach (@passwd_names);
    &gst_file_backup ($_) foreach (@shadow_names);
    &gst_file_backup ($_) foreach (@group_names);

    &write_profiledb ($hash);
    &write_group_passwd ($hash);
  }

  &gst_report_end ();
}


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


sub filter
{
  my ($tool) = @_;
  my ($hash);
  
  $hash = &xml_parse ($tool);
  &gst_report_end ();
  &xml_print ($hash);
}


# --- 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, [], "" ]
    };

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

&get_login_defs_prop_array ();
&get_profiles_prop_array   ();

&gst_run ($tool);
