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

our $VERSION = '5.50';

use Carp;
use English '-no_match_vars';
use Params::Validate ':all';

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

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

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

    if ( !$self->conf->{'install_vpopmail'} ) {
        $self->audit( "vpopmail: installing, skipping (disabled)" );
        return;
    }

    my $version = $self->conf->{install_vpopmail} || '5.4.33';

    if ( $OSNAME eq "freebsd" ) {
    # always install the port version, so subsequent ports will
    # find it registered in the ports db.
        $self->install_freebsd_port;
    }

    if ( $version ne 'port' ) {
        $self->install_from_source( %p );
    };

    return $self->post_install;
};

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

    my $conf = $self->conf;
    my $version = $conf->{'install_vpopmail'};

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

    my @defs = 'LOGLEVEL="p"';

    my $learn = $conf->{vpopmail_learn_passwords}     ? 'SET' : 'UNSET';
    my $ip_alias = $conf->{vpopmail_ip_alias_domains} ? 'SET' : 'UNSET';
    my $qmail_ext= $conf->{vpopmail_qmail_ext}        ? 'SET' : 'UNSET';
    my $single_dom=$conf->{vpopmail_disable_many_domains} ? 'SET' : 'UNSET';
    my $maildrop = $conf->{vpopmail_maildrop}         ? 'SET' : 'UNSET';
    my $mysql    = $conf->{'vpopmail_mysql'}          ? 'SET' : 'UNSET';
    my $roaming  = $conf->{vpopmail_roaming_users}    ? 'SET' : 'UNSET';
    my $mysql_rep= my $mysql_lim = my $sql_log = my $valias = 'UNSET';
    my $auth_log = $conf->{vpopmail_auth_logging}     ? 'SET' : 'UNSET';

    if ( $roaming eq 'SET' && $conf->{vpopmail_relay_clear_minutes} ) {
        push @defs, 'RELAYCLEAR='.$conf->{vpopmail_relay_clear_minutes};
    };

    if ( $mysql eq 'SET' ) {
        $self->error( "vpopmail_mysql is enabled but install_mysql is not. Please correct your settings" ) if ! $conf->{install_mysql};
        $mysql_rep = 'SET' if $conf->{vpopmail_mysql_replication};
        $mysql_lim = 'SET' if $conf->{vpopmail_mysql_limits};
        $valias    = 'SET' if $conf->{vpopmail_valias};
        $sql_log   = 'SET' if $conf->{vpopmail_mysql_logging};
    };

    $self->freebsd->install_port( 'vpopmail',
        flags => join( ',', @defs ),
        options => "# installed by Mail::Toaster
# Options for vpopmail-5.4.32_3
_OPTIONS_READ=vpopmail-5.4.32_3
_FILE_COMPLETE_OPTIONS_LIST=AUTH_LOG CLEAR_PASSWD DOCS DOMAIN_QUOTAS FILE_LOCKING FILE_SYNC FPIC IP_ALIAS LDAP LDAP_SASL LEARN_PASSWORDS MAILDROP MD5_PASSWORDS MYSQL MYSQL_LIMITS MYSQL_REPLICATION ONCHANGE_SCRIPT ORACLE PASSWD PGSQL QMAIL_EXT ROAMING SEEKABLE SINGLE_DOMAIN SMTP_AUTH_PATCH SPAMASSASSIN SPAMFOLDER SQL_LOG SQL_LOG_TRIM SUID_VCHKPW SYBASE USERS_BIG_DIR VALIAS
OPTIONS_FILE_$auth_log+=AUTH_LOG
OPTIONS_FILE_SET+=CLEAR_PASSWD
OPTIONS_FILE_SET+=DOCS
OPTIONS_FILE_UNSET+=DOMAIN_QUOTAS
OPTIONS_FILE_SET+=FILE_LOCKING
OPTIONS_FILE_UNSET+=FILE_SYNC
OPTIONS_FILE_SET+=FPIC
OPTIONS_FILE_$ip_alias+=IP_ALIAS
OPTIONS_FILE_UNSET+=LDAP
OPTIONS_FILE_UNSET+=LDAP_SASL
OPTIONS_FILE_$learn+=LEARN_PASSWORDS
OPTIONS_FILE_$maildrop+=MAILDROP
OPTIONS_FILE_SET+=MD5_PASSWORDS
OPTIONS_FILE_$mysql+=MYSQL
OPTIONS_FILE_$mysql_lim+=MYSQL_LIMITS
OPTIONS_FILE_$mysql_rep+=MYSQL_REPLICATION
OPTIONS_FILE_UNSET+=ONCHANGE_SCRIPT
OPTIONS_FILE_UNSET+=ORACLE
OPTIONS_FILE_UNSET+=PASSWD
OPTIONS_FILE_UNSET+=PGSQL
OPTIONS_FILE_$qmail_ext+=QMAIL_EXT
OPTIONS_FILE_$roaming+=ROAMING
OPTIONS_FILE_SET+=SEEKABLE
OPTIONS_FILE_$single_dom+=SINGLE_DOMAIN
OPTIONS_FILE_UNSET+=SMTP_AUTH_PATCH
OPTIONS_FILE_UNSET+=SPAMASSASSIN
OPTIONS_FILE_UNSET+=SPAMFOLDER
OPTIONS_FILE_$sql_log+=SQL_LOG
OPTIONS_FILE_UNSET+=SQL_LOG_TRIM
OPTIONS_FILE_UNSET+=SUID_VCHKPW
OPTIONS_FILE_UNSET+=SYBASE
OPTIONS_FILE_SET+=USERS_BIG_DIR
OPTIONS_FILE_$valias+=VALIAS
",
    ) or return;

    my $vpopdir = $self->get_vpop_dir;
    my $docroot = $self->conf->{'toaster_http_docs'};

    # add a symlink so docs are web browsable
    if ( -d $docroot && ! -e "$docroot/vpopmail" ) {
        if ( -d "$vpopdir/doc/man_html" ) {
            symlink "$vpopdir/doc/man_html", "$docroot/vpopmail";
        }
    }
}

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

    my $conf = $self->conf;
    my $version = $conf->{'install_vpopmail'} || "5.4.33";
    my $package = "vpopmail-$version";
    my $vpopdir = $self->get_vpop_dir;

    $self->create_user();   # add the vpopmail user/group
    my $uid = getpwnam( $conf->{'vpopmail_user'} || "vpopmail" );
    my $gid = getgrnam( $conf->{'vpopmail_group'} || "vchkpw"  );

    my $installed = $self->installed_version();

    if ( $installed && $installed eq $version ) {
        $self->util->yes_or_no(
                "Do you want to reinstall vpopmail with the same version?",
            timeout => 60,
            ) or do {
            $self->post_install();
            return 1;
        };
    }

    my $conf_args;
    foreach ( qw/ rebuild_tcpserver_file ip_alias_domains valias mysql_logging
        qmail_ext learn_passwords mysql / ) {
        my $mt_setting = 'vpopmail_' . $_;
        my $conf_arg = "--enable-$_";
        $conf_arg =~ s/_/-/g;
        my $r = $conf->{$mt_setting} ? 'yes' : 'no';
        $conf_args .= " $conf_arg=$r";
        print "$conf_arg=$r\n";
    };

    if ( ! $self->is_newer( min => "5.3.30", cur => $version ) ) {
        if ( defined $conf->{'vpopmail_default_quota'} ) {
            $conf_args .=
              " --enable-defaultquota=".$conf->{'vpopmail_default_quota'};
            print "default quota: ".$conf->{'vpopmail_default_quota'}."\n";
        }
        else {
            $conf_args .= " --enable-defaultquota=100000000S,10000C";
            print "default quota: 100000000S,10000C\n";
        }
    }

    $conf_args .= $self->roaming_users();

    if ( $OSNAME eq "darwin" && !-d "/usr/local/mysql"
        && -d "/opt/local/include/mysql" ) {
        $conf_args .= " --enable-incdir=/opt/local/include/mysql";
        $conf_args .= " --enable-libdir=/opt/local/lib/mysql";
    }

    my $tcprules = $self->util->find_bin( "tcprules", verbose=>0 );
    $conf_args .= " --enable-tcprules-prog=$tcprules";

    my $src = $conf->{'toaster_src_dir'} || "/usr/local/src";

    $self->util->cwd_source_dir( "$src/mail" );

    my $tarball = "$package.tar.gz";

    # save having to download it again
    if ( -e "/usr/ports/distfiles/vpopmail-$version.tar.gz" ) {
        copy(
            "/usr/ports/distfiles/vpopmail-$version.tar.gz",
            "/usr/local/src/mail/"
        );
    }

    $self->util->sources_get(
        'package' => $package,
        site      => "http://" . $conf->{'toaster_sf_mirror'},
        path      => "/vpopmail",
    );

    if ( -d $package ) {
        $self->util->source_warning(
                package => $package,
                src     => "$src/mail",
            ) or return;
    }

    $self->util->extract_archive( $tarball ) or die;

    if ( $conf->{vpopmail_mysql} ) {
        $conf_args .= $self->mysql_options();
    };
    $conf_args .= $self->logging();
    $conf_args .= $self->default_domain($version);
    $conf_args .= $self->etc_passwd();

    # in case someone updates their toaster and not their config file
    if ( defined $conf->{'vpopmail_qmail_ext'}
              && $conf->{'vpopmail_qmail_ext'} ) {
        $conf_args .= " --enable-qmail-ext=y";
        print "qmail extensions: yes\n";
    }
    if ( defined $conf->{'vpopmail_maildrop'} ) {
        $conf_args .= " --enable-maildrop=y";
    };

    print "fixup for longer passwords\n";
    system "sed -i -Ee '/^pw_clear_passwd char(/s/16/128/' vmysql.h";
    system "sed -i -Ee '/^pw_passwd char(/s/40/128/' vmysql.h";

    chdir($package);
    print "running configure with $conf_args\n\n";

    $self->util->syscmd( "./configure $conf_args", verbose => 0 );
    $self->util->syscmd( "make",                   verbose => 0 );
    $self->util->syscmd( "make install-strip",     verbose => 0 );

    if ( -e "vlimits.h" ) {
        # this was for a bug in vpopmail 5.4.?(1-2) installer
        $self->util->syscmd( "cp vlimits.h $vpopdir/include/", verbose => 0);
    }

    return 1;
}

