package Cpanel::Easy::Utils::Apache;

# cpanel - Cpanel/Easy/Utils/Apache.pm            Copyright(c) 2014 cPanel, Inc.
#                                                           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;
no warnings qw(redefine);

use Cpanel::Config          ();
use Cpanel::SafeFile        ();
use Cpanel::SafeRun::Errors ();

=pod

=head1 Description

Various utility routines specific to the Apache web server.

=head1 API

The following sections contain a description of each API in this module.

=cut

our %ItkVersion = (
    '2_2' => { dir => 'mpmitk-2.2', version => '2.2.17-01' },
);

# These MPMs are ordered to coincide with the way they're
# displayed in the UI.  By doing this, get_configured_mpm()
# will return the same one that EasyApache will load into
# Apache if multiple are selected.
my %ValidMpm = (
    '2' => {
        'MPMLeader'     => 1,
        'MPMPerchild'   => 1,
        'MPMPrefork'    => 1,
        'MPMThreadpool' => 1,
        'MPMWorker'     => 1,
    },
    '2_2' => {
        'MPMEvent'   => 1,
        'MPMPrefork' => 1,
        'MPMWorker'  => 1,
        'MPMItk'     => 1,
    },
    '2_4' => {
        'MPMEvent'   => 1,
        'MPMPrefork' => 1,
        'MPMWorker'  => 1,
    }
);

sub get_ssl_httpd_port {
    my ( $self, $fresh ) = @_;
    if ( !$fresh && defined $self->{'_'}{'cache'}{'get_ssl_httpd_port'} && $self->{'_'}{'cache'}{'get_ssl_httpd_port'} =~ m{ \A \d+ \z }xms ) {
        return $self->{'_'}{'cache'}{'get_ssl_httpd_port'};
    }
    my $cpconf = Cpanel::Config::loadcpconf();
    my $port   = '443';
    if ( defined $cpconf->{'apache_ssl_port'} ) {
        my ($tmp_port) = reverse split /:/, $cpconf->{'apache_ssl_port'};
        $port = int($tmp_port) && $tmp_port !~ m{\d+\.\d+} ? int($tmp_port) : 443;
    }
    $self->{'_'}{'cache'}{'get_ssl_httpd_port'} = $port;
    return $port;
}

sub get_std_httpd_port {
    my ( $self, $fresh ) = @_;
    if ( !$fresh && defined $self->{'_'}{'cache'}{'get_std_httpd_port'} && $self->{'_'}{'cache'}{'get_std_httpd_port'} =~ m{ \A \d+ \z }xms ) {
        return $self->{'_'}{'cache'}{'get_std_httpd_port'};
    }
    my $cpconf = Cpanel::Config::loadcpconf();
    my $port   = '443';
    if ( defined $cpconf->{'apache_port'} ) {
        my ($tmp_port) = reverse split /:/, $cpconf->{'apache_port'};
        $port = int($tmp_port) && $tmp_port !~ m{\d+\.\d+} ? int($tmp_port) : 443;
    }
    $self->{'_'}{'cache'}{'get_std_httpd_port'} = $port;
    return $port;
}

# Queries apxs for a specific query variable
sub query_apache_apxs {
    my $self = shift;
    my $var  = shift;
    my $apxs = $self->{'base_dir'} . '/bin/apxs';
    my $val;

    if ( defined $self->{'_'}{'cache'}{'apxs_vars'}{$var} ) {
        return $self->{'_'}{'cache'}{'apxs_vars'}{$var};
    }

    # NOTE: Do not cache the results using CachedCommand.  We'll
    # do that ourselves.  Reason being, we can't easily mock
    # data or empty cache results (e.g. memory).  So, we'll
    # cachy it ourselves.
    my $out = Cpanel::SafeRun::Errors::saferunnoerror( $apxs, '-q', $var );

    if ( defined $out && $out ne '' ) {
        $val = $out;
        chomp $val;
        $self->{'_'}{'cache'}{'apxs_vars'}{$var} = $val;
    }

    return $val;
}

# Retrieve current MPM in use by Apache
sub get_apache_mpm {
    my $self = shift;
    my @ret;

    # Use APXS to determine compiled MPM in Apache < 2.4
    if ( $self->{'new_httpd_vers'} eq '2_2' ) {
        my $val = $self->query_apache_apxs('MPM_NAME');

        if ($val) {
            @ret = ( 1, $val );
        }
        else {
            @ret = ( 0, "Failed to query apxs for MPM_NAME" );
        }
    }

    # Apache 2.4 made MPMs dynamically loadable, then silently
    # removed the MPM_NAME query variable.  So, now we're forced
    # to look at the httpd -V output for the _currently_ loaded
    # MPM.
    else {
        my $httpd = $self->{'base_dir'} . '/bin/httpd';
        my $out = Cpanel::SafeRun::Errors::saferunnoerror( $httpd, '-V' );

        if ( defined $out ) {

            # NOTE: As of this writing, it probably isn't beneficial to cache this
            if ( $out =~ /server\s+mpm:\s+(\w+)/i ) {
                @ret = ( 1, lc $1 );
            }
            else {
                @ret = ( 0, q{Unable to determine loaded MPM} );
            }
        }
        else {
            @ret = ( 0, q{Failed to parse httpd output for currently loaded MPM} );
        }
    }

    return @ret;
}

