The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mail::Toaster::Setup::Config;
use strict;
use warnings;

our $VERSION = '5.50';

#use Carp;
#use Config;
#use Cwd;
#use Data::Dumper;
#use File::Copy;
#use File::Path;
use English '-no_match_vars';
use Params::Validate ':all';
use Sys::Hostname;

use lib 'lib';
use parent 'Mail::Toaster::Base';

sub config {
    my $self  = shift;
    my %p = validate( @_, { $self->get_std_opts } );

    return $p{test_ok} if defined $p{test_ok}; # for testing only

    # apply the platform specific changes to the config file
    $self->tweaks();

    my $file_name = "toaster-watcher.conf";
    my $file_path = $self->util->find_config( $file_name );
    $self->setup->refresh_config( $file_path ) or return;

### start questions
    $self->config_hostname();
    $self->postmaster();
    $self->test_email();
    $self->test_email_pass();
    $self->vpopmail_mysql_pass();
    $self->openssl();
    $self->webmail_passwords();
### end questions
### don't forget to add changed fields to the list in save_changes

    $self->save_changes( $file_path );
    $self->install($file_name, $file_path );
};

sub apply_tweaks {
    my $self = shift;
    my %p = validate( @_,
        {   file    => { type => SCALAR },
            changes => { type => ARRAYREF },
            $self->get_std_opts
        },
    );

# changes is a list (array) of changes to apply to a text file
# each change is a hash with two elements: search, replace. the contents of
# file is searched for lines that matches search. Matches are replaced by the
# replace string. If search2 is also supplied and search does not match,
# search2 will be replaced.
# Ex:
# $changes = (
#    { search  => '#ssl_cert = /etc/ssl/certs/server.pem',
#      replace => 'ssl_cert = /etc/ssl/certs/server.pem',
#    },
# );
#
    # read in file
    my @lines = $self->util->file_read( $p{file} ) or return;

    my $total_found = 0;
    foreach my $e ( @{ $p{changes} } ) {
        my $search = $e->{search} or next;
        my $replace = $e->{replace} or next;
        my $search2 = $e->{search2};
        my $found = 0;

        if ( $search2 && $search2 eq 'section' ) {
# look for a multiline pattern such as: protocol manageseive {  ....  }
            my (@after, $in);
            foreach my $line ( @lines ) {
                if ( $in ) {
                    next if $line !~ /^ \s* \} \s* $/xms;
                    $in = 0;
                    next;
                }
                if ( $search eq $line ) {
                    $found++;
                    $in++;
                    next;
                };
                push @after, $line if ! $in;
            };
            @lines = @after;
        };
# search entire file for $search string
        for ( my $i = 0; $i < scalar @lines; $i++ ) {
            if ( $lines[$i] eq $search ) {
                $lines[$i] = $replace;
                $found++;
            };
        }
# search entire file for $search2 string
        if ( ! $found && $search2 ) {
            for ( my $i = 0; $i < scalar @lines; $i++ ) {
                if ( $lines[$i] eq $search2 ) {
                    $lines[$i] = $replace;
                    $found++;
                };
            }
        };
        $self->error( "attempt to replace\n$search\n\twith\n$replace\n\tfailed",
            frames => 1,
            fatal => 0) if ( ! $found && ! $e->{nowarn} );
        $total_found += $found;
    };

    $self->audit( "config tweaks replaced $total_found lines",verbose=>$p{verbose} );

    $self->util->file_write( $p{file}, lines => \@lines );
};

sub config_hostname {
    my $self = shift;

    return if ( $self->conf->{'toaster_hostname'} && $self->conf->{'toaster_hostname'} ne "mail.example.com" );

    $self->conf->{'toaster_hostname'} = $self->util->ask(
        "the hostname of this mail server",
        default  => hostname,
    );
    chomp $self->conf->{'toaster_hostname'};

    $self->audit( "toaster hostname set to " . $self->conf->{'toaster_hostname'} );
};

sub install {
    my $self = shift;
    my ($file_name, $file_path) = @_;

    # install $file_path in $prefix/etc/toaster-watcher.conf if it doesn't exist
    # already
    my $config_dir = $self->conf->{'system_config_dir'} || '/usr/local/etc';

    # if $config_dir is missing, create it
    $self->util->mkdir_system( dir => $config_dir ) if ! -e $config_dir;

    my @configs = (
        { newfile  => $file_path, existing => "$config_dir/$file_name", mode => '0640', overwrite => 0 },
        { newfile  => $file_path, existing => "$config_dir/$file_name-dist", mode => '0640', overwrite => 1 },
        { newfile  => 'toaster.conf-dist', existing => "$config_dir/toaster.conf", mode => '0644', overwrite => 0 },
        { newfile  => 'toaster.conf-dist', existing => "$config_dir/toaster.conf-dist", mode => '0644', overwrite => 1 },
    );

    foreach ( @configs ) {
        next if -e $_->{existing} && ! $_->{overwrite};
        $self->util->install_if_changed(
            newfile  => $_->{newfile},
            existing => $_->{existing},
            mode     => $_->{mode},
            clean    => 0,
            notify   => 1,
            verbose  => 0,
        );
    };
}