sub default_domain {
    my $self = shift;
    my $version = shift;

    my $default_domain;

    if ( defined $self->conf->{'vpopmail_default_domain'} ) {
        $default_domain = $self->conf->{'vpopmail_default_domain'};
    }
    else {
        $self->util->yes_or_no( "Do you want to use a default domain? " ) or do {
            print "default domain: NONE SELECTED.\n";
            return '';
        };

        $default_domain = $self->util->ask("your default domain");
    };

    if ( ! $default_domain ) {
        print "default domain: NONE SELECTED.\n";
        return '';
    };

    if ( $self->is_newer( min => "5.3.22", cur => $version ) ) {
        my $vpopetc = $self->get_vpop_etc;
        $self->util->file_write( "$vpopetc/defaultdomain",
            lines => [ $default_domain ],
            verbose => 0,
        );

        $self->util->chown( "$vpopetc/defaultdomain",
            uid  => $self->conf->{'vpopmail_user'}  || "vpopmail",
            gid  => $self->conf->{'vpopmail_group'} || "vchkpw",
        );

        return '';
    }

    print "default domain: $default_domain\n";
    return " --enable-default-domain=$default_domain";
};

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

    my $vetc = $self->get_vpop_etc;

    mkpath( $vetc, oct('0775') ) if ! -d $vetc;

    if ( -d $vetc ) {
        print "$vetc already exists.\n";
    }
    else {
        print "creating $vetc\n";
        mkdir( $vetc, oct('0775') ) or carp "failed to create $vetc: $!\n";
    }

    $self->setup->tcp_smtp( etc_dir => $vetc );
    $self->setup->tcp_smtp_cdb( etc_dir => $vetc );
}

