#! /usr/bin/perl
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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
# General Public License for more details.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc.
# All Rights Reserved.
# **** End License ****

use lib "/opt/vyatta/share/perl5/";
use Vyatta::Config;
use strict;
use Getopt::Long;

my ( $resync, $verbose, $force, $check );

GetOptions(
    "verbose" => \$verbose,
    "resync"  => \$resync,
    "force"   => \$force,
    "check"   => \$check,
);

# NOTE: the directory manipulations were changed to simply using "my_set"
#       and "my_delete" since the new CLI library no longer expose the
#       filesystem directories/paths. this should provide the same
#       functionality as the original script, so the original limitations
#       still apply:
#       1. the script is supposed to be run manually in config mode.
#       2. when necessary (given the configurations and options), the
#          script adds/modifies users in the "working config" (errors
#          not checked).
#       3. after running this script, a "commit" will need to be issued
#          before any changes will take effect.
sub set_user_attr {
  my $user = shift;
  system('/opt/vyatta/sbin/my_set', 'system', 'login', 'user', $user, @_);
}

sub del_user {
  my $user = shift;
  system('/opt/vyatta/sbin/my_delete', 'system', 'login', 'user', $user);
}

my $members;
( undef, undef, undef, $members ) = getgrnam('operator');
my @operators = split(/ /, $members );

( undef, undef, undef, $members ) = getgrnam('vyattacfg');
my @admins = split(/ /, $members );

sub get_user_level {
    my $name = shift;

    return 'admin' if ( $name eq 'root' );

    foreach my $id (@admins) {
        return 'admin' if ( $id eq $name );
    }

    foreach my $id (@operators) {
        return 'operator' if ( $id eq $name );
    }

    # If level indetermined returns undef
}

my @field_names =
  ( 'encrypted password', 'full name', 'home directory', 'level' );

sub system_vyatta_users {
    my %users = ();
    setpwent();
    while (
        my ( $name, $passwd, $uid, $gid, undef, $comment, undef, $home, $shell )
        = getpwent() )
    {
        if ( $name eq 'root' || $shell eq '/bin/vbash' ) {
            $users{$name} = [ $passwd, $comment, $home, get_user_level($name) ];
        }
    }
    endpwent();

    return %users;
}

sub listOrigUsers {
    my $config = new Vyatta::Config;
    my %users  = ();

    foreach my $name ( $config->listOrigNodes('system login user') ) {
        $config->setLevel("system login user $name");

        my $passwd =
          $config->returnOrigValue('authentication encrypted-password');
        my $comment = $config->returnOrigValue('full-name');
        my $home    = $config->returnOrigValue('home-directory');
        my $level   = $config->returnOrigValue('level');
        $level = 'admin' if ( !defined $level );

        $users{$name} = [ $passwd, $comment, $home, $level ];
    }

    return %users;
}

sub check_config {
    my %pwdusers  = system_vyatta_users();
    my %vtyusers  = listOrigUsers();
    my $exit_code = 0;

    if ($verbose) {
        printf "System users: %s\n",     join( ', ', keys %pwdusers );
        printf "Configured users: %s\n", join( ', ', keys %vtyusers );
    }

    while ( my ( $user, $fields ) = each(%vtyusers) ) {
        my @pwd_fields = @{ $pwdusers{$user} };
        my @cfg_fields = @$fields;

        if (@pwd_fields) {
            for ( my $i = 0 ; $i <= $#pwd_fields ; $i++ ) {
                if ( $pwd_fields[$i] ne $cfg_fields[$i] ) {
                    printf "%s: %s mismatch: '%s' != '%s'\n", $user,
                      $field_names[$i], $pwd_fields[$i], $cfg_fields[$i];
                    $exit_code = 1;
                }
            }
            delete $pwdusers{$user};
        }
        else {
            print "$user: does not exist in system\n";
            $exit_code = 1;
        }
    }

    foreach my $user ( keys %pwdusers ) {
        print "$user: does not exist in vyatta configuration\n";
        $exit_code = 1;
    }

    exit $exit_code;
}

sub listUsers {
    my $config = new Vyatta::Config;
    my %users  = ();

    foreach my $name ( $config->listOrigNodes('system login user') ) {
        $config->setLevel("system login user $name");

        my $passwd =
          $config->returnOrigValue('authentication encrypted-password');
        my $comment = $config->returnOrigValue('full-name');
        my $home    = $config->returnOrigValue('home-directory');
        my $level   = $config->returnOrigValue('level');
        $level = 'admin' if ( !defined $level );

        $users{$name} = [ $passwd, $comment, $home, $level ];
    }

    return %users;
}

sub resync_config {
    my %system_users = system_vyatta_users();
    my %vyatta_users = listUsers();
    my $config = new Vyatta::Config;

    $config->setLevel('system login user');

    foreach my $user ( keys %vyatta_users ) {
        if ( !defined $system_users{$user} ) {
            if ($force) {
                print "Deleting user: $user\n" if ($verbose);
                del_user($user);
            }
            else {
                print "user: $user does not exist in passwd file\n";
            }
        }
    }

    foreach my $user ( keys %system_users ) {
        my ( $passwd, $comment, $home, $level ) = @{ $system_users{$user} };

        if ( !defined $level ) {
            print "user $user: could not determine level (incorrect groups)\n";
            next;
        }

        my $existing = $vyatta_users{$user};
        if ( !defined $existing ) {
            if ($force) {
                print "Adding $user\n" if ($verbose);
                # user will be added in later "set" operations.
            }
            else {
                print "user: $user does not exist in vyatta config\n";
                next;
            }
        }
        else {
            my ( $opasswd, $ocomment, $ohome, $olevel ) = @{$existing};
            if (   $opasswd eq $passwd
                && $ocomment eq $comment
                && $ohome    eq $home
                && $olevel   eq $level ) {
	    	print "$user: no change\n" if ($verbose);
                next;
            }
	    else {
		print "$user: fields don't match\n" if ($verbose);
	    }
	}

        if ( $comment ne '' ) {
            set_user_attr($user, 'full-name', $comment);
        }

        set_user_attr($user, 'authentication', 'encrypted-password', $passwd);
        set_user_attr($user, 'level', $level);
        set_user_attr($user, 'home-directory', $home);
    }
}

if ($check) {
    check_config();
    exit 0;
}

if ($resync) {
    resync_config();
    exit 0;
}

print <<EOF;
usage: vyatta-passwd-sync.pl [--verbose ] [--force ] --resync
       vyatta-passwd-sync.pl [--verbose ] --check
EOF

exit 1;