sub openssl {
    my $self = shift;
    # OpenSSL certificate settings

    # country
    if ( $self->conf->{'ssl_country'} eq "SU" ) {
        print "             SSL certificate defaults\n";
        $self->conf->{'ssl_country'} =
          uc( $self->util->ask( "your 2 digit country code (US)", default  => "US" )
          );
    }
    $self->audit( "config: ssl_country, (" . $self->conf->{'ssl_country'} . ")" );

    # state
    if ( $self->conf->{'ssl_state'} eq "saxeT" ) {
        $self->conf->{'ssl_state'} =
          $self->util->ask( "the name (non abbreviated) of your state" );
    }
    $self->audit( "config: ssl_state, (" . $self->conf->{'ssl_state'} . ")" );

    # locality (city)
    if ( $self->conf->{'ssl_locality'} eq "dnalraG" ) {
        $self->conf->{'ssl_locality'} =
          $self->util->ask( "the name of your locality/city" );
    }
    $self->audit( "config: ssl_locality, (" . $self->conf->{'ssl_locality'} . ")" );

    # organization
    if ( $self->conf->{'ssl_organization'} eq "moc.elpmaxE" ) {
        $self->conf->{'ssl_organization'} = $self->util->ask( "the name of your organization" );
    }
    $self->audit( "config: ssl_organization, (" . $self->conf->{'ssl_organization'} . ")" );
};

sub webmail_passwords {
    my $self = shift;

    if ( $self->conf->{install_squirrelmail} &&
         $self->conf->{install_squirrelmail_sql} &&
         $self->conf->{install_squirrelmail_sql_pass} eq 'chAnge7his' ) {
         $self->conf->{install_squirrelmail_sql_pass} =
            $self->util->ask("squirrelmail database password");
    };

    if ( $self->conf->{install_roundcube} &&
         $self->conf->{install_roundcube_db_pass} eq 'To4st3dR0ndc@be' ) {
         $self->conf->{install_roundcube_db_pass} =
            $self->util->ask("roundcube database password");
    };

    if ( $self->conf->{install_spamassassin} &&
         $self->conf->{install_spamassassin_sql} &&
         $self->conf->{install_spamassassin_dbpass} eq 'assSPAMing' ) {
         $self->conf->{install_spamassassin_dbpass} =
            $self->util->ask("spamassassin database password");
    };

    if ( $self->conf->{install_phpmyadmin} &&
         $self->conf->{phpMyAdmin_controlpassword} eq 'pmapass') {
         $self->conf->{phpMyAdmin_controlpassword} =
            $self->util->ask("phpMyAdmin control password");
    };

    return 1;
};

sub postmaster {
    my $self = shift;

    return if ( $self->conf->{'toaster_admin_email'} && $self->conf->{'toaster_admin_email'} ne "postmaster\@example.com" );

    $self->conf->{'toaster_admin_email'} = $self->util->ask(
        "the email address for administrative emails and notices\n".
            " (probably yours!)",
        default => "postmaster",
    ) || 'root';

    $self->audit(
        "toaster admin emails sent to " . $self->conf->{'toaster_admin_email'} );
};

sub save_changes {
    my $self = shift;
    my ($file_path) = @_;

    my @fields = qw/ toaster_hostname toaster_admin_email toaster_test_email
        toaster_test_email_pass vpopmail_mysql_pass ssl_country ssl_state
        ssl_locality ssl_organization install_squirrelmail_sql_pass
        install_roundcube_db_pass install_spamassassin_dbpass
        phpMyAdmin_controlpassword
        /;
    push @fields, 'vpopmail_mysql_pass' if $self->conf->{'vpopmail_mysql'};

    my @lines = $self->util->file_read( $file_path, verbose => 0 );
    foreach my $key ( @fields ) {
        foreach my $line (@lines) {
            if ( $line =~ /^$key\s*=/ ) {
# format the config entries to match config file format
                $line = sprintf( '%-34s = %s', $key, $self->conf->{$key} );
            }
        };
    };

    $self->util->file_write( "/tmp/toaster-watcher.conf", lines => \@lines );

    my $r = $self->util->install_if_changed(
            newfile  => "/tmp/toaster-watcher.conf",
            existing => $file_path,
            mode     => '0640',
            clean    => 1,
            notify   => -e $file_path ? 1 : 0,
    )
    or return $self->error( "installing /tmp/toaster-watcher.conf to $file_path failed!" );

    my $status = $r == 1 ? "ok" : "ok (current)";
    $self->audit( "config: updating $file_path, $status" );
    return $r;
};