sub etc_passwd {
    my $self = shift;

    unless ( defined $self->conf->{'vpopmail_etc_passwd'} ) {
        print "\t\t CAUTION!!  CAUTION!!

    The system user account feature is NOT compatible with qmail-smtpd-chkusr.
    If you selected that option in the qmail build, you should not answer
    yes here. If you are unsure, select (n).\n";

        if ( $self->util->yes_or_no( "Do system users (/etc/passwd) get mail? (n) ")) {
            print "system password accounts: yes\n";
            return " --enable-passwd";
        }
    }

    if ( $self->conf->{'vpopmail_etc_passwd'} ) {
        print "system password accounts: yes\n";
        return " --enable-passwd";
    }

    print "system password accounts: no\n";
};

sub get_vpop_etc {
    my $self = shift;
    my $base = $self->get_vpop_dir;
    return "$base/etc";
};

sub get_vpop_dir {
    my $self = shift;
    return $self->{conf}{vpopmail_home_dir} || '/usr/local/vpopmail';
};

sub installed_version {
    my $self = shift;

    my $vpopdir = $self->get_vpop_dir;
    return if ! -x "$vpopdir/bin/vpasswd";

    my $installed = `$vpopdir/bin/vpasswd -v | head -1 | cut -f2 -d" "`;
    chop $installed;
    $self->alert( "vpopmail version $installed installed." );
    return $installed;
}

