#
#
#  Copyright (C) 2006-2010 Andrew Speer <andrew@webdyne.org>.
#  All rights reserved.
#
#  This file is part of WebDyne.
#
#  WebDyne is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
package WebDyne::Install::Apache::Constant;


#  Pragma
#
use strict qw(vars);


#  Vars to use
#
use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT %Constant);


#  External modules
#
use File::Find;
use File::Spec;
use IO::File;
use Cwd qw(realpath);
use Env::Path;
use WebDyne::Base;


#  Constants for our Constants module. PATH is only used as last resort
#
use constant PATH => [qw(
    /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin /opt/sbin /opt/bin)];


#  Need to null out some Fcntl functions under Win32
#
#use Fcntl;
#sub Fcntl::O_NONBLOCK { 0 }
#sub Fcntl::O_NOCTTY { 0 }


#  Local constants
#
our $Httpd_Bin=&httpd_bin();
our $Httpd_Config_hr=&httpd_config();
my  $ServerRoot;


#  Version information
#
$VERSION='1.017';


#------------------------------------------------------------------------------

#  Name of user httpd runs under
#
my ($apache_uname, $apache_gname, $apache_uid, $apache_gid);
my @apache_uname=$ENV{'APACHE_UNAME'} ||
    qw(apache apache2 www wwwrun httpd httpd2 www-data webservd);
foreach my $name (@apache_uname) {
    unless ($apache_uid || $^O=~/MSWin[32|64]/) {
	if ($apache_uid=getpwnam($name)) { $apache_uname=$name; last }
    }
}
my @apache_gname=$ENV{'APACHE_GNAME'} ||
    qw(apache apache2 www wwwrun httpd httpd2 www-data webservd);
foreach my $name (@apache_gname) {
    unless ($apache_gid || $^O=~/MSWin[32|64]/) {
	if ($apache_gid=getgrnam($name)) { $apache_gname=$name; last }
    }
}
debug("apache_uid: $apache_uid, apache_gid: $apache_gid");


#  Check we have something for Apache uname etc.
#
unless ($apache_uid || $^O=~/MSWin[32|64]/ ) {
    warn('unable to determine Apache uname - please supply via APACHE_UNAME environment variable')};
unless ($apache_gid || $^O=~/MSWin[32|64]/ ) {
    warn('unable to determine Apache gname - please supply via APACHE_GNAME environment variable')};


#  Get mod_perl file and modules library location
#
my $dir_apache_modules=&dir_apache_modules();
my $file_mod_perl_lib=&file_mod_perl_lib($dir_apache_modules);


#  Is mod_perl2/1 installed
#
my $mp2_installed=&mp2_installed();


#  Real deal
#
%Constant = (


    #  Mod_perl1/2 installed
    #
    MP2_INSTALLED			  =>  $mp2_installed,


    #  Binary
    #
    HTTPD_BIN				  =>  $Httpd_Bin,


    #  Config file templates and final names, delimiter if inserted into master httpd.conf
    #
    FILE_WEBDYNE_CONF_TEMPLATE		  =>  'webdyne.conf.inc',
    FILE_WEBDYNE_CONF			  =>  'webdyne.conf',
    FILE_APACHE_CONF_TEMPLATE		  =>  'apache.conf.inc',
    FILE_APACHE_CONF_DELIM		  =>  '#*WebDyne*',
    FILE_MOD_PERL_1_99_COMPAT		  =>  'webdyne-mod_perl-1_99-compat.pl',


    #  Get apache directory name
    #
    DIR_APACHE_CONF			  =>  &dir_apache_conf(),
    DIR_APACHE_MODULES			  =>  $dir_apache_modules,


    #  Mod_perl library name
    #
    FILE_MOD_PERL_LIB			  =>  $file_mod_perl_lib,


    #  Need apache uid and gid, as some dirs will be chowned to this
    #  at install time
    #
    APACHE_UNAME			  =>  $apache_uname,
    APACHE_GNAME			  =>  $apache_gname,
    APACHE_UID				  =>  $apache_uid,
    APACHE_GID				  =>  $apache_gid,


    #  SELinux context for cache directory
    #
    SELINUX_CONTEXT_HTTPD		  =>  'httpd_sys_content_t',
    SELINUX_CONTEXT_LIB			  =>  'lib_t',
    SELINUX_ENABLED_BIN			  =>  &find_bin('selinuxenabled'),
    SELINUX_CHCON_BIN			  =>  &find_bin('chcon'),
    SELINUX_SEMANAGE_BIN		  =>  &find_bin('semanage'),
    
    
    #  Perl shared lib files that need to be checked for SELinux context
    #
    SELINUX_SO_CHECK			  =>  {
    
      IO		=>	'IO/IO.so',
      'Digest::MD5'	=>	'Digest/MD5/MD5.so',
      Fcntl		=>	'Fcntl/Fcntl.so',
      'Time::HiRes'	=>	'Time/HiRes/HiRes.so',
      Storable		=>	'Storable/Storable.so',
      'File::Glob'	=>	'File/Glob/Glob.so',
      Opcode		=>	'Opcode/Opcode.so',
      'Data::Dumper'	=>	'Dumper/Dumper.so'
      
    },


    #  Server config
    #
    %{$Httpd_Config_hr},


   );


