#!/usr/local/cpanel/3rdparty/bin/perl
package Cpanel::CCS::Utils;

# cpanel - Cpanel/CCS/Utils.pm                     Copyright 2019 cPanel, L.L.C.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

use Config::Tiny              ();
use Cpanel::Sys::Hostname     ();
use Cpanel::Version           ();
use Cpanel::SafetyBits::Chown ();
use Template                  ();

use lib '/var/cpanel/perl';

use Cpanel::CCS::Constants ();
use Cpanel::CCS::Userdata  ();

our $CCS_DIR       = Cpanel::CCS::Constants::CCS_INSTALL_DIR . '/conf';
our $TEMPLATE_FILE = 'caldavd.tmpl';
our $PLIST_FILE    = 'caldavd-dev.plist';
our $CCS_CONF_FILE = Cpanel::CCS::Constants::CCS_CPDATA_DIR . '/config';
our %DEFAULT_CONF  = (
    'LOG_LEVEL'                 => 'info',
    'ENABLE_SNI'                => 'true',
    'DEFAULT_SSL_BUNDLE'        => "$CCS_DIR/cpanel.pem",
    'MAIL_USER'                 => 'cpanel-ccs',
    'SMTP_PORT'                 => 25,
    'IMAP_PORT'                 => 993,
    'SMTP_USE_SSL'              => 'true',
    'PGLOCKTIMEOUT'             => 10,
    'PGMAXCONNECTIONS'          => 64,
    'PGSHAREDBUF'               => '128MB',
    'REDIRECTHTTPTOHTTPS'       => 'true',
    'USERQUOTA'                 => 104857600,
    'MAXIMUMATTACHMENTSIZE'     => 10485760,                # 0 for unlimited doesn't actually work for this value lol
    'MAXCOLLECTIONSPERHOME'     => 0,
    'MAXRESOURCESPERCOLLECTION' => 0,
    'MAXRESOURCESIZE'           => 1048576,
    'MAXATTENDEESPERINSTANCE'   => 0,
    'MAXALLOWEDINSTANCES'       => 0,
);

use constant ENOENT => 2;

=head1 NAME

Cpanel::CCS::Utils

=head1 SYNOPSIS

    Cpanel::CCS::Utils::rebuild_ccs_config();

=head1 DESCRIPTION

Various utility functions for CCS. Currently contains a config rebuilder.

=head1 SUBROUTINES

=head2 rebuild_ccs_conf

Rebuilds /opt/cpanel-ccs/conf/caldavd-dev.plist based off a TT template.

=cut

sub rebuild_ccs_conf {

    # Tolerate config file not existing
    my $ccs_conf = Config::Tiny->new()->read($CCS_CONF_FILE);
    $ccs_conf = $ccs_conf ? $ccs_conf->{_} : {};
    $ccs_conf ||= {};

    # Check for APNS, enable it if the cert is installed.
    my $ssl_dir   = '/var/cpanel/ssl';
    my %installed = ( 'caldav_apns' => 0, 'carddav_apns' => 0 );
    foreach my $svc_dir (qw{caldav_apns carddav_apns}) {
        my @good_files = grep { -s "$ssl_dir/$svc_dir/$_"; } qw{cert.pem key.pem};
        if ( scalar(@good_files) eq 2 ) {
            $installed{$svc_dir} = 1;
        }
    }

    require Cpanel::PostgresUtils;
    my $config = {
        _get_default_conf(),
        %$ccs_conf,
        'ADMIN_UUID'             => Cpanel::CCS::Userdata::get_user_uid( 'root', 'autocreate' => 0 ),
        'CALDAV_APNS_INSTALLED'  => $installed{'caldav_apns'},
        'CARDDAV_APNS_INSTALLED' => $installed{'carddav_apns'},
        'APNS_ENABLED'           => $installed{'caldav_apns'} || $installed{'carddav_apns'},
        'PGVERSION'              => join( '.', ( Cpanel::PostgresUtils::get_version() ) ) + 0,          # coerce numeric
    };
    my $processor = Template->new(
        {
            'INCLUDE_PATH' => $CCS_DIR,
            'OUTPUT_PATH'  => $CCS_DIR,
        }
    ) || die $Template::ERROR;
    require Cpanel::Umask;
    my $masked = Cpanel::Umask->new(077);
    $processor->process( $TEMPLATE_FILE, $config, "$PLIST_FILE.new" ) || die $processor->error();

    # Rename in place
    rename( "$CCS_DIR/$PLIST_FILE.new", "$CCS_DIR/$PLIST_FILE" );

    # Chown to cpanel-ccs user
    Cpanel::SafetyBits::Chown::safe_chown( 'cpanel-ccs', 'cpanel-ccs', "$CCS_DIR/$PLIST_FILE" );

    return 0;
}