sub logging {
    my $self = shift;

    my $conf = $self->conf;
    if ( defined $conf->{vpopmail_logging} && $conf->{vpopmail_logging} ) {
        if ( $conf->{'vpopmail_logging_verbose'} ) {
            print "logging: verbose with failed passwords\n";
            return " --enable-logging=v";
        }

        print "logging: everything\n";
        return " --enable-logging=y";
    }

    if ( ! $self->util->yes_or_no( "Do you want logging enabled? (y) ")) {
        return " --enable-logging=p";
    };

    if ( $self->util->yes_or_no( "Do you want verbose logging? (y) ")) {
        print "logging: verbose\n";
        return " --enable-logging=v";
    }

    print "logging: verbose with failed passwords\n";
    return " --enable-logging=p";
};

sub post_install {
    my $self = shift;
    $self->vpopmail_etc;
    $self->mysql_privs;
    $self->util->install_module( "vpopmail" ) if $self->{conf}{install_ezmlm_cgi};
    print "vpopmail: complete.\n";
    return 1;
};

sub roaming_users {
    my $self = shift;

    my $roaming = $self->conf->{'vpopmail_roaming_users'};

    if ( defined $roaming && !$roaming ) {
        print "roaming users: no\n";
        return " --enable-roaming-users=n";
    }

    # default to enabled
    if ( !defined $self->conf->{'vpopmail_roaming_users'} ) {
        print "roaming users: value not set?!\n";
    }

    print "roaming users: yes\n";

    my $min = $self->conf->{'vpopmail_relay_clear_minutes'};
    if ( $min && $min ne 180 ) {
        print "roaming user minutes: $min\n";
        return " --enable-roaming-users=y" .
            " --enable-relay-clear-minutes=$min";
    };
    return " --enable-roaming-users=y";
};

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

    return $p{test_ok} if defined $p{test_ok};

    print "do vpopmail directories exist...\n";
    my $vpdir = $self->conf->{'vpopmail_home_dir'};
    foreach ( "", "bin", "domains", "etc/", "include", "lib" ) {
        $self->setup->test->pretty("  $vpdir/$_", -d "$vpdir/$_" );
    }

    print "checking vpopmail binaries...\n";
    foreach (
        qw/
        clearopensmtp   vaddaliasdomain     vadddomain
        valias          vadduser            vchkpw
        vchangepw       vconvert            vdeldomain
        vdelivermail    vdeloldusers        vdeluser
        vdominfo        vipmap              vkill
        vmkpasswd       vmoddomlimits       vmoduser
        vpasswd         vpopbull            vqmaillocal
        vsetuserquota   vuserinfo   /
      )
    {
        $self->setup->test->pretty("  $_", -x "$vpdir/bin/$_" );
    }

    print "do vpopmail libs exist...\n";
    foreach ("$vpdir/lib/libvpopmail.a") {
        $self->setup->test->pretty("  $_", -e $_ );
    }

    print "do vpopmail includes exist...\n";
    foreach (qw/ config.h vauth.h vlimits.h vpopmail.h vpopmail_config.h /) {
        $self->setup->test->pretty("  include/$_", -e "$vpdir/include/$_" );
    }

    print "checking vpopmail etc files...\n";
    my @vpetc = qw/ inc_deps lib_deps tcp.smtp tcp.smtp.cdb vlimits.default /;
    push @vpetc, 'vpopmail.mysql' if $self->conf->{'vpopmail_mysql'};

    foreach ( @vpetc ) {
        $self->setup->test->pretty("  $_", (-e "$vpdir/etc/$_" && -s "$vpdir/etc/$_" ));
    }
}

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

    my $vpopdir = $self->get_vpop_dir;
    my $vpuser  = $self->conf->{vpopmail_user}  || 'vpopmail';
    my $vpgroup = $self->conf->{vpopmail_group} || 'vchkpw';

    my $uid = getpwnam($vpuser);
    my $gid = getgrnam($vpgroup);

    if ( !$uid || !$gid ) {
        $self->group_add( $vpgroup, "89" );
        $self->user_add( $vpuser, 89, 89, homedir => $vpopdir );
    }

    $uid = getpwnam($vpuser);
    $gid = getgrnam($vpgroup);

    return $self->error( "failed to add vpopmail user or group!")
        if ( !$uid || !$gid );

    return 1;
}

