#!/usr/pkg/bin/perl
# set-config - Set a Webmin configuration option to the value provided and 
# restart Webmin to apply the change.
use strict;
use warnings;
BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Color'; }
use 5.010; # Version in CentOS 6

use Getopt::Long qw(:config permute pass_through);
use Pod::Usage;
use Term::ANSIColor qw(:constants);
use Fcntl qw( :flock );

sub main {
    my %opt;
    GetOptions(
        'help|h' => \$opt{'help'},
        'config|c=s' => \$opt{'config'},
        'module|m=s' => \$opt{'module'},
        'option|o=s' => \$opt{'option'},
        'value|v=s' => \$opt{'value'},
        'force|f' => \$opt{'force'}
    );
    pod2usage(0) if ( $opt{'help'} );

    $opt{'config'} ||= "/etc/webmin";

    set_config( \%opt );
    
    return 0;
}
exit main( \@ARGV ) if !caller(0);

sub set_config {
    my ($optref) = @_;
    my $key = $optref->{'option'};
    my $value = $optref->{'value'};
    $key or die RED, "An --option must be specified", RESET;

    my @config_lines;
    # Module or root-level config?
    my $config_file;
    if ($optref->{'module'}) {
        $config_file = "$optref->{'config'}/$optref->{'module'}/config";
    } else {
        $config_file = "$optref->{'config'}/miniserv.conf";
    }
    # Read'em
    open my $fh, '+<', $config_file
      or die RED, "Unable to open $config_file", RESET;
    flock($fh, LOCK_EX) or die RED, "Unable to lock $config_file", RESET;
    chomp(@config_lines = <$fh>);
    # Change'em
    my $found = 0;
    my $exit_code = 0;
    # Validate it against the config.info if this is a module and
    if ($optref->{'module'} && !($optref->{'force'})) {
        validate_config_option($optref);
    }
    for (@config_lines) {
        if (/^${key}=(.*)/) {
            s/^${key}=(.*)/${key}=${value}/;
            $found++;
        }
    }
    unless ($found > 0) {
        push(@config_lines, "$key=$value");
        $exit_code++;
    }
    # Write'em
    seek($fh, 0, 0);
    print $fh qq|$_\n| for @config_lines;
    close $fh;

    # Restart Webmin if editing miniserv.conf
    unless ($optref->{'module'}) {
        say "Restarting Webmin to apply miniserv.conf changes...";
        system("$optref->{'config'}/restart");
    }
    exit $exit_code;
}

sub validate_config_option {
    my ($optref) = @_;

    my $root = root($optref->{'config'});
    my $key = $optref->{'option'};

    # Load the config.info
    open my $fh, '<', "$root/$optref->{'module'}/config.info";
    # Does this key exist?
    my $found;
    while (<$fh>) {
        if (/^${key}=(.*)/) {
            $found++;
        }
    }
    close $fh;
    $found or
      die RED, "Option '$key' is unknown in module $optref->{'module'}", RESET;
    return 1;
}

sub root {
    my ($config) = @_;
    open(my $CONF, "<", "$config/miniserv.conf") || die RED,
        "Failed to open $config/miniserv.conf", RESET;
    my $root;
    while (<$CONF>) {
        if (/^root=(.*)/) {
            $root = $1;
        }
    }
    close($CONF);
    # Does the Webmin root exist?
    if ( $root ) {
        die "$root is not a directory. Is --config correct?" unless (-d $root);
    } else {
        die "Unable to determine Webmin installation directory from $ENV{'WEBMIN_CONFIG'}";
    }

    return $root;
}

1;

=pod

=head1 NAME

set-config

=head1 DESCRIPTION

Set a configuration directive in either C<miniserv.conf> (the core Webmin config) or in the specified module C<config>.

=head1 SYNOPSIS

set-config [options] [--module] --option <option-name> --value <value>

=head1 OPTIONS

=over

=item --help, -h

Print this usage summary and exit.

=item --config, -c

Specify the full path to the Webmin configuration directory. Defaults to
C</etc/webmin>

=item --module, -m

Specify which module configuration to modify. If none given, configuration will be assumed to be the Webmin core configuration (/etc/webmin/miniserv.conf).

=item --option, -o

Specify the option to change.

=item --value, -v

The value to change the option to.

=item --force, -f

Skip validation of the option name. Allows modifying hidden options, and adding unknown options.

=back

=head1 EXIT CODES

0 on successfully replacing a config variable

1 on successfully adding a new config variable (the specified option did not
already exist in the file, and was added)

>1 on error

=head1 LICENSE AND COPYRIGHT

Copyright 2018 Jamie Cameron <jcameron@webmin.com>, Joe Cooper
<joe@virtualmin.com>.