sub test_email {
    my $self = shift;

    return if $self->conf->{'toaster_test_email'} ne "test\@example.com";

    $self->conf->{'toaster_test_email'} = $self->util->ask(
        "an email account for running tests",
        default  => "postmaster\@" . $self->conf->{'toaster_hostname'}
    );

    $self->audit( "toaster test account set to ".$self->conf->{'toaster_test_email'} );
};

sub test_email_pass {
    my $self = shift;

    return if ( $self->conf->{'toaster_test_email_pass'} && $self->conf->{'toaster_test_email_pass'} ne "cHanGeMe" );

    $self->conf->{'toaster_test_email_pass'} = $self->util->ask( "the test email account password" );

    $self->audit(
        "toaster test password set to ".$self->conf->{'toaster_test_email_pass'} );
};

sub tweaks {
    my $self  = shift;
    my %p = validate( @_, { $self->get_std_opts } );

    my $status = "ok";

    my $file = $self->util->find_config( 'toaster-watcher.conf' );

    # verify that find_config worked and $file is readable
    return $self->error( "tweaks: read test on $file, FAILED",
        fatal => $p{fatal} ) if ! -r $file;

    my %changes;
    %changes = $self->config_tweaks_freebsd() if $OSNAME eq 'freebsd';
    %changes = $self->config_tweaks_darwin()  if $OSNAME eq 'darwin';
    %changes = $self->config_tweaks_linux()   if $OSNAME eq 'linux';

    %changes = $self->config_tweaks_testing(%changes);
    %changes = $self->config_tweaks_mysql(%changes);

    # foreach key of %changes, apply to $conf
    my @lines = $self->util->file_read( $file );
    foreach my $line (@lines) {
        next if $line =~ /^#/;  # comment lines
        next if $line !~ /=/;   # not a key = value

        my ( $key, $val ) = $self->util->parse_line( $line, strip => 0 );

        if ( defined $changes{$key} && $changes{$key} ne $val ) {
            $status = "changed";
            #print "\t setting $key to ". $changes{$key} . "\n";
            $line = sprintf( '%-34s = %s', $key, $changes{$key} );
            print "\t$line\n";
        }
    }
    return 1 unless ( $status && $status eq "changed" );

    # ask the user for permission to install
    return 1
      if ! $self->util->yes_or_no(
'config tweaks: The changes shown above are recommended for use on your system.
May I apply the changes for you?',
        timeout => 30,
      );

    # write $conf to temp file
    $self->util->file_write( "/tmp/toaster-watcher.conf", lines => \@lines );

    # if the file ends with -dist, then save it back with out the -dist suffix
    # the find_config sub will automatically prefer the newer non-suffixed one
    if ( $file =~ m/(.*)-dist\z/ ) {
        $file = $1;
    };

    # update the file if there are changes
    my $r = $self->util->install_if_changed(
        newfile  => "/tmp/toaster-watcher.conf",
        existing => $file,
        clean    => 1,
        notify   => 0,
        verbose    => 0,
    );

    return 0 unless $r;
    $r == 1 ? $r = "ok" : $r = "ok (current)";
    $self->audit( "config tweaks: updated $file, $r" );
}

sub config_tweaks_darwin {
    my $self = shift;

    $self->audit( "config tweaks: applying Darwin tweaks" );

    return (
        toaster_http_base    => '/Library/WebServer',
        toaster_http_docs    => '/Library/WebServer/Documents',
        toaster_cgi_bin      => '/Library/WebServer/CGI-Executables',
        toaster_prefix       => '/opt/local',
        toaster_src_dir      => '/opt/local/src',
        system_config_dir    => '/opt/local/etc',
        vpopmail_valias      => '0',
        install_mysql        => '0      # 0, 1, 2, 3, 40, 41, 5',
        install_portupgrade  => '0',
        filtering_maildrop_filter_file => '/opt/local/etc/mail/mailfilter',
        qmail_mysql_include  => '/opt/local/lib/mysql/libmysqlclient.a',
        vpopmail_home_dir    => '/opt/local/vpopmail',
        vpopmail_mysql       => '0',
        smtpd_use_mysql_relay_table => '0',
        qmailadmin_spam_command => '| /opt/local/bin/maildrop /opt/local/etc/mail/mailfilter',
        qmailadmin_http_images  => '/Library/WebServer/Documents/images',
    );
};