sub mysql_options {
    my $self = shift;
    my $mysql_repl = $self->conf->{vpopmail_mysql_replication};
    my $my_write   = $self->conf->{vpopmail_mysql_repl_master} || 'localhost';
    my $db         = $self->conf->{vpopmail_mysql_database} || 'vpopmail';

    my $opts;
    if ( $self->conf->{vpopmail_mysql_limits} ) {
        print "mysql qmailadmin limits: yes\n";
        $opts .= " --enable-mysql-limits=y";
    }

    if ( $mysql_repl ) {
        $opts .= " --enable-mysql-replication=y";
        print "mysql replication: yes\n";
        print "      replication master: $my_write\n";
    }

    if ( $self->conf->{vpopmail_disable_many_domains} ) {
        $opts .= " --disable-many-domains";
    }

    return $opts;
}

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

    if ( !$self->conf->{'vpopmail_mysql'} ) {
        print "vpopmail mysql_privs: mysql support not selected\n";
        return;
    }

    my $mysql_repl    = $self->conf->{vpopmail_mysql_replication};
    my $my_write      = $self->conf->{vpopmail_mysql_repl_master} || 'localhost';
    my $my_write_port = $self->conf->{vpopmail_mysql_repl_master_port} || 3306;
    my $my_read       = $self->conf->{vpopmail_mysql_repl_slave}  || 'localhost';
    my $my_read_port  = $self->conf->{vpopmail_mysql_repl_slave_port} || 3306;
    my $db            = $self->conf->{vpopmail_mysql_database} || 'vpopmail';

    my $user = $self->conf->{'vpopmail_mysql_user'} || $self->conf->{vpopmail_mysql_repl_user};
    my $pass = $self->conf->{'vpopmail_mysql_pass'} || $self->conf->{vpopmail_mysql_repl_pass};

    my $vpopdir = $self->get_vpop_dir;

    my @lines = "$my_read|0|$user|$pass|$db";
    if ($mysql_repl) {
        push @lines, "$my_write|$my_write_port|$user|$pass|$db";
    }
    else {
        push @lines, "$my_read|$my_read_port|$user|$pass|$db";
    }

    $self->util->file_write( "$vpopdir/etc/vpopmail.mysql",
        lines => \@lines,
        verbose => 1,
    );

    my $dot = $self->mysql->parse_dot_file( ".my.cnf", "[mysql]", 0 )
        || { user => $user, pass => $pass, host => $my_write, db => $db };

    my ( $dbh, $dsn, $drh ) = $self->mysql->connect( $dot, 1 );
    if ( !$dbh ) {
        $dot = { user => 'root', pass => '', host => $my_write };
        ( $dbh, $dsn, $drh ) = $self->mysql->connect( $dot, 1 );
    };

    if ( !$dbh ) {
        print <<"EOMYSQLGRANT";

        WARNING: I couldn't connect to your database server!  If this is a new install,
        you will need to connect to your database server and run this command manually:

        mysql -u root -h $my_write -p
        CREATE DATABASE $db;
        GRANT ALL PRIVILEGES ON $db.* TO $user\@'$my_write' IDENTIFIED BY '$pass';
        use $db;
        CREATE TABLE relay ( ip_addr char(18) NOT NULL default '',
          timestamp char(12) default NULL, name char(64) default NULL,
          PRIMARY KEY (ip_addr)) PACK_KEYS=1;
        ALTER TABLE vpopmail MODIFY pw_clear_passwd VARCHAR(128);
        ALTER TABLE vpopmail MODIFY pw_passwd VARCHAR(128);
        quit;

        If this is an upgrade and you already use MySQL authentication,
        then you can safely ignore this warning.

EOMYSQLGRANT
        return;
    }

    my $query = "use $db";
    my $sth = $self->mysql->query( $dbh, $query, 1 );
    if ( !$sth->errstr ) {
        $self->audit( "vpopmail: database setup, ok (exists)" );
        $sth->finish;
        return 1;
    }

    print "vpopmail: no vpopmail database, creating it now...\n";
    $query = "CREATE DATABASE $db";
    $sth   = $self->mysql->query( $dbh, $query );

    print "vpopmail: granting privileges to $user\n";
    $query =
      "GRANT ALL PRIVILEGES ON $db.* TO $user\@'$my_write' IDENTIFIED BY '$pass'";
    $sth = $self->mysql->query( $dbh, $query );

    print "vpopmail: creating the relay table.\n";
    $query = "CREATE TABLE $db.relay ( ip_addr char(18) NOT NULL default '', timestamp char(12) default NULL, name char(64) default NULL, PRIMARY KEY (ip_addr)) PACK_KEYS=1";
    $sth = $self->mysql->query( $dbh, $query );
    $self->audit( "vpopmail: databases created, ok" );
    $sth = $self->mysql->query( $dbh, "ALTER TABLE vpopmail MODIFY pw_clear_passwd VARCHAR(128)" );
    $sth = $self->mysql->query( $dbh, "ALTER TABLE vpopmail MODIFY pw_passwd VARCHAR(128)" );

    $sth->finish;

    return 1;
}

1;