#!/usr/local/bin/perl
# ==================================================================
# Gossamer Links - enhanced directory management system
#
#   Website  : http://gossamer-threads.com/
#   Support  : http://gossamer-threads.com/scripts/support/
#   CVS Info : 087,071,086,086,085      
#   Revision : $Id: setup.cgi,v 1.89 2009/05/08 19:56:50 brewt Exp $
#
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================

use strict;
use lib '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin';
use Links qw/$IN $CFG $DB %STASH/;
use Links::SQL;

$| = 1;
local $SIG{__DIE__} = \&Links::fatal;
Links::init('/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin');
Links::init_admin();

main();

sub main {
# ------------------------------------------------------------------
# Main admin loop, displays html pages and other admin tasks.
#

# Make sure we are only run from the web.
    if (! defined $ENV{REQUEST_METHOD}) {
        print "\nThis script can only be accessed from your browser.\n\n";
        if ($CFG->{admin_root_url}) {
            print "Try visiting:\n\t$CFG->{admin_root_url}/setup.cgi\n\n";
        }
        return;
    }

# If we don't have anything to do, and aren't setup yet, go to setup_first template.
    if (! $IN->param('do') and ! $CFG->{setup}) {
        $IN->param('do', 'page');
        $IN->param('page', 'setup_first.html');
    }

# If we can't find the admin templates, perhaps the path is screwed, try to
# reset it.
    if (! -e "$CFG->{admin_root_path}/templates/admin") {
        die 'Configured admin path does not appear to be valid!';
    }
# Otherwise do the command or display the setup frameset.
    my $action = $IN->param('do') || 'disp_home';
    no strict 'refs';   my %subs = %{__PACKAGE__ . "::"}; use strict 'refs';

    if (exists $subs{$action}) {
        $subs{$action}->();
    }
    elsif ($action eq 'page') {
        Links::admin_page();
    }
    else {
        die "Invalid Request: '$action'";
    }
}

sub setup_sql {
# ------------------------------------------------------------------
# Change the sql server information.
#
    my ($host, $port, $output, $action, $ret);

    $action = $IN->param('action');
    print $IN->header();
    if ($action !~ /^create|overwrite|load$/) {
        return Links::admin_page('setup_sql.html', [ $IN, { error => "Invalid action: '$action'" }]);
    }

    $host = $IN->param('host');
    ($host =~ s/\:(\d+)$//) and ($port = $1);

    my $prefix = $IN->param('prefix');
    $prefix =~ /^\w*$/ or return Links::admin_page('setup_sql.html', [ $IN, { error => "Invalid prefix: '$prefix'. Can only be letters, numbers and underscore." } ]);

    $DB->prefix($prefix);
    $ret = $DB->set_connect({
        driver     => scalar $IN->param('driver'),
        host       => $host,
        port       => $port,
        database   => scalar $IN->param('database'),
        login      => scalar $IN->param('login'),
        password   => scalar $IN->param('password'),
        RaiseError => 0,
        PrintError => 0
    });
    if (! defined $ret) {
        return Links::admin_page('setup_sql.html', [$IN, { error => $GT::SQL::error }]);
    }
    if ($action eq 'create') {
        $output = Links::SQL::tables('check');
    }
    elsif ($action eq 'overwrite') {
        $output = Links::SQL::tables('force');
# Create the admin user.
        my $db   = $DB->table('Users');
        my $pass = join '', map { chr(65 + rand 57) } 1 .. 8;
        $db->insert({ Username => 'admin', Password => $pass, Email => $CFG->{db_admin_email}, ReceiveMail => 'No', Status => 'Administrator' });
    }
    elsif ($action eq 'load') {
        $output = Links::SQL::load_from_sql();
    }
    Links::admin_page('setup_sql.html', [$IN, { message_pre => $output }]);
}

sub setup_path {
# ------------------------------------------------------------------
# Set the path information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_path(0);
    }
    else {
        _update_cfg();
        if ($IN->param('update_others')) {
            $CFG->{build_images_url}    = "$CFG->{build_root_url}/images";
            $CFG->{build_css_url}       = "$CFG->{build_root_url}/links.css";
            $CFG->{build_new_path}      = "$CFG->{build_root_path}/New";
            $CFG->{build_new_url}       = "$CFG->{build_root_url}/New";
            $CFG->{build_cool_path}     = "$CFG->{build_root_path}/Cool";
            $CFG->{build_cool_url}      = "$CFG->{build_root_url}/Cool";
            $CFG->{build_ratings_path}  = "$CFG->{build_root_path}/Ratings";
            $CFG->{build_ratings_url}   = "$CFG->{build_root_url}/Ratings";
            $CFG->{build_detail_path}   = "$CFG->{build_root_path}/Detailed";
            $CFG->{build_detail_url}    = "$CFG->{build_root_url}/Detailed";
        }
    }
    $CFG->save();
    Links::admin_page('setup_path.html', [$IN, { message => "All paths and URL's have been updated successfully." }]);
}