#  Get absolute binary file location
#
sub httpd_bin {


    #  If in Win32 need to get location of Apache from reg. Not much error checking
    #  because not fatal if reg key not found etc.
    #
    debug();
    my ($path,@name_bin);
    if ($^O=~/MSWin[32|64]/) {

	#  Windows
	#
	debug("detected MS Win: $^O");
	require Win32::TieRegistry;
	my $reg_ix=tie(
	    my %reg, 'Win32::TieRegistry', 'HKEY_LOCAL_MACHINE\Software\Apache Group\Apache');
	my $version=(sort {$b cmp $a} keys %reg)[0];
	unless ($version) {
	    $reg_ix=tie(
		%reg, 'Win32::TieRegistry', 'HKEY_LOCAL_MACHINE\Software\Apache Software Foundation\Apache');
	    $version=(sort {$b cmp $a} keys %reg)[0];
	}
	debug("registry says version: $version");
	$path=($ServerRoot=$reg{$version}->{'ServerRoot'}); #||
	    # last resorts. blech
	    #'C:\Apache;C:\Apache~1;C:\Apache2;C:\Apache2.2;C:\Progra~1\Apache;C:\Progra~1\Apache2;C:\Progra~1\Apache~1;'.
		#'D:\Apache;D:\Apache~1;D:\Apache2;D:\Apache2.2;D:\Progra~1\Apache;D:\Progra~1\Apache2;D:\Progra~1\Apache~1;'.
		    #'E:\Apache;E:\Apache~1;E:\Apache2;E:\Apache2.2;E:\Progra~1\Apache;E:\Progra~1\Apache2;E:\Progra~1\Apache~1;';

	#  Some Apache distro's use '/bin/' after ServerRoot
	#
	if ($path) { $path=join(Env::Path->PathSeparator, File::Spec->catdir($path, 'bin')) }

	#  Various names for Apache under Windows
	#
	@name_bin=qw(Apache.exe Apache2.exe httpd.exe httpd2.exe);
    }
    else {

    	#  Add some hard coded paths as last resort options, will work if su'd to root
    	#  without getting root's path
    	#
	$path=join(Env::Path->PathSeparator, $ENV{'PATH'} , @{+PATH});;
	@name_bin=qw(httpd httpd2 httpd2.2 apache apache2 apache2.2);
    }
    debug("apache final search path: '$path'");
    debug('apache names %s', Dumper(\@name_bin));


    #  Find the httpd bin file
    #
    my $httpd_bin;
    unless ($httpd_bin=$ENV{'HTTPD_BIN'}) {

	my @dir=grep { -d $_ } split(Env::Path->PathSeparator, $path);
	my %dir=map { $_=> 1} @dir;
	DIR: foreach my $dir (@dir) {
	    next unless delete $dir{$dir};
	    next unless -d $dir;
	    debug("searching dir: $dir");
	    foreach my $name_bin (@name_bin) {
		if (-f File::Spec->catfile($dir, $name_bin)) {
		    $httpd_bin=File::Spec->catfile($dir, $name_bin);
		    last DIR;
		}
	    }
	}
    }
    debug("httpd_bin returning: $httpd_bin");


    #  Warn if unable to find
    #
    unless (-f $httpd_bin) {
	warn('unable to find/determine Apache binary location - please supply via HTTPD_BIN environment variable');
    }


    #  Return
    #
    return File::Spec->canonpath($httpd_bin);

}