sub config_tweaks_freebsd {
    my $self = shift;
    $self->audit( "config tweaks: applying FreeBSD tweaks" );

    return (
        install_squirrelmail => 'port    # 0, ver, port',
        install_autorespond  => 'port    # 0, ver, port',
        install_ezmlm        => 'port    # 0, ver, port',
        install_courier_imap => '0       # 0, ver, port',
        install_dovecot      => 'port    # 0, ver, port',
        install_clamav       => 'port    # 0, ver, port',
        install_ripmime      => 'port    # 0, ver, port',
        install_cronolog     => 'port    # ver, port',
        install_daemontools  => 'port    # ver, port',
        install_qmailadmin   => 'port    # 0, ver, port',
        install_djbdns       => 'port    # ver, port',
    )
}

sub config_tweaks_linux {
    my $self = shift;
    $self->audit( "config tweaks: applying Linux tweaks " );

    return (
        toaster_http_base           => '/var/www',
        toaster_http_docs           => '/var/www',
        toaster_cgi_bin             => '/usr/lib/cgi-bin',
        vpopmail_valias             => '0',
        install_mysql               => '0      # 0, 1, 2, 3, 40, 41, 5',
        vpopmail_mysql              => '0',
        smtpd_use_mysql_relay_table => '0',
        qmailadmin_http_images      => '/var/www/images',
        install_dovecot             => '1.0.2',
    )
}

sub config_tweaks_mysql {
    my ($self, %changes) = @_;

    return %changes if $self->conf->{install_mysql};
    return %changes if ! $self->util->yes_or_no("Enable MySQL support?");

    $self->audit( "config tweaks: applying MT testing tweaks" );

    $changes{'install_mysql'}   = '55      # 0, 1, 2, 3, 40, 41, 5, 55';
    $changes{'install_mysqld'}  = '1       # 0, 1';
    $changes{'vpopmail_mysql'}  = '1         # enables all mysql options';
    $changes{'smtpd_use_mysql_relay_table'} = 0;
    $changes{'install_squirrelmail_sql'}    = 1;
    $changes{'install_spamassassin_sql'}    = 1;

    return %changes;
};

sub config_tweaks_testing {
    my ($self, %changes) = @_;

    my $hostname = hostname;
    return %changes if ( ! $hostname || $hostname ne 'jail.simerson.net' );

    $self->audit( "config tweaks: applying MT testing tweaks" );

    $changes{'toaster_hostname'}      = 'jail.simerson.net';
    $changes{'toaster_admin_email'}   = 'postmaster@jail.simerson.net';
    $changes{'toaster_test_email'}    = 'test@jail.simerson.net';
    $changes{'toaster_test_email_pass'}   = 'sdfsdf';
    $changes{'install_squirrelmail_sql'}  = '1';
    $changes{'install_phpmyadmin'}        = '1';
    $changes{'install_sqwebmail'}         = 'port';
    $changes{'install_vqadmin'}           = 'port';
    $changes{'install_openldap_client'}   = '1';
    $changes{'install_ezmlm_cgi'}         = '1';
    $changes{'install_dspam'}             = '1';
    $changes{'install_pyzor'}             = '1';
    $changes{'install_bogofilter'}        = '1';
    $changes{'install_dcc'}               = '1';
    $changes{'install_lighttpd'}          = '1';
    $changes{'install_courier_imap'}      = 'port';
    $changes{'install_gnupg'}             = 'port';
    $changes{'vpopmail_default_domain'}   = 'jail.simerson.net';
    $changes{'pop3_ssl_daemon'}           = 'qpop3d';
    $changes{'install_spamassassin_flags'}= '-v -u spamd -q -A 10.0.1.67 -H /var/spool/spamd -x';
    $changes{'install_isoqlog'}           = 'port    # 0, ver, port';

    return %changes;
}

sub vpopmail_mysql_pass {
    my $self = shift;

    return if ! $self->conf->{'vpopmail_mysql'};
    return if ( $self->conf->{'vpopmail_mysql_pass'}
        && $self->conf->{'vpopmail_mysql_pass'} ne "supersecretword" );

    $self->conf->{'vpopmail_mysql_pass'} =
        $self->util->ask( "the password for securing vpopmails "
            . "database connection. You MUST enter a password here!",
        );

    $self->audit( "vpopmail MySQL password set to ".$self->conf->{'vpopmail_mysql_pass'});
}

1;