sub setup_build {
# ------------------------------------------------------------------
# Set the build information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_build(1);
    }
    else {
        _update_cfg();
    }

    $CFG->save();
    Links::admin_page('setup_build.html', [$IN, { message => "All build options have been updated successfully." }]);
}

sub setup_user {
# ------------------------------------------------------------------
# Set the user information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_user(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();
    Links::admin_page('setup_user.html', [$IN, { message => "All user options have been updated successfully." }]);
}

sub setup_email {
# ------------------------------------------------------------------
# Set the email information.
#
    print $IN->header();

    if ($IN->param('db_mail_path') and $IN->param('db_smtp_server')) {
        Links::admin_page('setup_email.html', [$IN, { message => "You can not specify both an SMTP server and a path to sendmail!" }]);
        return;
    }

    if ($IN->param('reset_defaults')) {
        $CFG->default_email(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();
    Links::admin_page('setup_email.html', [$IN, { message => "All email options have been updated successfully." }]);
}

sub setup_search {
# ------------------------------------------------------------------
# Set the email information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_search(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();
    Links::admin_page('setup_search.html', [$IN, { message => "All search options have been updated successfully." }]);
}

sub setup_review {
# ------------------------------------------------------------------
# Set the review information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_review(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();
    Links::admin_page('setup_review.html', [$IN, { message => "All review options have been updated successfully. " }]);
}

sub setup_date {
# ------------------------------------------------------------------
# Set the date information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_date(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();

# Reload the date module.
    delete $STASH{date_loaded};
    Links::init_date;
    Links::admin_page('setup_date.html', [$IN, { message => "All date options have been updated successfully." }]);
}

sub setup_misc {
# ------------------------------------------------------------------
# Set the misc information.
#
    print $IN->header();

    if ($IN->param('reset_defaults')) {
        $CFG->default_misc(1);
    }
    else {
        _update_cfg();
    }
    $CFG->save();
    Links::admin_page('setup_misc.html', [$IN, { message => "All misc options have been updated successfully." }]);
}

sub setup_pass {
# ------------------------------------------------------------------
# Creates the .htaccess/.htpasswd file.
#
    print $IN->header();

    my $htpasswd = $CFG->{admin_root_path} . "/.htpasswd";
    my $htaccess = $CFG->{admin_root_path} . "/.htaccess";
    unless (-w $htaccess and -w $htpasswd) {
        Links::admin_page('setup_pass.html', [$IN, { error => "Sorry, but we don't have write access to the htaccess files: '$htaccess' and '$htpasswd'." }]);
        return;
    }

    my $username  = $IN->param('admin_username');
    my $password  = $IN->param('admin_password');
    my $password2 = $IN->param('admin_password_confirm');
    my $to_delete = $IN->param('delete') ? $IN->param('delete_user') : $username;

    if ($password and $password ne $password2) {
        Links::admin_page('setup_pass.html', [$IN, { error => "Your passwords do not match." }]);
        return;
    }

    my $fh = \do { local *FH; *FH };
    open $fh, "< $htpasswd" or die "Unable to open '$htpasswd': $!";
    my @lines = <$fh>;
    close $fh;
    @lines = grep ! /^\Q$to_delete\E:/, @lines if $to_delete;

    if ($username and $password) {
        require GT::MD5::Crypt;
        my $salt = join '', ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/')[map rand 64, 1 .. 8];
        my $crypted = GT::MD5::Crypt::apache_md5_crypt($password, $salt);
        push @lines, "$username:$crypted\n";
    }

    if (@lines and -z $htaccess) {
        _create_htaccess($htaccess, $htpasswd);
    }
    elsif (!@lines and -s $htaccess) {
        open $fh, "> $htaccess";
        close $fh;
    }

    open $fh, "> $htpasswd" or die "Unable to open '$htpasswd': $!";
    print $fh @lines if @lines;
    close $fh;

    Links::admin_page('setup_pass.html', [$IN, { message => "Your directory is now password protected. The next screen you visit, you should be prompted for a password." } ]);
}

sub _create_htaccess {
# ------------------------------------------------------------------
# Creates the htaccess file.
#
    my ($htaccess, $htpasswd) = @_;
    open HTAC, "> $htaccess" or die "Unable to open '$htaccess': $!";
    print HTAC <<HTACCESS;
AuthUserFile   $htpasswd
AuthGroupFile  /dev/null
AuthType       Basic
AuthName       Protected

require valid-user
HTACCESS
    close HTAC;
}

sub init_setup {
# ------------------------------------------------------------------
# Sets the mysql information.
#
    my ($host, $port, $overwrite);
    print $IN->header();

# Test the ability to create a def file.
    unless (open TEST, "> $CFG->{admin_root_path}/defs/database.def") {
        return Links::admin_page('setup_second.html', [$IN, { error => <<HTML }]);
Unable to create our def file in $CFG->{admin_root_path}/defs/. <br />
Please make sure this directory exists, and is writeable by the server. <br />
If this is the wrong directory, you will need to manually set the directory <br />
in Links::Config::Data. Error was: $!
HTML
    }
    close TEST;
    unlink "$CFG->{admin_root_path}/defs/database.def";

# Set the connection info.
    $overwrite = $IN->param('overwrite') ? 'force' : 'check';
    $host = $IN->param('host');
    ($host =~ s/\:(\d+)$//) and ($port = $1);

    my $prefix = $IN->param('prefix');
    $prefix =~ /^\w*$/ or return Links::admin_page('setup_sql.html', [ $IN, { error => "Invalid prefix: '$prefix'. Can only be letters, numbers and underscore." } ]);

    $DB->prefix($prefix);
    my $ret = $DB->set_connect({
        driver     => scalar $IN->param('driver'),
        host       => $host,
        port       => $port,
        database   => scalar $IN->param('database'),
        login      => scalar $IN->param('login'),
        password   => scalar $IN->param('password'),
        RaiseError => 0,
        PrintError => 0
    });
    if (! defined $ret) {
        return Links::admin_page('setup_second.html', [$IN, { error => $GT::SQL::error }]);
    }
# Now let's create the tables.
    eval { local $SIG{__DIE__}; require Links::SQL; };
    if ($@) { return Links::admin_page('setup_second.html', [ $IN, { error => "Unable to load Links::SQL module: $@\n" }]); }

    my $output = Links::SQL::tables($overwrite);

# Update other paths and URL's.
    $CFG->{build_css_url}       = "$CFG->{build_root_url}/links.css";
    $CFG->{build_new_path}      = "$CFG->{build_root_path}/New";
    $CFG->{build_new_url}       = "$CFG->{build_root_url}/New";
    $CFG->{build_cool_path}     = "$CFG->{build_root_path}/Cool";
    $CFG->{build_cool_url}      = "$CFG->{build_root_url}/Cool";
    $CFG->{build_ratings_path}  = "$CFG->{build_root_path}/Ratings";
    $CFG->{build_ratings_url}   = "$CFG->{build_root_url}/Ratings";
    $CFG->{build_detail_path}   = "$CFG->{build_root_path}/Detailed";
    $CFG->{build_detail_url}    = "$CFG->{build_root_url}/Detailed";
    $CFG->{build_images_url}    = "$CFG->{build_root_url}/images";

# Create the admin user.
    my $db   = $DB->table('Users');
    my $pass = $db->random_pass;
    $db->insert({ Username => 'admin', Password => $pass, Email => $CFG->{db_admin_email}, ReceiveMail => 'No', Status => 'Administrator' });

# And lets set sensible defaults for the rest of the config vars.
    $CFG->create_defaults();

# And save the config.
    $CFG->save();
    Links::admin_page('setup_third.html', [ $IN, { message => "The data tables have been setup: <pre>$output</pre>" } ]);
}

sub disp_home {
# ------------------------------------------------------------------
# Display the home page.
#
    print $IN->header();
    Links::admin_page('setup.html', $IN);
}

sub reset_setup {
# ------------------------------------------------------------------
# Sets the cfg->{setup} to 0, and prints out the setup_first page.
#
    print $IN->header();

    $CFG->{setup} = 0;
    $CFG->save;
    Links::admin_page('setup_first.html', $IN);
}

sub _update_cfg {
# ------------------------------------------------------------------
# Updates the config based on the form input.
#
    for my $param ($IN->param) {
        next unless exists $CFG->{$param};
        my $val = $IN->param($param);
        if ($val eq 'custom' and my $custom = $IN->param("${param}_custom")) {
            $CFG->{$param} = $custom;
        }
        elsif (ref $CFG->{$param} eq 'ARRAY') {
            my @val = split /\s*[,\n]\s*/, $val;
            $CFG->{$param} = \@val;
        }
        elsif (ref $CFG->{$param} eq 'HASH') {
            my $h = {};
            my @pairs = split /\s*[,\n]\s*/, $val;
            foreach my $pair (@pairs) {
                my ($k, $v) = split /\s*=>?\s*/, $pair;
                $h->{$k} = $v;
            }
            $CFG->{$param} = $h;
        }
        else {
            $CFG->{$param} = $val;
        }
    }
}