sub find_bin {

    my $bin=shift();
    my $path=join(Env::Path->PathSeparator, $ENV{'PATH'} , @{+PATH});
    debug("find_bin $bin, path: $path");
    my $fn;
    unless ($fn=$ENV{"${bin}_BIN"}) {
	my @dir=grep { -d $_ } split(Env::Path->PathSeparator, $path);
	my %dir=map { $_=> 1} @dir;
	foreach my $dir (@dir) {
	    next unless delete $dir{$dir};
	    next unless -d $dir;
	    debug("searching dir: $dir");
	    if (-f File::Spec->catfile($dir, $bin)) {
		$fn=File::Spec->catfile($dir, $bin); last;
	    }
	}
   }


    #  Return
    #
    debug("find_bin returning: $fn");
    return $fn ? File::Spec->canonpath($fn) : undef;

}


sub httpd_config {


    #  Return if no Httpd_Bin, means apache binary not found
    #
    debug();
    return unless $Httpd_Bin;


    #  Need to get httpd config as series of key/val pairs
    #
    my %config;
    my @httpd_config=qx(\"$Httpd_Bin\" -V);
    debug('httpd_config: %s', Dumper(\@httpd_config));


    #  Go through
    #
    foreach my $httpd_config (@httpd_config) {

	next unless ($httpd_config=~/\s*\-D\s*(.*)/);
	my ($key, $value)=split(/\=/, $1);
	$key=~s/\s+.*$//g;
	$value ||= '';
	$value=~s/^\"//;
	$value=~s/\"$//;
	$key=~/^HTTPD/ || ($key="HTTPD_${key}");
	$config{$key}=$value || 1;

    }


    #  In windows HTTPD_ROOT does not always match actual installed root path,
    #  so override if possible
    #
    my $httpd_dn=($config{'HTTPD_ROOT'}=$ServerRoot || $config{'HTTPD_ROOT'});


    #  Last resort - if does not exist use apache bin location as ref for
    #  HTTPD_ROOT
    #
    unless (-d $httpd_dn) {
	debug('using last resort to find httpd_dn');
	$config{'HTTPD_ROOT'}=do {
	    my $dn=File::Spec->catdir(
		(File::Spec->splitpath($Httpd_Bin))[1],
		File::Spec->updir());
            (-d $dn) && ($dn=realpath($dn));
            $dn;
        };
    }
    debug('httpd_dn set to: %s', $config{'HTTPD_ROOT'});


    #  And return the config
    #
    return \%config;

}


sub dir_apache_conf {


    #  Get Apache config dir, ensure is absolute
    #
    debug();
    my $apache_conf_dn;
    unless ($apache_conf_dn=$ENV{'DIR_APACHE_CONF'}) {

	$apache_conf_dn=$Httpd_Config_hr->{'HTTPD_SERVER_CONFIG_FILE'};
	my $apache_conf_fn=(File::Spec->splitpath($apache_conf_dn))[2];
	$apache_conf_dn=~s/\Q$apache_conf_fn\E$//;

	#$apache_conf_dn=(File::Spec->splitpath(
	#    $Httpd_Config_hr->{'HTTPD_SERVER_CONFIG_FILE'}))[1];
	debug("apache_conf_fn initital value: $apache_conf_dn");
	unless ($apache_conf_dn=~/^\//) {

	    debug("apache_conf_dn path appears relative - prepending HTTPD_ROOT");
	    $apache_conf_dn=File::Spec->catdir(
		$Httpd_Config_hr->{'HTTPD_ROOT'},$apache_conf_dn);

	}
	else {
	    debug('apache_conf_dn appears fully qualified - leaving intact');
	}


	#  Check for ../conf.d path
	#
	foreach my $dn ('conf.d', File::Spec->catdir(File::Spec->updir(), 'conf.d')) {
	    debug("looking for conf.d path: $dn");
	    my $test_dn=File::Spec->canonpath(
		File::Spec->catdir($apache_conf_dn, $dn));
	    debug("testing: $test_dn");
	    if (-d $test_dn) {
		$apache_conf_dn=realpath($test_dn);
		debug("found, setting apache_conf_dn: $apache_conf_dn");
		$Httpd_Config_hr->{'HTTPD_SERVER_CONFIG_SKIP'}=1;
	    }
	    else {
		debug('not found');
	    }
	}
    }
    else {
	debug('setting apache_conf_dn from %ENV');
    }
    debug("apache_conf_dn set to: $apache_conf_dn");


    #  Warn if not found
    #
    unless (-d $apache_conf_dn) {
	warn('unable to find/determine Apache conf directory - please supply via DIR_APACHE_CONF environment variable');
    }


    #  Return it
    #
    return $apache_conf_dn;

}


sub dir_apache_modules {


    #  Get Apache config dir, ensure is absolute
    #
    debug();
    my @dn=(
	File::Spec->catdir(
	    $Httpd_Config_hr->{'HTTPD_ROOT'}, 'modules'),
	File::Spec->catdir(
	    $Httpd_Config_hr->{'HTTPD_ROOT'}, 'libexec'),
	# last resorts, blech
	'/usr/lib/apache2/modules',
	'/usr/lib/apache2.2/modules',
	'/usr/lib/httpd/modules',
	'/usr/lib/apache2',
	'/usr/lib/apache2.2',
	'/usr/local/lib',
	'/usr/local/lib64',
	'/usr/lib',
	'/usr/lib64'
       );


    #  Check for modules
    #
    my $apache_modules_dn;
    unless ($apache_modules_dn=$ENV{'DIR_APACHE_MODULES'}) {
	foreach my $dn (@dn) {
	    debug("looking for path: $dn");
	    if (-d $dn) {
		$apache_modules_dn=$dn;
		debug("found, setting apache_modules_dn to: $dn");
		last;
	    }
	}
	$apache_modules_dn ||= 'modules';
    }
    else {
	debug('setting apache_modules_dn from %ENV');
    }
    debug("apache_modules_dn set to: $apache_modules_dn");


    #  Warn if not found. Update - don't do this anymore, just warn later if mod_perl lib not found, which
    #  used this routine to find potential module storage dir.
    #
    #unless (-d $apache_modules_dn) {
	#warn('unable to find/determine Apache modules directory - please supply via DIR_APACHE_MODULES environment variable');
    #}


    #  Return it
    #
    return $apache_modules_dn;

}


sub file_mod_perl_lib {


    #  Get the name of the mod_perl library
    #
    my $dn=shift();
    debug("looking for mod_perl dn: $dn");
    my $mod_perl_cn;
    unless ($mod_perl_cn=$ENV{'FILE_MOD_PERL_LIB'}) {
	foreach my $fn (qw(libperl mod_perl mod_perl2 mod_perl2.2 mod_perl-2 mod_perl-2.2)) {
	    foreach my $ext (qw(so dll)) {
		my $cn=File::Spec->catfile($dn, "$fn.$ext");
		debug("looking for cn: $cn");
	  	if (-f $cn) {
		    $mod_perl_cn=$cn;
		    last;
		}
		else {
		    debug('not found');
		}
	    }
	    last if $mod_perl_cn;
	}
    }
    else {
	debug("setting mod_perl_cn from %ENV")
    }
    debug("mod_perl_cn set to: $mod_perl_cn");


    unless (-f $mod_perl_cn) {
	warn('unable to find/determine mod_perl library - please supply via FILE_MOD_PERL_LIB environment variable');
    }
    return $mod_perl_cn;

}


sub mp2_installed {

    local $SIG{__DIE__};
    eval { require Apache2 };
    eval { require mod_perl };
    eval { require mod_perl2 };
    eval { undef } if $@;
    my $mp2_installed;
    if (($mod_perl::VERSION || $mod_perl2::VERSION || $ENV{MOD_PERL_API_VERSION}) >= 1.99) {
        $mp2_installed=1;
    }
    elsif (($mod_perl::VERSION || $mod_perl2::VERSION || $ENV{MOD_PERL_API_VERSION}) >= 1) {
        $mp2_installed=0
    }
    elsif ( $file_mod_perl_lib ) {
        # Last resort - mod_perl libary exists, but we couldn't load 
        #
        $mp2_installed=0;
        my $version=qx($Httpd_Bin -v);
        if ($version=~/(\d+)\.(\d+)/) {
            $version=$1.$2;
            if ($version >=2) {
                $mp2_installed=1;
            }
        }
    }
    debug("mp2_installed: $mp2_installed");
    
    return $mp2_installed;
    
}


#  Finalise and export vars
#
debug('final constants: %s', Dumper(\%Constant));
require Exporter;
require WebDyne::Constant;
@ISA=qw(Exporter WebDyne::Constant);
#  Local constants override globals
+__PACKAGE__->local_constant_load(\%Constant);
foreach (keys %Constant) { ${$_}=$Constant{$_} }
@EXPORT=map { '$'.$_ } keys %Constant;
@EXPORT_OK=@EXPORT;
%EXPORT_TAGS=(all => [@EXPORT_OK]);
$_=\%Constant;