# Code that needs to patch Apache, often times need to know the source directory.
# Rather than guessing it in every routine, this one here is written to do handle
# all of that labor.
sub get_apache_src_dir {
    my $self           = shift;
    my $apache_version = $self->{'working_profile'}{'Apache'}{'version'};
    my $ns             = sprintf( 'Cpanel::Easy::Apache::%s', $apache_version );
    my $hr             = $self->get_easyconfig_hr_from_ns($ns);

    return sprintf( '%s/%s', $self->{'opt_mod_src_dir'}, $hr->{'src_cd2'} );
}

# chooses the correct MPM ITK directory containing source code
sub mpmitk {
    my $self  = shift;
    my $apver = $self->{'working_profile'}{'Apache'}{'version'};
    my $nfo;

    if ( defined $ItkVersion{$apver} ) {
        $nfo = $ItkVersion{$apver};
    }
    else {
        $self->print_alert("MPM ITK is not compatible with Apache $apver");
    }

    return $nfo;
}

# As of this writing, the MPM ITK is a series of patches that are
# applied to the Apache source code.  Once the patches are applied,
# you simply issue a '--with-mpm=itk'.  The author is currently
# working on a version of the MPM that can be loaded as a dynamic
# module for Apache 2.4; however it's broken.
sub apply_mpmitk_patches {
    my ( $self, $src_dir, $ap_dir ) = @_;
    local *FH;

    # the 'series' file is provided by the MPM ITK author to let the
    # person who uses it, explicitly know which order to apply patches.
    unless ( open( FH, '<', "$src_dir/series" ) ) {
        return ( 0, q{Could not open '[_1]' for reading : [_2]}, "$src_dir/series", $! );
    }

    local $_;

    while (<FH>) {
        chomp;

        # Use a minimum patch of -p1 because some of the ITK patches
        # create new files.  Since patches use a source directory of
        # 'a' and dest direcdtory of 'b', this would cause it to
        # create new files that are a sub directory of 'b'.
        my @ret = $self->apply_patch( "$src_dir/$_", $ap_dir, 1 );
        return @ret unless @ret;
    }

    close FH;

    return ( 1, q{Ok} );
}

# Determines if the profile defined in the user's profile, is
# available in EasyApache.
sub is_valid_apache_selected {
    my $self     = shift;
    my $selected = $self->{working_profile}->{Apache}->{version};
    my $ref      = $self->{state}->{optmods}->{Apache}->{_main}->{$selected};
    my @ret;

    if ( defined $ref && !$ref->{'skip'} ) {
        @ret = ( 1, q{Ok} );
    }
    else {
        # pretty the apache version
        $selected =~ tr/\_/\./;
        $selected = "1.3"         if $selected eq '1';
        $selected = "$selected.0" if $selected =~ /^\d+$/;

        my @arg = ($selected);
        my $msg = q{ERROR: Apache [_1] is not available in EasyApache.};

        if ( $ref->{'skip_reason'} ) {

            # the first string is the format string
            my @reason = @{ $ref->{'skip_reason'} };
            my $string = shift @reason;

            # the rest are the args
            $msg .= " $string";
            push @arg, @reason;
        }
        else {
            $msg .= q{ Please adjust your profile to build a supported version.};
        }

        @ret = ( 0, $msg, @arg );
    }

    return @ret;
}

=pod

=head2 Cpanel::Easy::Utils::Apache::get_configured_mpm()

Examines the user's profile and returns the Apache MPM that will
be used by EasyApache.

  Input:
    N/A

  Output:
    All lowercase string of the Apache MPM

  Notes:
    The result should adhere to the following rules:
      1. if more than one selected, select the last valid one
      2. if MPM ITK is selected, then it takes precedence
      3. if no valid MPMs selected, use prefork
      4. if no MPM selected at all, use prefork

=cut

sub get_configured_mpm {
    my $self           = shift;
    my $apache_version = $self->{'working_profile'}{'Apache'}{'version'};
    my $mpm            = 'prefork';

    my @mpm;
    local $_;

    # get a list of selected MPMs in current profile
    for ( grep( /^MPM/, keys %{ $self->{'working_profile'}{'Apache'}{'optmods'} } ) ) {
        push @mpm, $_ if $self->{'working_profile'}{'Apache'}{'optmods'}{$_};
    }

    # filter out MPMs that are invalid for the Apache version
    @mpm = grep { $ValidMpm{$apache_version}{$_} } @mpm;

    # if ITK is selected, it has precedence
    my @orig = @{ $self->{'state'}->{'order'} };
    push @orig, 'Cpanel::Easy::Apache::MPMItk' if grep( /itk/i, @mpm );

    # return the last valid AND selected MPM
    my @order = map { my $t = $_; $t =~ s/^Cpanel::Easy::Apache:://; $t } grep( /^Cpanel::Easy::Apache::MPM/, @orig );
    while ( ( my $m = pop @order ) ) {
        if ( grep( /$m/, @mpm ) ) {
            $m =~ s/^MPM(\w+)$/$1/;
            $mpm = lc $m;
            last;
        }
    }

    return $mpm;
}

1;

__END__