sub _disable_plus_addressing_autocreate_if_necessary {

    # Check if disabled first
    require Cpanel::PwCache;
    require Cpanel::Email::DisableMailboxAutocreate;
    return if Cpanel::Email::DisableMailboxAutocreate->is_on( Cpanel::PwCache::gethomedir('cpanel-ccs'), 'cpanel-ccs' );

    # OK, disable it since it isn't already disabled
    require Cpanel::AccessIds::ReducedPrivileges;

    {
        my $privs = Cpanel::AccessIds::ReducedPrivileges->new('cpanel-ccs');
        local $@;
        my $worked = eval {
            Cpanel::Email::DisableMailboxAutocreate->set_on( Cpanel::PwCache::gethomedir('cpanel-ccs'), 'cpanel-ccs' );
            1;
        };
        die "Failed to disable mailbox autocreate: $@" if !$worked;
    }
    return 1;
}

sub _get_default_conf {
    my $hostname         = Cpanel::Sys::Hostname::gethostname();
    my $greater_than_v84 = Cpanel::Version::compare( Cpanel::Version::getversionnumber, ">=", "11.85", );
    return (
        %DEFAULT_CONF,
        'HOSTNAME'   => $hostname,
        'MAIL_ADDR'  => "cpanel-ccs\@${hostname}",
        'MAIL_PASS'  => _get_local_ccs_user_password_reset_if_broke(),
        'MAIL_HOST'  => '127.0.0.1',
        'HTTP_PORT'  => $greater_than_v84 ? 2079 : 8080,
        'HTTPS_PORT' => $greater_than_v84 ? 2080 : 8443,
    );
}

sub _get_local_ccs_user_password_reset_if_broke {
    my $needs_flushing = _disable_plus_addressing_autocreate_if_necessary();
    my $passfile       = "$CCS_DIR/.ccspass";
    my ( $ccs_password, $failed_open );
    open( my $rdr, "<", $passfile ) or do {
        die "Could not open file $passfile for reading: $!" if !$!{ENOENT};
        $failed_open = 1;
    };
    if ( !$failed_open ) {
        $ccs_password = <$rdr>;
        chomp($ccs_password);
        close($rdr);
    }
    if ( !$ccs_password ) {

        # Set a strong password
        require Cpanel::ChangePasswd;
        require Cpanel::PasswdStrength::Generate;
        my $newpass          = Cpanel::PasswdStrength::Generate::generate_password( 42, 'no_symbols' => 1, 'no_othersymbols' => 1 );
        my $greater_than_106 = Cpanel::Version::compare( Cpanel::Version::getversionnumber, ">", "11.107", );
        my ( $status, $msg ) =
          $greater_than_106
          ? Cpanel::ChangePasswd::change_cPanel_user_password( 'cpanel-ccs', $newpass )
          : Cpanel::ChangePasswd::change_password_withchpass( 'cpanel-ccs', $newpass );
        die $msg if !$status;

        # Store the password
        require Cpanel::Umask;
        my $umask_obj = Cpanel::Umask->new(0077);
        open( my $wtr, ">", $passfile ) or die "Could not open file $passfile for writing: $!";
        print $wtr $newpass;
        close($wtr);
        chmod( 0700, $passfile ) or die "Could not chmod file $passfile to mode 0700: $!";
        $ccs_password   = $newpass;
        $needs_flushing = 1;
    }

    if ($needs_flushing) {

        # Jiggle the handle
        require Cpanel::Dovecot::Utils;
        Cpanel::Dovecot::Utils::flush_auth_caches('cpanel-ccs');
    }
    return $ccs_password;
}

1;
