--- doc/install/linux/install.pl 2011/05/31 13:29:46 1.19 +++ doc/install/linux/install.pl 2019/10/30 03:42:05 1.59 @@ -26,7 +26,12 @@ use strict; use File::Copy; use Term::ReadKey; +use Socket; +use Sys::Hostname::FQDN(); use DBI; +use Cwd(); +use File::Basename(); +use lib File::Basename::dirname(Cwd::abs_path($0)); use LCLocalization::localize; # ========================================================= The language handle @@ -72,7 +77,7 @@ if (!open(LOG,">>loncapa_install.log")) &mt('Stopping execution.')."\n"; exit; } else { - print LOG '$Id: install.pl,v 1.19 2011/05/31 13:29:46 raeburn Exp $'."\n"; + print LOG '$Id: install.pl,v 1.59 2019/10/30 03:42:05 raeburn Exp $'."\n"; } # @@ -159,9 +164,20 @@ sub get_user_selection { } sub get_distro { - my ($distro,$gotprereqs,$updatecmd,$packagecmd,$installnow); + my ($distro,$gotprereqs,$updatecmd,$packagecmd,$installnow,$unknown); $packagecmd = '/bin/rpm -q LONCAPA-prerequisites '; - if (-e '/etc/redhat-release') { + if (-e '/etc/oracle-release') { + open(IN,'; + chomp($versionstring); + close(IN); + if ($versionstring =~ /^Oracle Linux Server release (\d+)/) { + my $version = $1; + $distro = 'oracle'.$1; + $updatecmd = 'yum install LONCAPA-prerequisites'; + $installnow = 'yum -y install LONCAPA-prerequisites'; + } + } elsif (-e '/etc/redhat-release') { open(IN,'; chomp($versionstring); @@ -191,11 +207,15 @@ sub get_distro { $distro = 'rhes'.$1; $updatecmd = 'yum install LONCAPA-prerequisites'; $installnow = 'yum -y install LONCAPA-prerequisites'; - } elsif ($versionstring =~ /CentOS release (\d+)/) { + } elsif ($versionstring =~ /Red Hat Enterprise Linux release (\d+)/) { + $distro = 'rhes'.$1; + $updatecmd = 'dnf install LONCAPA-prerequisites'; + $installnow = 'dnf -y install LONCAPA-prerequisites'; + } elsif ($versionstring =~ /CentOS(?:| Linux) release (\d+)/) { $distro = 'centos'.$1; $updatecmd = 'yum install LONCAPA-prerequisites'; $installnow = 'yum -y install LONCAPA-prerequisites'; - } elsif ($versionstring =~ /Scientific Linux (SL )?release ([\d.]+) /) { + } elsif ($versionstring =~ /Scientific Linux (?:SL )?release ([\d.]+) /) { my $ver = $1; $ver =~ s/\.\d+$//; $distro = 'scientific'.$ver; @@ -204,6 +224,7 @@ sub get_distro { } else { print &mt('Unable to interpret [_1] to determine system type.', '/etc/redhat-release')."\n"; + $unknown = 1; } } elsif (-e '/etc/SuSE-release') { open(IN,'; chomp($versionstring); close(IN); - $packagecmd = '/usr/bin/dpkg -l loncapa-prerequisites '; - $updatecmd = 'apt-get install loncapa-prerequisites'; if ($versionstring =~ /^Ubuntu (\d+)\.\d+/i) { $distro = 'ubuntu'.$1; $updatecmd = 'sudo apt-get install loncapa-prerequisites'; } elsif ($versionstring =~ /^Debian\s+GNU\/Linux\s+(\d+)\.\d+/i) { $distro = 'debian'.$1; + $updatecmd = 'apt-get install loncapa-prerequisites'; } elsif (-e '/etc/debian_version') { open(IN,'; @@ -250,13 +271,15 @@ sub get_distro { close(IN); if ($version =~ /^(\d+)\.\d+\.?\d*/) { $distro='debian'.$1; + $updatecmd = 'apt-get install loncapa-prerequisites'; } else { print &mt('Unable to interpret [_1] to determine system type.', '/etc/debian_version')."\n"; + $unknown = 1; } - } else { - print &mt('Unable to interpret [_1] to determine system type.', - '/etc/issue')."\n"; + } + if ($distro ne '') { + $packagecmd = '/usr/bin/dpkg -l loncapa-prerequisites '; } } elsif (-e '/etc/debian_version') { open(IN,') { + chomp(); + if (/^ID="(\w+)"/) { + $id=$1; + } elsif (/^VERSION_ID="([\d\.]+)"/) { + $version=$1; + } + } + close(IN); + if ($id eq 'sles') { + my ($major,$minor) = split(/\./,$version); + if ($major =~ /^\d+$/) { + $distro = $id.$major; + $updatecmd = 'zypper install LONCAPA-prerequisites'; + } + } + } + if ($distro eq '') { + print &mt('Unable to interpret [_1] to determine system type.', + '/etc/os-release')."\n"; + $unknown = 1; + } + } else { + print &mt('Unknown installation: expecting a debian, ubuntu, suse, sles, redhat, fedora, scientific linux, or oracle linux system.')."\n"; } - } else { - print &mt('Unknown installation: expecting a debian, ubuntu, suse, sles, redhat, fedora or scientific linux system.')."\n"; } return ($distro,$packagecmd,$updatecmd,$installnow); } +# +# get_hostname() prompts the user to provide the server's hostname. +# +# If invalid input is provided, the routine is called recursively +# until, a valid hostname is provided. +# + +sub get_hostname { + my $hostname; + print &mt('Enter the hostname of this server, e.g., loncapa.somewhere.edu'."\n"); + my $choice = ; + chomp($choice); + $choice =~ s/(^\s+|\s+$)//g; + if ($choice eq '') { + print &mt("Hostname you entered was either blank or contanied only white space.\n"); + } elsif ($choice =~ /^[\w\.\-]+$/) { + $hostname = $choice; + } else { + print &mt("Hostname you entered was invalid -- a hostname may only contain letters, numbers, - and .\n"); + } + while ($hostname eq '') { + $hostname = &get_hostname(); + } + print "\n"; + return $hostname; +} + +# +# get_hostname() prompts the user to provide the server's IPv4 IP address +# +# If invalid input is provided, the routine is called recursively +# until, a valid IPv4 address is provided. +# + +sub get_hostip { + my $hostip; + print &mt('Enter the IP address of this server, e.g., 192.168.10.24'."\n"); + my $choice = ; + chomp($choice); + $choice =~ s/(^\s+|\s+$)//g; + my $badformat = 1; + if ($choice eq '') { + print &mt("IP address you entered was either blank or contained only white space.\n"); + } else { + if ($choice =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) { + if (($1<=255) && ($2<=255) && ($3<=255) && ($4<=255)) { + $badformat = 0; + } + } + if ($badformat) { + print &mt('Host IP you entered was invalid -- a host IP has the format d.d.d.d where each d is an integer between 0 and 255')."\n"; + } else { + $hostip = $choice; + } + } + while ($hostip eq '') { + $hostip = &get_hostip(); + } + print "\n"; + return $hostip; +} + sub check_prerequisites { my ($packagecmd,$distro) = @_; my $gotprereqs; @@ -314,12 +428,39 @@ sub check_locale { print &mt('Failed to open: [_1], default locale not checked.', '/etc/default/locale'); } - } elsif ($distro =~ /^(suse|sles)/) { - if (!open($fh,"= 15)) { + if (!open($fh,"= 18) { + if (!open($fh,"= 7) { + if (!open($fh,"; chomp(@data); foreach my $item (@data) { - if ($item =~ /^\Q$langvar\E=\"([^\"]*)\"/) { + if ($item =~ /^\Q$langvar\E=\"?([^\"]*)\"?/) { my $default = $1; if ($default ne 'en_US.UTF-8') { if ($distro =~ /^debian/) { - $command = 'dpkg-reconfigure locales'; + $command = 'locale-gen en_US.UTF-8'."\n". + 'update-locale LANG=en_US.UTF-8'; } elsif ($distro =~ /^ubuntu/) { - $command = 'sudo set-language-env -E'; + $command = 'sudo locale-gen en_US.UTF-8'."\n". + 'sudo update-locale LANG=en_US.UTF-8'; } elsif ($distro =~ /^(suse|sles)/) { - $command = 'yast language'; - } else { + $command = 'yast language'; + } elsif (-e '/usr/bin/system-config-language') { $command = 'system-config-language'; + } elsif (-e '/usr/bin/localectl') { + $command = '/usr/bin/localectl set-locale LANG=en_US.UTF-8'; + } else { + $command = 'No standard command found'; } } last; @@ -357,14 +504,15 @@ sub check_required { } my $gotprereqs = &check_prerequisites($packagecmd,$distro); if ($gotprereqs eq '') { - return ($distro,$gotprereqs); + return ($distro,$gotprereqs,'',$packagecmd,$updatecmd); } my $localecmd = &check_locale($distro); unless ($localecmd eq '') { return ($distro,$gotprereqs,$localecmd); } - my ($mysqlon,$mysqlsetup,$dbh,$has_pass,$has_lcdb,%recommended,$downloadstatus, - $filetouse,$production,$testing,$apachefw,$tostop); + my ($mysqlon,$mysqlsetup,$mysqlrestart,$dbh,$has_pass,$has_lcdb,%recommended, + $downloadstatus,$filetouse,$production,$testing,$apachefw,$tostop, + $uses_systemctl,$hostname,$hostip); my $wwwuid = &uid_of_www(); my $wwwgid = getgrnam('www'); if (($wwwuid eq '') || ($wwwgid eq '')) { @@ -373,38 +521,72 @@ sub check_required { unless( -e "/usr/local/sbin/pwauth") { $recommended{'pwauth'} = 1; } + $hostname = Sys::Hostname::FQDN::fqdn(); + if ($hostname eq '') { + $hostname =&get_hostname(); + } else { + print &mt("Hostname detected: $hostname. Is that correct? ~[Y/n~]"); + if (!&get_user_selection(1)) { + $hostname =&get_hostname(); + } + } + $hostip = Socket::inet_ntoa(scalar(gethostbyname($hostname)) || 'localhost'); + if ($hostip eq '') { + $hostip=&get_hostip(); + } else { + print &mt("Host IP address detected: $hostip. Is that correct? ~[Y/n~]"); + if (!&get_user_selection(1)) { + $hostip=&get_hostip(); + } + } + print_and_log("\n".&mt('Hostname is [_1] and IP address is [_2]',$hostname,$hostip)."\n"); $mysqlon = &check_mysql_running($distro); if ($mysqlon) { my $mysql_has_wwwuser = &check_mysql_wwwuser(); - ($mysqlsetup,$has_pass,$dbh) = - &check_mysql_setup($instdir,$dsn); - if ($mysqlsetup eq 'noroot') { - $recommended{'mysqlperms'} = 1; + ($mysqlsetup,$has_pass,$dbh,$mysql_has_wwwuser) = + &check_mysql_setup($instdir,$dsn,$distro,$mysql_has_wwwuser); + if ($mysqlsetup eq 'needsrestart') { + $mysqlrestart = ''; + if ($distro eq 'ubuntu') { + $mysqlrestart = 'sudo '; + } + $mysqlrestart .= 'service mysql restart'; + return ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow,$mysqlrestart); } else { - unless ($mysql_has_wwwuser) { + if ($mysqlsetup eq 'noroot') { $recommended{'mysqlperms'} = 1; + } else { + unless ($mysql_has_wwwuser) { + $recommended{'mysqlperms'} = 1; + } + } + if ($dbh) { + $has_lcdb = &check_loncapa_mysqldb($dbh); + } + unless ($has_lcdb) { + $recommended{'mysql'} = 1; } - } - if ($dbh) { - $has_lcdb = &check_loncapa_mysqldb($dbh); - } - unless ($has_lcdb) { - $recommended{'mysql'} = 1; } } + my ($sslhostsfilesref,$has_std,$has_int,$rewritenum,$nochgstd,$nochgint); ($recommended{'firewall'},$apachefw) = &chkfirewall($distro); - ($recommended{'runlevels'},$tostop) = &chkconfig($distro,$instdir); + ($recommended{'runlevels'},$tostop,$uses_systemctl) = &chkconfig($distro,$instdir); $recommended{'apache'} = &chkapache($distro,$instdir); + ($recommended{'apachessl'},$sslhostsfilesref,$has_std,$has_int,$rewritenum, + $nochgstd,$nochgint) = &chkapachessl($distro,$instdir,$hostname,$hostip); $recommended{'stopsrvcs'} = &chksrvcs($distro,$tostop); ($recommended{'download'},$downloadstatus,$filetouse,$production,$testing) = &need_download(); return ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow, - \%recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus, - $filetouse,$production,$testing,$apachefw); + $mysqlrestart,\%recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus, + $filetouse,$production,$testing,$apachefw,$uses_systemctl,$hostname, + $hostip,$sslhostsfilesref,$has_std,$has_int,$rewritenum,$nochgstd, + $nochgint); } sub check_mysql_running { my ($distro) = @_; + my $use_systemctl; my $mysqldaemon ='mysqld'; if ($distro =~ /^(suse|sles|debian|ubuntu)/) { $mysqldaemon = 'mysql'; @@ -416,8 +598,37 @@ sub check_mysql_running { $process = 'mysqld'; $proc_owner = 'mysql'; } + } elsif ($distro =~ /^fedora(\d+)/) { + if ($1 >= 16) { + $process = 'mysqld'; + $proc_owner = 'mysql'; + $use_systemctl = 1; + } + if ($1 >= 19) { + $mysqldaemon ='mariadb'; + } + } elsif ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)/) { + if ($1 >= 7) { + $mysqldaemon ='mariadb'; + $process = 'mysqld'; + $proc_owner = 'mysql'; + $use_systemctl = 1; + } + } elsif ($distro =~ /^sles(\d+)/) { + if ($1 >= 12) { + $use_systemctl = 1; + $proc_owner = 'mysql'; + $process = 'mysqld'; + } + if ($1 >= 15) { + $mysqldaemon ='mariadb'; + } + } elsif ($distro =~ /^suse(\d+)/) { + if ($1 >= 13) { + $use_systemctl = 1; + } } - if (open(PIPE,"ps -ef |grep $process |grep -v grep 2>&1 |")) { + if (open(PIPE,"ps -ef |grep $process |grep ^$proc_owner |grep -v grep 2>&1 |")) { my $status = ; close(PIPE); chomp($status); @@ -425,7 +636,11 @@ sub check_mysql_running { print_and_log(&mt('MySQL is running.')."\n"); return 1; } else { - system("/etc/init.d/$mysqldaemon start >/dev/null 2>&1 "); + if ($use_systemctl) { + system("/bin/systemctl start $mysqldaemon.service >/dev/null 2>&1 "); + } else { + system("/etc/init.d/$mysqldaemon start >/dev/null 2>&1 "); + } print_and_log(&mt('Waiting for MySQL to start.')."\n"); sleep 5; if (open(PIPE,"ps -ef |grep $process |grep -v grep 2>&1 |")) { @@ -460,8 +675,9 @@ sub check_mysql_running { sub chkconfig { my ($distro,$instdir) = @_; - my (%needfix,%tostop); + my (%needfix,%tostop,%uses_systemctl); my $checker_bin = '/sbin/chkconfig'; + my $sysctl_bin = '/bin/systemctl'; my %daemon = ( mysql => 'mysqld', apache => 'httpd', @@ -480,77 +696,146 @@ sub chkconfig { if ($distro =~ /^(suse|sles)9/) { $daemon{'apache'} = 'apache'; } + if ($distro =~ /^(suse|sles)([\d\.]+)/) { + my $name = $1; + my $num = $2; + if ($num > 11) { + $uses_systemctl{'apache'} = 1; + if (($name eq 'sles') || ($name eq 'suse' && $num >= 13.2)) { + $uses_systemctl{'mysql'} = 1; + $uses_systemctl{'ntp'} = 1; + $uses_systemctl{'cups'} = 1; + $uses_systemctl{'memcached'} = 1; + if (($name eq 'sles') && ($num >= 15)) { + $daemon{'ntp'} = 'chronyd'; + $daemon{'mysql'} = 'mariadb'; + } else { + $daemon{'ntp'} = 'ntpd'; + } + } + } + } } elsif ($distro =~ /^(?:debian|ubuntu)(\d+)/) { my $version = $1; @runlevels = qw/2 3 4 5/; @norunlevels = qw/0 1 6/; - $checker_bin = '/usr/sbin/sysv-rc-conf'; + if (($distro =~ /^ubuntu/) && ($version <= 16)) { + $checker_bin = '/usr/sbin/sysv-rc-conf'; + } else { + $uses_systemctl{'ntp'} = 1; + $uses_systemctl{'mysql'} = 1; + $uses_systemctl{'apache'} = 1; + $uses_systemctl{'memcached'} = 1; + $uses_systemctl{'cups'} = 1; + } $daemon{'mysql'} = 'mysql'; $daemon{'apache'} = 'apache2'; $daemon{'ntp'} = 'ntp'; if (($distro =~ /^ubuntu/) && ($version <= 8)) { $daemon{'cups'} = 'cupsys'; } + } elsif ($distro =~ /^fedora(\d+)/) { + my $version = $1; + if ($version >= 15) { + $uses_systemctl{'ntp'} = 1; + } + if ($version >= 16) { + $uses_systemctl{'mysql'} = 1; + $uses_systemctl{'apache'} = 1; + $uses_systemctl{'memcached'} = 1; + $uses_systemctl{'cups'} = 1; + } + if ($version >= 19) { + $daemon{'mysql'} = 'mariadb'; + } + if ($version >= 26) { + $daemon{'ntp'} = 'chronyd'; + } + } elsif ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)/) { + my $version = $1; + if ($version >= 7) { + $uses_systemctl{'ntp'} = 1; + $uses_systemctl{'mysql'} = 1; + $uses_systemctl{'apache'} = 1; + $uses_systemctl{'memcached'} = 1; + $uses_systemctl{'cups'} = 1; + $daemon{'mysql'} = 'mariadb'; + } + if (($version >= 8) || ($distro eq 'oracle7')) { + $daemon{'ntp'} = 'chronyd'; + } } + my $nocheck; if (! -x $checker_bin) { + if ($uses_systemctl{'mysql'} && $uses_systemctl{'apache'}) { + if (! -x $sysctl_bin) { + $nocheck = 1; + } + } else { + $nocheck = 1; + } + } + if ($nocheck) { print &mt('Could not check runlevel status for MySQL or Apache')."\n"; return; } my $rlstr = join('',@runlevels); my $nrlstr = join('',@norunlevels); + foreach my $type ('apache','mysql','ntp','cups','memcached') { my $service = $daemon{$type}; - if ($type eq 'ntp') { - if ($distro =~ /^(?:fedora)(\d+)/) { - my $version = $1; - if ($version >= 15) { - if (!-l "/etc/systemd/system/multi-user.target.wants/ntpd.service") { - $needfix{$type} = 'systemctl enable ntpd.service'; - } - next; + if ($uses_systemctl{$type}) { + if (($type eq 'memcached') || ($type eq 'cups')) { + if (-l "/etc/systemd/system/multi-user.target.wants/$service.service") { + $tostop{$type} = 1; } - } - } - my $command = $checker_bin.' --list '.$service.' 2>/dev/null'; - if ($type eq 'cups') { - if ($distro =~ /^(?:debian|ubuntu)(\d+)/) { - my $version = $1; - if (($distro =~ /^ubuntu/) && ($version <= 8)) { - $command = $checker_bin.' --list cupsys 2>/dev/null'; - } - } - } - my $results = `$command`; - my $tofix; - if ($results eq '') { - if (($type eq 'apache') || ($type eq 'mysql') || ($type eq 'ntp')) { - if ($distro =~ /^(debian|ubuntu)/) { - $tofix = "update-rc.d $type defaults"; - } else { - $tofix = "$checker_bin --add $service\n"; + } else { + if (!-l "/etc/systemd/system/multi-user.target.wants/$service.service") { + $needfix{$type} = "systemctl enable $service.service"; } } } else { - my %curr_runlevels; - for (my $rl=0; $rl<=6; $rl++) { - if ($results =~ /$rl:on/) { $curr_runlevels{$rl}++; } + my $command = $checker_bin.' --list '.$service.' 2>/dev/null'; + if ($type eq 'cups') { + if ($distro =~ /^(?:debian|ubuntu)(\d+)/) { + my $version = $1; + if (($distro =~ /^ubuntu/) && ($version <= 8)) { + $command = $checker_bin.' --list cupsys 2>/dev/null'; + } + } } - if (($type eq 'apache') || ($type eq 'mysql') || ($type eq 'ntp')) { - my $warning; - foreach my $rl (@runlevels) { - if (!exists($curr_runlevels{$rl})) { - $warning = 1; + my $results = `$command`; + my $tofix; + if ($results eq '') { + if (($type eq 'apache') || ($type eq 'mysql') || ($type eq 'ntp')) { + if ($distro =~ /^(debian|ubuntu)/) { + $tofix = "update-rc.d $type defaults"; + } else { + $tofix = "$checker_bin --add $service\n"; } } - if ($warning) { - $tofix = "$checker_bin --level $rlstr $service on\n"; + } else { + my %curr_runlevels; + for (my $rl=0; $rl<=6; $rl++) { + if ($results =~ /$rl:on/) { $curr_runlevels{$rl}++; } + } + if (($type eq 'apache') || ($type eq 'mysql') || ($type eq 'ntp')) { + my $warning; + foreach my $rl (@runlevels) { + if (!exists($curr_runlevels{$rl})) { + $warning = 1; + } + } + if ($warning) { + $tofix = "$checker_bin --level $rlstr $service on\n"; + } + } elsif (keys(%curr_runlevels) > 0) { + $tostop{$type} = 1; } - } elsif (keys(%curr_runlevels) > 0) { - $tostop{$type} = 1; } - } - if ($tofix) { - $needfix{$type} = $tofix; + if ($tofix) { + $needfix{$type} = $tofix; + } } } if ($distro =~ /^(suse|sles)([\d\.]+)$/) { @@ -562,13 +847,59 @@ sub chkconfig { } else { $major = $version; } - if ($major > 10) { + if (($major > 10) && ($major <= 13)) { if (&check_SuSEfirewall2_setup($instdir)) { $needfix{'insserv'} = 1; } } } - return (\%needfix,\%tostop); + return (\%needfix,\%tostop,\%uses_systemctl); +} + +sub uses_firewalld { + my ($distro) = @_; + my ($inuse,$checkfirewalld,$zone); + if ($distro =~ /^(suse|sles)([\d\.]+)$/) { + if (($1 eq 'sles') && ($2 >= 15)) { + $checkfirewalld = 1; + } + } elsif ($distro =~ /^fedora(\d+)$/) { + if ($1 >= 18) { + $checkfirewalld = 1; + } + } elsif ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)/) { + if ($1 >= 7) { + $checkfirewalld = 1; + } + } + if ($checkfirewalld) { + my ($loaded,$active); + if (open(PIPE,"systemctl status firewalld |")) { + while () { + chomp(); + if (/^\s*Loaded:\s+(\w+)/) { + $loaded = $1; + } + if (/^\s*Active\s+(\w+)/) { + $active = $1; + } + } + close(PIPE); + } + if (($loaded eq 'loaded') || ($active eq 'active')) { + $inuse = 1; + my $cmd = 'firewall-cmd --get-default-zone'; + if (open(PIPE,"$cmd |")) { + my $result = ; + chomp($result); + close(PIPE); + if ($result =~ /^\w+$/) { + $zone = $result; + } + } + } + } + return ($inuse,$zone); } sub chkfirewall { @@ -579,30 +910,44 @@ sub chkfirewall { https => 443, ); my %activefw; - if (&firewall_is_active()) { - my $iptables = &get_pathto_iptables(); - if ($iptables eq '') { - print &mt('Firewall not checked as path to iptables not determined.')."\n"; - } else { - my @fwchains = &get_fw_chains($iptables,$distro); - if (@fwchains) { - foreach my $service ('http','https') { - foreach my $fwchain (@fwchains) { - if (&firewall_is_port_open($iptables,$fwchain,$ports{$service})) { - $activefw{$service} = 1; - last; + my ($firewalld,$zone) = &uses_firewalld($distro); + if ($firewalld) { + my %current; + if (open(PIPE,'firewall-cmd --permanent --zone='.$zone.' --list-services |')) { + my $svc = ; + close(PIPE); + chomp($svc); + map { $current{$_} = 1; } (split(/\s+/,$svc)); + } + if ($current{'http'} && $current{'https'}) { + $configfirewall = 0; + } + } else { + if (&firewall_is_active()) { + my $iptables = &get_pathto_iptables(); + if ($iptables eq '') { + print &mt('Firewall not checked as path to iptables not determined.')."\n"; + } else { + my @fwchains = &get_fw_chains($iptables,$distro); + if (@fwchains) { + foreach my $service ('http','https') { + foreach my $fwchain (@fwchains) { + if (&firewall_is_port_open($iptables,$fwchain,$ports{$service})) { + $activefw{$service} = 1; + last; + } } } + if ($activefw{'http'}) { + $configfirewall = 0; + } + } else { + print &mt('Firewall not checked as iptables Chains not identified.')."\n"; } - if ($activefw{'http'}) { - $configfirewall = 0; - } - } else { - print &mt('Firewall not checked as iptables Chains not identified.')."\n"; } + } else { + print &mt('Firewall not enabled.')."\n"; } - } else { - print &mt('Firewall not enabled.')."\n"; } return ($configfirewall,\%activefw); } @@ -610,17 +955,47 @@ sub chkfirewall { sub chkapache { my ($distro,$instdir) = @_; my $fixapache = 1; - if ($distro =~ /^(debian|ubuntu)/) { - if (!-e "$instdir/debian-ubuntu/loncapa") { + if ($distro =~ /^(debian|ubuntu)(\d+)$/) { + my $distname = $1; + my $version = $2; + my ($stdconf,$stdsite); + if (($distname eq 'ubuntu') && ($version > 12)) { + $stdconf = "$instdir/debian-ubuntu/ubuntu14/loncapa_conf"; + $stdsite = "$instdir/debian-ubuntu/ubuntu14/loncapa_sites"; + } else { + $stdconf = "$instdir/debian-ubuntu/loncapa"; + } + if (!-e $stdconf) { $fixapache = 0; print &mt('Warning: No LON-CAPA Apache configuration file found for installation check.')."\n"; - } elsif ((-e "/etc/apache2/sites-available/loncapa") && (-e "$instdir/debian-ubuntu/loncapa")) { - if (open(PIPE, "diff --brief $instdir/debian-ubuntu/loncapa /etc/apache2/sites-available/loncapa |")) { - my $diffres = ; - close(PIPE); - chomp($diffres); - unless ($diffres) { - $fixapache = 0; + } else { + my ($configfile,$sitefile); + if (($distname eq 'ubuntu') && ($version > 12)) { + $sitefile = '/etc/apache2/sites-available/loncapa'; + $configfile = "/etc/apache2/conf-available/loncapa"; + } else { + $configfile = "/etc/apache2/sites-available/loncapa"; + } + if (($configfile ne '') && (-e $configfile) && (-e $stdconf)) { + if (open(PIPE, "diff --brief $stdconf $configfile |")) { + my $diffres = ; + close(PIPE); + chomp($diffres); + unless ($diffres) { + $fixapache = 0; + } + } + } + if ((!$fixapache) && ($distname eq 'ubuntu') && ($version > 12)) { + if (($sitefile ne '') && (-e $sitefile) && (-e $stdsite)) { + if (open(PIPE, "diff --brief $stdsite $sitefile |")) { + my $diffres = ; + close(PIPE); + chomp($diffres); + unless ($diffres) { + $fixapache = 0; + } + } } } } @@ -631,16 +1006,21 @@ sub chkapache { } } } - } elsif ($distro =~ /^(?:suse|sles)([\d\.]+)$/) { + } elsif ($distro =~ /^(suse|sles)([\d\.]+)$/) { + my ($name,$version) = ($1,$2); my $apache = 'apache'; - if ($1 >= 10) { + my $conf_file = "$instdir/sles-suse/default-server.conf"; + if ($version >= 10) { $apache = 'apache2'; } - if (!-e "$instdir/sles-suse/default-server.conf") { + if (($name eq 'sles') && ($version >= 12)) { + $conf_file = "$instdir/sles-suse/apache2.4/default-server.conf"; + } + if (!-e $conf_file) { $fixapache = 0; print &mt('Warning: No LON-CAPA Apache configuration file found for installation check.')."\n"; - } elsif ((-e "/etc/$apache/default-server.conf") && (-e "$instdir/sles-suse/default-server.conf")) { - if (open(PIPE, "diff --brief $instdir/sles-suse/default-server.conf /etc/$apache/default-server.conf |")) { + } elsif (-e "/etc/$apache/default-server.conf") { + if (open(PIPE, "diff --brief $conf_file /etc/$apache/default-server.conf |")) { my $diffres = ; close(PIPE); chomp($diffres); @@ -665,12 +1045,17 @@ sub chkapache { } } else { my $configfile = 'httpd.conf'; - if ($distro =~ /^(?:centos|rhes|scientific)(\d+)$/) { - if ($1 > 5) { + my $mpmfile = 'mpm.conf'; + if ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)$/) { + if ($1 >= 7) { + $configfile = 'apache2.4/httpd.conf'; + } elsif ($1 > 5) { $configfile = 'new/httpd.conf'; } } elsif ($distro =~ /^fedora(\d+)$/) { - if ($1 > 10) { + if ($1 > 17) { + $configfile = 'apache2.4/httpd.conf'; + } elsif ($1 > 10) { $configfile = 'new/httpd.conf'; } } @@ -687,10 +1072,312 @@ sub chkapache { } } } + if (-e "/etc/httpd/conf.modules.d/00-mpm.conf") { + if (!-e "$instdir/centos-rhes-fedora-sl/$mpmfile") { + print &mt('Warning: No LON-CAPA Apache MPM configuration file found for installation check.')."\n"; + } elsif ((-e "/etc/httpd/conf.modules.d/00-mpm.conf") && (-e "$instdir/centos-rhes-fedora-sl/$mpmfile")) { + if (open(PIPE, "diff --brief $instdir/centos-rhes-fedora-sl/$mpmfile /etc/httpd/conf.modules.d/00-mpm.conf |")) { + my $diffres = ; + close(PIPE); + chomp($diffres); + if ($diffres) { + $fixapache = 1; + } + } + } + } } return $fixapache; } +# +# chkapachessl() determines whether a server's Apache SSL configuration +# needs updating to support LON-CAPA. +# +# LON-CAPA uses VirtualHosts for port 443, and requires that they are +# defined in one Apache configuration file containing two VirtualHost +# blocks, in order: +# +# (1) a block with no ServerName, or with ServerName set to the +# server's hostname. This block should contain: +# +# +# LON-CAPA rewrite rules defined in sslrewrite.conf +# +# +# (2) a block with ServerName set to internal-$hostname +# (where $hostname is server's hostname). +# This block should contain the config and rewrite rules +# found in loncapassl.conf. +# +# chkapachessl() retrieves the names of .conf files in +# the directory appropriate for the particular Linux distro, +# and then checks to see which .conf file is the best candidate as +# the single file containing VirtualHosts definitions and +# rewrite blocks. +# +# The best candidate is the one containing a block: +# +# (where ????? might be _default_ or * or an IP address) +# +# +# +# with the fewest differences between the contents of the +# IfModule block and the expected contents (from sslrewrite.conf) +# +# If there are no files with rewrite blocks, then a candidate file +# is chosen from the .conf files containing VirtualHosts definitions. +# +# If the user includes "Configure SSL for Apache web server" as +# one of the actions to take to prepare the server for LON-CAPA +# installation, then the output from &chkapachessl() will be +# used to determined which file will contain VirtualHost configs. +# +# If there are no files containing VirtualHosts definitions, then +# blocks will be appended to +# the standard Apache SSL config for the particular distro: +# ssl.conf for RHEL/CentOS/Scientific/Fedora, vhost-ssl.conf +# for SuSE/SLES, and default-ssl.conf for Ubuntu. +# +# Once a file is selected, the contents of sslrewrite.conf and +# loncapassl.conf are compared with appropriate blocks in the file +# and the user will be prompted to agree to insertion of missing +# lines and/or deletion of surplus lines. +# + +sub chkapachessl { + my ($distro,$instdir,$hostname,$hostip) = @_; + my $fixapachessl = 1; + my $sslintconf = "$instdir/loncapassl.conf"; + my $sslrewriteconf = "$instdir/sslrewrite.conf"; + my (%sslfiles,%rewrites,%vhostonly,$has_std,$has_int,$rewritenum,$nochgint,$nochgstd); + $nochgstd = 0; + $nochgint = 0; + if (!-e $sslintconf) { + $fixapachessl = 0; + print &mt('Warning: LON-CAPA SSL Apache configuration file [_1] needed for installation check.',$sslintconf)."\n"; + } elsif (!-e $sslrewriteconf) { + $fixapachessl = 0; + print &mt('Warning: LON-CAPA SSL Apache configuration file [_1] needed for installation check is missing.',$sslrewriteconf)."\n"; + } else { + my $ssldir; + if ($distro =~ /^(debian|ubuntu)(\d+)$/) { + $ssldir = '/etc/apache2/sites-available'; + } elsif ($distro =~ /(suse|sles)/) { + $ssldir = '/etc/apache2/vhosts.d'; + } else { + $ssldir = '/etc/httpd/conf.d'; + } + my @rewritessl = (); + if (open(my $fh,'<',$sslrewriteconf)) { + my $skipnext = 0; + while (<$fh>) { + chomp(); + s/(^\s+|\s+$)//g; + next if ($_ eq ''); + next if ($_ eq ''); + next if ($_ eq ''); + if ($_ eq 'RewriteCond %{REMOTE_ADDR} {[[[[HostIP]]]]}') { + if (($hostip ne '') && ($hostip ne '127.0.0.1')) { + push(@rewritessl,'RewriteCond %{REMOTE_ADDR} '.$hostip); + next; + } else { + $skipnext = 1; + } + } elsif (($_ eq 'RewriteRule (.*) - [L]') && ($skipnext)) { + $skipnext = 0; + next; + } + push(@rewritessl,$_); + } + } + my @intssl = (); + if (open(my $fh,'<',$sslintconf)) { + while(<$fh>) { + chomp(); + s/(^\s+|\s+$)//g; + next if ($_ eq ''); + if ($_ eq 'ServerName internal-{[[[[Hostname]]]]}') { + if ($hostname ne '') { + push(@intssl,'ServerName internal-'.$hostname); + next; + } + } + next if ($_ eq ''); + next if ($_ eq ''); + push(@intssl,$_); + } + } + if (-d $ssldir) { + my @actualint = (); + if (opendir(my $dir,$ssldir)) { + my @sslconf_files; + foreach my $file (grep(!/^\.+/,readdir($dir))) { + next if (($distro =~ /(suse|sles)/) && ($file =~ /\.template$/)); + next if ($file =~ /\.rpmnew$/); + if (open(my $fh,'<',"$ssldir/$file")) { + while (<$fh>) { + if (/^\s*\s*$/) { + push(@sslconf_files,$file); + last; + } + } + close($fh); + } + } + closedir($dir); + if (@sslconf_files) { + foreach my $file (@sslconf_files) { + if (open(my $fh,'<',"$ssldir/$file")) { + my ($virtualhost,$rewrite,$num) = (0,0,0); + my ($currname,$has_rewrite); + while (<$fh>) { + chomp(); + next if (/^\s*$/); + if ($virtualhost) { + if (/^\s*<\/VirtualHost>/) { + if ($currname !~ /^\Qinternal-$hostname\E/) { + if ($has_rewrite) { + delete($vhostonly{$file}); + } else { + $vhostonly{$file} = 1; + } + } + $sslfiles{$currname}{$file} = 1; + $virtualhost = 0; + $currname = ''; + $has_rewrite = ''; + next; + } elsif (/^\s*ServerName\s+([^\s]+)\s*$/) { + $currname = $1; + } + if ($currname =~ /^\Qinternal-$hostname\E/) { + s/(^\s+|\s+$)//g; + push(@actualint,$_); + $has_int = $file; + } else { + if ($rewrite) { + if (/^\s*<\/IfModule>/) { + $rewrite = 0; + $num ++; + } else { + s/(^\s+|\s+$)//g; + push(@{$rewrites{$file}[$num]},$_); + } + } elsif (/^\s*/) { + $rewrite = 1; + $has_rewrite = 1; + if ($currname eq '') { + $currname = $hostname; + } + $rewrites{$file}[$num] = []; + } + } + } elsif (/^\s*\s*$/) { + $virtualhost = 1; + } + } + close($fh); + } + } + } + if (keys(%rewrites)) { + my $mindiffsall; + foreach my $file (sort(keys(%rewrites))) { + if (ref($rewrites{$file}) eq 'ARRAY') { + my $mindiffs; + for (my $i=0; $i<@{$rewrites{$file}}; $i++) { + if (ref($rewrites{$file}[$i]) eq 'ARRAY') { + my @diffs = &compare_arrays($rewrites{$file}[$i],\@rewritessl); + if (@diffs == 0) { + $fixapachessl = 0; + $mindiffs = 0; + $rewritenum = 1+$i; + last; + } else { + if ($mindiffs eq '') { + $mindiffs = scalar(@diffs); + $rewritenum = 1+$i; + } elsif (scalar(@diffs) <= $mindiffs) { + $mindiffs = scalar(@diffs); + $rewritenum = 1+$i; + } + } + } + } + if ($mindiffsall eq '') { + $mindiffsall = $mindiffs; + $has_std = $file; + } elsif ($mindiffs <= $mindiffsall) { + $mindiffsall = $mindiffs; + $has_std = $file; + } + if ($mindiffsall == 0) { + $nochgstd = 1; + } + } + } + } elsif (keys(%vhostonly) > 0) { + if (($has_int ne '') && (exists($vhostonly{$has_int}))) { + $has_std = $has_int; + } + } + if (@actualint) { + my @diffs = &compare_arrays(\@actualint,\@intssl); + if (@diffs) { + $fixapachessl = 1; + } else { + $nochgint = 1; + } + } else { + $fixapachessl = 1; + } + } + } + unless ($fixapachessl) { + if ($distro =~ /^(debian|ubuntu)(\d+)$/) { + my $enabled_dir = '/etc/apache2/sites-enabled'; + if (keys(%sslfiles)) { + foreach my $key (sort(keys(%sslfiles))) { + if (ref($sslfiles{$key}) eq 'HASH') { + foreach my $file (sort(keys(%{$sslfiles{$key}}))) { + unless ((-l "$enabled_dir/$file") && + (readlink("$enabled_dir/$file") eq "$ssldir/$file")) { + print_and_log(&mt("Warning, use: 'sudo a2ensite $file' to activate LON-CAPA SSL Apache config\n")); + } + } + } + } + } + } + } + } + return ($fixapachessl,\%sslfiles,$has_std,$has_int,$rewritenum,$nochgstd,$nochgint); +} + +# +# compare_arrays() expects two refs to arrays as args. +# +# The contents of the two arrays are compared, and if they +# are different, and array of the differences is returned. +# + +sub compare_arrays { + my ($arrayref1,$arrayref2) = @_; + my (@difference,%count); + @difference = (); + %count = (); + if ((ref($arrayref1) eq 'ARRAY') && (ref($arrayref2) eq 'ARRAY')) { + foreach my $element (@{$arrayref1}, @{$arrayref2}) { $count{$element}++; } + foreach my $element (keys(%count)) { + if ($count{$element} == 1) { + push(@difference,$element); + } + } + } + return @difference; +} + sub chksrvcs { my ($distro,$tostop) = @_; my %stopsrvcs; @@ -816,13 +1503,36 @@ sub need_download { } sub check_mysql_setup { - my ($instdir,$dsn) = @_; + my ($instdir,$dsn,$distro,$mysql_has_wwwuser) = @_; my ($mysqlsetup,$has_pass); my $dbh = DBI->connect($dsn,'root','',{'PrintError'=>0}); if ($dbh) { $mysqlsetup = 'noroot'; } elsif ($DBI::err =~ /1045/) { $has_pass = 1; + } elsif ($distro =~ /^ubuntu(\d+)$/) { + my $version = $1; + if ($1 > 12) { + print_and_log(&mt('Restarting mysql, please be patient')."\n"); + if (open (PIPE, "service mysql restart 2>&1 |")) { + while () { + print $_; + } + close(PIPE); + } + unless ($mysql_has_wwwuser) { + $mysql_has_wwwuser = &check_mysql_wwwuser(); + } + $dbh = DBI->connect($dsn,'root','',{'PrintError'=>0}); + if ($dbh) { + $mysqlsetup = 'noroot'; + } elsif ($DBI::err =~ /1045/) { + $has_pass = 1; + } else { + $mysqlsetup = 'needsrestart'; + return ($mysqlsetup,$has_pass,$dbh,$mysql_has_wwwuser); + } + } } if ($has_pass) { print &mt('You have already set a root password for the MySQL database.')."\n"; @@ -849,11 +1559,11 @@ sub check_mysql_setup { } } } - } elsif ($mysqlsetup ne 'noroot') { + } elsif ($mysqlsetup ne 'noroot') { print_and_log(&mt('Problem accessing MySQL.')."\n"); $mysqlsetup = 'rootfail'; } - return ($mysqlsetup,$has_pass,$dbh); + return ($mysqlsetup,$has_pass,$dbh,$mysql_has_wwwuser); } sub check_mysql_wwwuser { @@ -898,10 +1608,16 @@ sub get_pathto_iptables { sub firewall_is_active { if (-e '/proc/net/ip_tables_names') { - return 1; - } else { - return 0; + if (open(PIPE,'cat /proc/net/ip_tables_names |grep filter |')) { + my $status = ; + close(PIPE); + chomp($status); + if ($status eq 'filter') { + return 1; + } + } } + return 0; } sub get_fw_chains { @@ -917,6 +1633,8 @@ sub get_fw_chains { @posschains = ('ufw-user-input','INPUT'); } elsif ($distro =~ /^debian5/) { @posschains = ('INPUT'); + } elsif ($distro =~ /^(suse|sles)(\d+)/) { + @posschains = ('IN_public'); } else { @posschains = ('RH-Firewall-1-INPUT','INPUT'); if (!-e '/etc/sysconfig/iptables') { @@ -1059,11 +1777,12 @@ print " ".&mt('3.')." ".&mt('Set-up the MySQL database.')." ".&mt('4.')." ".&mt('Set-up MySQL permissions.')." ".&mt('5.')." ".&mt('Configure Apache web server.')." -".&mt('6.')." ".&mt('Configure start-up of services.')." -".&mt('7.')." ".&mt('Check firewall settings.')." -".&mt('8.')." ".&mt('Stop services not used by LON-CAPA,')." +".&mt('6.')." ".&mt('Configure SSL for Apache web server.')." +".&mt('7.')." ".&mt('Configure start-up of services.')." +".&mt('8.')." ".&mt('Check firewall settings.')." +".&mt('9.')." ".&mt('Stop services not used by LON-CAPA,')." ".&mt('i.e., services for a print server: [_1] daemon.',"'cups'")." -".&mt('9.')." ".&mt('Download LON-CAPA source code in readiness for installation.')." +".&mt('10.')." ".&mt('Download LON-CAPA source code in readiness for installation.')." ".&mt('Typically, you will run this script only once, when you first install LON-CAPA.')." @@ -1093,33 +1812,42 @@ chomp($instdir); my %callsub; my @actions = ('wwwuser','pwauth','mysql','mysqlperms','apache', - 'runlevels','firewall','stopsrvcs','download'); + 'apachessl','runlevels','firewall','stopsrvcs','download'); my %prompts = &texthash( wwwuser => "Create the 'www' user?", pwauth => 'Install the package LON-CAPA uses to authenticate users?', mysql => 'Set-up the MySQL database?', mysqlperms => 'Set-up MySQL permissions?', apache => 'Configure Apache web server?', + apachessl => 'Configure SSL for Apache web server?', runlevels => 'Set overrides for start-up order of services?', firewall => 'Configure firewall settings for Apache', stopsrvcs => 'Stop extra services not required on a LON-CAPA server?', download => 'Download LON-CAPA source code in readiness for installation?', ); -print "\n".&mt('Checking system status ...')."\n"; +print "\n".&mt('Checking system status ...')."\n\n"; my $dsn = "DBI:mysql:database=mysql"; -my ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow,$recommended, - $dbh,$has_pass,$has_lcdb,$downloadstatus,$filetouse,$production, - $testing,$apachefw) = &check_required($instdir,$dsn); +my ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow,$mysqlrestart, + $recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus,$filetouse,$production, + $testing,$apachefw,$uses_systemctl,$hostname,$hostip,$sslhostsfiles,$has_std, + $has_int,$rewritenum,$nochgstd,$nochgint) = &check_required($instdir,$dsn); if ($distro eq '') { print "\n".&mt('Linux distribution could not be verified as a supported distribution.')."\n". &mt('The following are supported: [_1].', 'CentOS, RedHat Enterprise, Fedora, Scientific Linux, '. - 'openSuSE, SLES, Ubuntu LTS, Debian')."\n\n". + 'Oracle Linux, openSuSE, SLES, Ubuntu LTS, Debian')."\n\n". &mt('Stopping execution.')."\n"; exit; } +if ($mysqlrestart) { + print "\n".&mt('The mysql daemon needs to be restarted using the following command:')."\n". + $mysqlrestart."\n\n". + &mt('Stopping execution of install.pl script.')."\n". + &mt('Please run the install.pl script again, once you have restarted mysql.')."\n"; + exit; +} if ($localecmd ne '') { print "\n".&mt('Although the LON-CAPA application itself is localized for a number of different languages, the default locale language for the Linux OS on which it runs should be US English.')."\n"; print "\n".&mt('Run the following command from the command line to set the default language for your OS, and then run this LON-CAPA installation set-up script again.')."\n\n". @@ -1132,7 +1860,6 @@ if (!$gotprereqs) { &mt('The following command can be used to install the package (and dependencies):')."\n\n". $updatecmd."\n\n"; if ($installnow eq '') { - print &mt('Stopping execution.')."\n"; exit; } else { print &mt('Run command? ~[Y/n~]'); @@ -1147,8 +1874,8 @@ if (!$gotprereqs) { exit; } else { ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow, - $recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus, - $filetouse,$production,$testing,$apachefw) = + $mysqlrestart,$recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus, + $filetouse,$production,$testing,$apachefw,$uses_systemctl) = &check_required($instdir,$dsn); } } else { @@ -1222,7 +1949,7 @@ if ($callsub{'download'}) { print &mt('The most recent LON-CAPA release is version: [_1].',$production)."\n". &mt('Download the production release? ~[Y/n~]'); if (&get_user_selection(1)) { - $sourcetarball = $production.'tar.gz'; + $sourcetarball = 'loncapa-'.$production.'.tar.gz'; } } } elsif ($filetouse ne '') { @@ -1272,16 +1999,53 @@ if ($dbh) { if ($callsub{'apache'}) { if ($distro =~ /^(suse|sles)/) { - ©_apache2_suseconf($instdir); + ©_apache2_suseconf($instdir,$hostname,$distro); } elsif ($distro =~ /^(debian|ubuntu)/) { - ©_apache2_debconf($instdir); + ©_apache2_debconf($instdir,$distro,$hostname); } else { - ©_httpd_conf($instdir,$distro); + ©_httpd_conf($instdir,$distro,$hostname); + ©_mpm_conf($instdir,$distro); } } else { print_and_log(&mt('Skipping configuration of Apache web server.')."\n"); } +if ($callsub{'apachessl'}) { + my $targetdir = '/etc/httpd/conf.d'; + if ($distro =~ /^(suse|sles)/) { + $targetdir = '/etc/apache2/vhosts.d'; + } elsif ($distro =~ /^(debian|ubuntu)/) { + $targetdir = '/etc/apache2/sites-available'; + } + my ($new_rewrite,$new_int) = + ©_apache_sslconf_files($distro,$hostname,$hostip,$instdir,$targetdir,$sslhostsfiles, + $has_std,$has_int,$rewritenum,$nochgstd,$nochgint); + if ($distro =~ /^(debian|ubuntu)/) { + my $apache2_sites_enabled_dir = '/etc/apache2/sites-enabled'; + if (-d $apache2_sites_enabled_dir) { + if ($has_std ne '') { + unless ((-l "$apache2_sites_enabled_dir/$has_std") && (readlink(("$apache2_sites_enabled_dir/$has_std") eq "$targetdir/$has_std"))) { + my $made_symlink = eval { symlink("$targetdir/$has_std","$apache2_sites_enabled_dir/$has_std"); 1}; + if ($made_symlink) { + print_and_log(&mt('Enabling "[_1]" Apache SSL configuration.',$has_std)."\n"); + } + } + } + if (($has_int ne '') && ($has_int ne $has_std)) { + unless ((-l "$apache2_sites_enabled_dir/$has_int") && (readlink("$apache2_sites_enabled_dir/$has_int") eq "$targetdir/$has_int")) { + my $made_symlink = eval { symlink("$targetdir/$has_int","$apache2_sites_enabled_dir/$has_int"); 1 }; + if ($made_symlink) { + print_and_log(&mt('Enabling "[_1]" Apache SSL configuration.',$has_int)."\n"); + } + } + } + } + } + print_and_log("\n"); +} else { + print_and_log(&mt('Skipping configuration of SSL for Apache web server.')."\n"); +} + if ($callsub{'runlevels'}) { my $count = 0; if (ref($recommended) eq 'HASH') { @@ -1300,18 +2064,51 @@ if ($callsub{'runlevels'}) { } } } - if ($distro =~ /^(suse|sles)/) { - &update_SuSEfirewall2_setup($instdir); + if ($distro =~ /^(suse|sles)(\d+)/) { + unless(($1 eq 'sles') && ($2 >= 15)) { + &update_SuSEfirewall2_setup($instdir); + } } } else { &print_and_log(&mt('Skipping setting override for start-up order of services.')."\n"); } if ($callsub{'firewall'}) { - if ($distro =~ /^(suse|sles)/) { + my ($firewalld,$zone) = &uses_firewalld($distro); + if ($firewalld) { + my (%current,%added); + if (open(PIPE,"firewall-cmd --permanent --zone=$zone --list-services |")) { + my $svc = ; + close(PIPE); + chomp($svc); + map { $current{$_} = 1; } (split(/\s+/,$svc)); + } + foreach my $service ('http','https') { + unless ($current{$service}) { + if (open(PIPE,"firewall-cmd --permanent --zone=$zone --add-service=$service |")) { + my $result = ; + if ($result =~ /^success/) { + $added{$service} = 1; + } + } + } + } + if (keys(%added) > 0) { + print &mt('Firewall configured to allow access for: [_1].', + join(', ',sort(keys(%added))))."\n"; + } + if ($current{'http'} || $current{'https'}) { + print &mt('Firewall already configured to allow access for:[_1].', + (($current{'http'})? ' http':'').(($current{'https'})? ' https':''))."\n"; + } + unless ($current{'ssh'}) { + print &mt('If you would the like to allow access to ssh from outside, use the command[_1].', + "firewall-cmd --permanent --zone=$zone --add-service=ssh")."\n"; + } + } elsif ($distro =~ /^(suse|sles)/) { print &mt('Use [_1] to configure the firewall to allow access for [_2].', 'yast -- Security and Users -> Firewall -> Interfaces', - 'ssh, http, https')."\n"; + 'ssh, http, https')."\n"; } elsif ($distro =~ /^(debian|ubuntu)(\d+)/) { if (($1 eq 'ubuntu') || ($2 > 5)) { print &mt('Use [_1] to configure the firewall to allow access for [_2].', @@ -1328,21 +2125,31 @@ if ($callsub{'firewall'}) { } } } - } elsif ($distro =~ /^scientific/) { + } elsif ($distro =~ /^(scientific|oracle)/) { print &mt('Use [_1] to configure the firewall to allow access for [_2].', 'system-config-firewall-tui -- Customize', 'ssh, http')."\n"; } else { - print &mt('Use [_1] to configure the firewall to allow access for [_2].', - 'setup -- Firewall configuration -> Customize', - 'ssh, http, https')."\n"; + my $version; + if ($distro =~ /^(redhat|centos)(\d+)$/) { + $version = $1; + } + if ($version > 5) { + print &mt('Use [_1] to configure the firewall to allow access for [_2].', + 'system-config-firewall-tui -- Customize', + 'ssh, http')."\n"; + } else { + print &mt('Use [_1] to configure the firewall to allow access for [_2].', + 'setup -- Firewall configuration -> Customize', + 'ssh, http, https')."\n"; + } } } else { &print_and_log(&mt('Skipping Firewall configuration.')."\n"); } if ($callsub{'stopsrvcs'}) { - &kill_extra_services($distro,$recommended->{'stopsrvcs'}); + &kill_extra_services($distro,$recommended->{'stopsrvcs'},$uses_systemctl); } else { &print_and_log(&mt('Skipping stopping unnecessary service ([_1] daemons).',"'cups','memcached'")."\n"); } @@ -1529,7 +2336,7 @@ END } sub kill_extra_services { - my ($distro,$stopsrvcs) = @_; + my ($distro,$stopsrvcs,$uses_systemctl) = @_; if (ref($stopsrvcs) eq 'HASH') { my @stopping = sort(keys(%{$stopsrvcs})); if (@stopping) { @@ -1560,10 +2367,31 @@ sub kill_extra_services { } } &print_and_log(&mt('Removing [_1] from startup.',$service)."\n"); - if ($distro =~ /^(debian|ubuntu)/) { - &print_and_log(`update-rc.d -f $daemon remove`); + if ($distro =~ /^(?:debian|ubuntu)(\d+)/) { + my $version = $1; + if (($distro =~ /^ubuntu/) && ($version > 16)) { + if (ref($uses_systemctl) eq 'HASH') { + if ($uses_systemctl->{$service}) { + if (`systemctl is-enabled $service`) { + &print_and_log(`systemctl disable $service`); + } + } + } + } else { + &print_and_log(`update-rc.d -f $daemon remove`); + } } else { - &print_and_log(`/sbin/chkconfig --del $service`); + if (ref($uses_systemctl) eq 'HASH') { + if ($uses_systemctl->{$service}) { + if (`systemctl is-enabled $service`) { + &print_and_log(`systemctl disable $service`); + } + } else { + &print_and_log(`/sbin/chkconfig --del $service`); + } + } else { + &print_and_log(`/sbin/chkconfig --del $service`); + } } } } @@ -1601,8 +2429,34 @@ CREATE TABLE IF NOT EXISTS metadata (tit sub setup_mysql_permissions { my ($dbh,$has_pass,@mysql_lc_commands) = @_; - my $mysqlversion = &get_mysql_version(); - my @mysql_commands = ("INSERT user (Host, User, Password) VALUES('localhost','www',password('localhostkey'));"); + my ($mysqlversion,$mysqlsubver,$mysqlname) = &get_mysql_version(); + my ($usesauth,$is_mariadb,$hasauthcol,@mysql_commands); + if ($mysqlname =~ /^MariaDB/i) { + $is_mariadb = 1; + if ($mysqlversion >= 10.2) { + $usesauth = 1; + } elsif ($mysqlversion >= 5.5) { + $hasauthcol = 1; + } + } else { + if (($mysqlversion > 5.7) || (($mysqlversion == 5.7) && ($mysqlsubver > 5))) { + $usesauth = 1; + } elsif (($mysqlversion >= 5.6) || (($mysqlversion == 5.5) && ($mysqlsubver >= 7))) { + $hasauthcol = 1; + } + } + if ($usesauth) { + @mysql_commands = ("INSERT user (Host, User, ssl_cipher, x509_issuer, x509_subject, authentication_string) VALUES('localhost','www','','','','')"); + if ($is_mariadb) { + push(@mysql_commands,"ALTER USER 'www'\@'localhost' IDENTIFIED BY 'localhostkey'"); + } else { + push(@mysql_commands,"ALTER USER 'www'\@'localhost' IDENTIFIED WITH mysql_native_password BY 'localhostkey'"); + } + } elsif ($hasauthcol) { + @mysql_commands = ("INSERT user (Host, User, Password, ssl_cipher, x509_issuer, x509_subject, authentication_string) VALUES('localhost','www',password('localhostkey'),'','','','');"); + } else { + @mysql_commands = ("INSERT user (Host, User, Password, ssl_cipher, x509_issuer, x509_subject) VALUES('localhost','www',password('localhostkey'),'','','');"); + } if ($mysqlversion < 4) { push (@mysql_commands," INSERT db (Host,Db,User,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Grant_priv,References_priv,Index_priv,Alter_priv) VALUES('localhost','loncapa','www','Y','Y','Y','Y','Y','Y','N','Y','Y','Y')"); @@ -1652,7 +2506,7 @@ INSERT db (Host,Db,User,Select_priv,Inse } } if ($got_passwd) { - my (@newpass_cmds) = &new_mysql_rootpasswd($newmysqlpass); + my (@newpass_cmds) = &new_mysql_rootpasswd($newmysqlpass,$usesauth,$is_mariadb); push(@mysql_commands,@newpass_cmds); } else { print_and_log(&mt('Failed to get MySQL root password from user input.')."\n"); @@ -1661,7 +2515,6 @@ INSERT db (Host,Db,User,Select_priv,Inse if (@mysql_commands) { foreach my $cmd (@mysql_commands) { $dbh->do($cmd) || print $dbh->errstr."\n"; - } } if (@mysql_lc_commands) { @@ -1683,23 +2536,33 @@ INSERT db (Host,Db,User,Select_priv,Inse } sub new_mysql_rootpasswd { - my ($currmysqlpass) = @_; - return ("SET PASSWORD FOR 'root'\@'localhost'=PASSWORD('$currmysqlpass')", - "FLUSH PRIVILEGES;"); + my ($currmysqlpass,$usesauth,$is_mariadb) = @_; + if ($usesauth) { + if ($is_mariadb) { + return ("ALTER USER 'root'\@'localhost' IDENTIFIED BY '$currmysqlpass'", + "FLUSH PRIVILEGES;"); + } else { + return ("ALTER USER 'root'\@'localhost' IDENTIFIED WITH mysql_native_password BY '$currmysqlpass'", + "FLUSH PRIVILEGES;"); + } + } else { + return ("SET PASSWORD FOR 'root'\@'localhost'=PASSWORD('$currmysqlpass')", + "FLUSH PRIVILEGES;"); + } } sub get_mysql_version { - my $version; + my ($version,$subversion,$name); if (open(PIPE," mysql -V |")) { my $info = ; chomp($info); close(PIPE); - ($version) = ($info =~ /(\d+\.\d+)\.\d+,/); + ($version,$subversion,$name) = ($info =~ /(\d+\.\d+)\.(\d+)\-?(\w*),/); } else { print &mt('Could not determine which version of MySQL is installed.'). "\n"; } - return $version; + return ($version,$subversion,$name); } ########################################################### @@ -1710,14 +2573,18 @@ sub get_mysql_version { ########################################################### sub copy_httpd_conf { - my ($instdir,$distro) = @_; + my ($instdir,$distro,$hostname) = @_; my $configfile = 'httpd.conf'; - if ($distro =~ /^(?:centos|rhes|scientific)(\d+)$/) { - if ($1 > 5) { + if ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)$/) { + if ($1 >= 7) { + $configfile = 'apache2.4/httpd.conf'; + } elsif ($1 > 5) { $configfile = 'new/httpd.conf'; } } elsif ($distro =~ /^fedora(\d+)$/) { - if ($1 > 10) { + if ($1 > 17) { + $configfile = 'apache2.4/httpd.conf'; + } elsif ($1 > 10) { $configfile = 'new/httpd.conf'; } } @@ -1729,6 +2596,527 @@ sub copy_httpd_conf { print_and_log("\n"); } +########################################################### +## +## RHEL/CentOS/Fedora/Scientific Linux +## Copy LON-CAPA mpm.conf to /etc/httpd/conf.modules.d/00-mpm.conf +## +## The LON-CAPA mpm.conf enables the prefork MPM module in +## Apache. This is also the default for RHEL/CentOS/Oracle +## Linux 7and earlier, and Fedora 26 and earlier. For more +## recent versions of those distros, the event MPM is enabled +## by default. After ©_mpm_conf() is run, the prefork MPM +## module will be enabled instead of the event MPM module. +## +########################################################### + +sub copy_mpm_conf { + my ($instdir,$distro) = @_; + my $mpmfile = 'mpm.conf'; + if ((-e "/etc/httpd/conf.modules.d/00-mpm.conf") && + (-e "$instdir/centos-rhes-fedora-sl/$mpmfile")) { + print_and_log(&mt('Copying the LON-CAPA [_1] to [_2].',"'mpm.conf'", + "'/etc/httpd/conf.modules.d/00-mpm.conf'")."\n"); + copy "$instdir/centos-rhes-fedora-sl/$mpmfile","/etc/httpd/conf.modules.d/00-mpm.conf"; + chmod(0644,"/etc/httpd/conf.modules.d/00-mpm.conf"); + print_and_log("\n"); + } else { + my $logfail; + if ($distro =~ /^(?:centos|rhes|scientific|oracle)(\d+)$/) { + if ($1 > 7) { + $logfail = 1; + } + } elsif ($distro =~ /^fedora(\d+)$/) { + if ($1 > 26) { + $logfail = 1; + } + } + if ($logfail) { + print_and_log(&mt('Warning: copying the LON-CAPA [_1] failed because [_2] and/or [_3] are missing.', + $mpmfile,"'$instdir/centos-rhes-fedora-sl/$mpmfile'", + "'/etc/httpd/conf.modules.d/00-mpm.conf'")); + print_and_log("\n"); + } + } +} + +############################################### +## +## Copy loncapassl.conf and sslrewrite.conf +## +############################################### + +# +# The Apache SSL configuration used by LON-CAPA is contained in +# two files: sslrewrite.conf and loncapassl.conf. +# +# Starting with LON-CAPA 2.12, name-based virtual hosts are used +# with port 443. The default virtual host (i.e., the one listed +# first) is for the server's standard hostname, and that is the one +# which will respond to client browser requests for https:// pages. +# +# Accordingly, a system administrator will need to edit the config +# config file to include paths to a signed SSL certificate (public), +# chain (public) and key (private) pem files. The certificate should +# have been signed by a recognized certificate authority ((e.g., +# InCommon or Let's Encrypt). +# +# The sslrewrite.conf file contains the rewrite configuration for +# the default virtual host. The rewrite rules defined are used to +# allow internal HEAD requests to /cgi-bin/mimetex.cgi to be served +# http://, in order to support vertical alignment of mimetex images +# (one of the options for rendering Math content); (b) allow requests +# for certain URLs (external resource, and syllabus, if external URL +# used) to be served http:// to accommodate the use of iframes which +# would otherwise result in browser blocking of mixed active content. +# +# The loncapassl.conf file contains the configuration for the +# "internal" virtual host, which will respond to requests for https:// +# pages from other LON-CAPA servers in the network to which the node +# belongs. The ServerName is internal- where +# is the server's hostname. There is no need to create a DNS entry +# for internal-, as LON-CAPA 2.12 automatically performs +# the required hostname to IP mapping. +# +# Requests to /raw on the "internal" virtual host require a valid +# SSL client certificate, signed by the certificate authority +# for the LON-CAPA network to which the node belongs. +# +# The configuration file to which the contents of sslrewrite.conf +# and loncapassl.conf will be written will have either been identified +# when &chkapachessl() was run, or if no files were found with +# existing rewrite blocks, then a candidate file will be chosen +# from the .conf files containing VirtualHosts definitions. +# If there is more than one suitable candidate file, the system +# administrator will be prompted to select from the available files. +# +# If there are no files containing VirtualHosts definitions, then +# blocks will be appended to +# the standard Apache SSL config for the particular distro: +# ssl.conf for RHEL/CentOS/Scientific/Fedora, vhost-ssl.conf +# for SuSE/SLES, and default-ssl.conf for Ubuntu. +# +# Once a file is selected, the contents of sslrewrite.conf and +# loncapassl.conf are compared with appropriate blocks in the file +# and the user will be prompted to agree to insertion of missing lines +# and/or deletion of surplus lines. +# + +sub copy_apache_sslconf_files { + my ($distro,$hostname,$hostip,$instdir,$targetdir,$targetfilesref, + $has_std,$has_int,$rewritenum,$nochgstd,$nochgint) = @_; + my ($new_std,$new_int); + my (@internal,@standard,%int_by_linenum,%int_by_linetext, + %rule_by_linenum,%rule_by_linetext,%foundint); + if (-e "$instdir/loncapassl.conf") { + if (open(my $fh,'<',"$instdir/loncapassl.conf")) { + my $num = 1; + while (<$fh>) { + chomp(); + if (/^ServerName/) { + s/(\Qinternal-{[[[[Hostname]]]]}\E)/internal-$hostname/; + } + push(@internal,$_); + $int_by_linenum{$num} = $_; + s/(^\s+|\s+$)//g; + push(@{$int_by_linetext{$_}},$num); + $num ++; + } + close($fh); + } + } + if (-e "$instdir/sslrewrite.conf") { + if (open(my $fh,'<',"$instdir/sslrewrite.conf")) { + my $num = 1; + while (<$fh>) { + chomp(); + if (/\Q{[[[[HostIP]]]]}\E/) { + s/(\QRewriteCond %{REMOTE_ADDR} {[[[[HostIP]]]]}\E)/RewriteCond %{REMOTE_ADDR} $hostip/; + } + push(@standard,$_); + $rule_by_linenum{$num} = $_; + s/(^\s+|\s+$)//g; + push(@{$rule_by_linetext{$_}},$num); + $num ++; + } + close($fh); + } + } + if (!$nochgstd) { + if ($has_std eq '') { + my $file; + if ($has_int ne '') { + if (open(my $fh,'<',"$targetdir/$has_int")) { + my @saved = <$fh>; + close($fh); + if (open(my $fhout, '>',"$targetdir/$has_int")) { + print $fhout "\n". + "ServerName $hostname\n". + join("\n",@standard)."\n". + "\n\n". + join('',@saved); + close($fhout); + $new_int = $has_int; + } + } + } + } else { + if ($rewritenum eq '') { + &append_to_vhost($targetdir,$has_std,$hostname,\%rule_by_linenum,'std'); + $new_std = $has_std; + } else { + $new_std = &modify_ssl_config($targetdir,$has_std,$hostname,$rewritenum, + \%rule_by_linetext,\%rule_by_linenum,'std'); + } + } + } + if (!$nochgint) { + if ($has_int eq '') { + if ($has_std ne '') { + if (open(my $fhout,'>>',"$targetdir/$has_std")) { + print $fhout "\n".join("\n",@internal)."\n"; + close($fhout); + $new_int = $has_std; + } + } + } else { + $new_int = &modify_ssl_config($targetdir,$has_int,$hostname,$rewritenum,\%int_by_linetext,\%int_by_linenum,'int'); + } + } + if (($has_std eq '') && ($has_int eq '')) { + my ($file,$numfiles) = &get_sslconf_filename($distro,$targetdir,$targetfilesref); + if ($numfiles == 0) { + if (open(my $fhout, '>>', "$targetdir/$file")) { + print $fhout "\n". + "ServerName $hostname\n". + join("\n",@standard)."\n". + "\n\n". + join("\n",@internal)."\n"; + close($fhout); + $new_std = $file; + $new_int = $file; + } + } elsif ($numfiles == 1) { + &append_to_vhost($targetdir,$file,$hostname,\%rule_by_linenum,'std'); + if (open(my $fhout, '>>', "$targetdir/$file")) { + print $fhout "\n".join("\n",@internal)."\n"; + close($fhout); + $new_std = $file; + $new_int = $file; + } + } elsif ($numfiles == -1) { + print_and_log(&mt('Failed to copy contents of [_1] or [_2] to a file in [_3]', + "'loncapassl.conf'","'sslrewrite.conf'","'$targetdir'")."\n"); + } + } + if ($nochgstd) { + print_and_log(&mt('No change required to file: [_1] in [_2], (no difference between [_3] and rewrite block.)', + "'$has_std'","'$targetdir'","'sslrewrite.conf'")); + } + if ($nochgint) { + print_and_log(&mt('No change required to file: [_1] in [_2], (no difference between [_3] and virtualhost block.)', + "'$has_int'","'$targetdir'","'loncapassl.conf'")); + } + if ($new_int) { + print_and_log(&mt('Successfully copied contents of [_1] to [_2].',"'loncapassl.conf'","'$targetdir/$new_int'")."\n"); + chmod(0444,"$targetdir/loncapassl.conf"); + } + if ($new_std) { + print_and_log(&mt('Successfully copied contents of [_1] to [_2].',"'sslrewrite.conf'","'$targetdir/$new_std'")."\n"); + chmod(0444,"$targetdir/loncapassl.conf"); + } + return ($new_int,$new_std); +} + +# +# append_to_vhost() is called to add rewrite rules (in a +# block), provided +# in the sslrewrite.conf configuration file, to an Apache +# SSL configuration file within a VirtualHost for port 443 +# (for server's public-facing hostname). +# +sub append_to_vhost { + my ($targetdir,$filename,$hostname,$by_linenum,$type) = @_; + return unless (ref($by_linenum) eq 'HASH'); + my ($startvhost,$endvhost); + if (-e "$targetdir/$filename") { + my (@lines,$currname,$virtualhost,$hasname); + if (open(my $fh,'<',"$targetdir/$filename")) { + my $currline = 0; + while (<$fh>) { + $currline ++; + push(@lines,$_); + chomp(); + s/(^\s+|\s+$)//g; + if (/^/) { + $virtualhost = 1; + unless ($endvhost) { + $startvhost = $currline; + } + } + if ($virtualhost) { + if (/^ServerName\s+([^\s]+)\s*$/) { + $currname = $1; + unless ($endvhost) { + if ((($currname eq '') || ($currname eq $hostname)) && ($type eq 'std')) { + $hasname = 1; + } + } + } + if (/^<\/VirtualHost>/) { + $virtualhost = 0; + unless ($endvhost) { + if (((($currname eq '') || ($currname eq $hostname)) && ($type eq 'std')) || + (($currname eq 'internal-'.$hostname) && ($type eq 'int'))) { + $endvhost = $currline; + } else { + undef($startvhost); + } + } + } + } + } + close($fh); + } + if ($endvhost) { + if (open(my $fout,'>',"$targetdir/$filename")) { + for (my $i=0; $i<@lines; $i++) { + if ($i == $startvhost) { + unless (($hasname) && ($type eq 'std')) { + print $fout "ServerName $hostname\n"; + } + } + if ($i == $endvhost-1) { + foreach my $item (sort { $a <=> $b } keys(%{$by_linenum})) { + print $fout $by_linenum->{$item}."\n"; + } + } + print $fout $lines[$i]; + } + close($fout); + } + } + } + return $endvhost; +} + +# +# get_sslconf_filename() is called when the Apache SSL configuration +# option has been selected and there are no files containing +# VirtualHost definitions containing rewrite blocks, +# +# In this case get_sslconf_filename() is used to chose from the +# available .conf files containing VirtualHosts definitions. If +# there is ambiguity about which file to use, &apacheconf_choice() +# will be called to prompt the user to choose one of the possible +# files. +# + +sub get_sslconf_filename { + my ($distro,$targetdir,$targetfilesref) = @_; + my ($configfile,$numfiles,@possfiles); + if (ref($targetfilesref) eq 'HASH') { + if (keys(%{$targetfilesref}) > 0) { + foreach my $name (sort(keys(%{$targetfilesref}))) { + if (ref($targetfilesref->{$name}) eq 'HASH') { + foreach my $file (sort(keys(%{$targetfilesref->{$name}}))) { + next if ($file eq ''); + next if (!-e "$targetdir/$file"); + unless (grep(/^\Q$file\E$/,@possfiles)) { + push(@possfiles,$file); + } + } + } + } + } + if (@possfiles == 0) { + $configfile = 'ssl.conf'; + if ($distro =~ /^(suse|sles)/) { + $configfile = 'vhost-ssl.conf'; + } elsif ($distro =~ /^(debian|ubuntu)/) { + $configfile = 'default-ssl.conf'; + } + $numfiles = 0; + print &mt('No configuration files in [_1] contain a block which can be used to house Apache rewrite rules from https to http.',$targetdir)."\n\n". + &mt('Accordingly, the contents of sslrewrite.conf will be included in a block which will be added to a file named: [_1].',$configfile)."\n\n"; + } elsif (@possfiles == 1) { + $configfile = $possfiles[0]; + $numfiles = 1; + print &mt('A single configuration file in [_1] contains a block.',$targetdir)."\n". + &mt('The contents of sslrewrite.conf will be added to this block.')."\n\n"; + } else { + print &mt('More than one Apache config file contains a block.')."\n\n".&mt('The possible files are:')."\n"; + my $counter = 1; + my $max = scalar(@possfiles); + foreach my $file (@possfiles) { + print "$counter. $file\n"; + $counter ++; + } + print "\n".&mt('Enter a number between 1 and [_1] to indicate which file should be modified to include the contents of sslrewrite.conf.',$max)."\n"; + my $choice = &apacheconf_choice($max); + if (($choice =~ /^\d+$/) && ($choice >= 1) && ($choice <= $max)) { + $configfile = $possfiles[$choice-1]; + $numfiles = 1; + } else { + $numfiles = -1; + } + } + } + return ($configfile,$numfiles); +} + +# +# &apacheconf_choice() prompts a user to choose an integer between 1 and the +# maximum number of available of possible Apache SSL config files found +# at the distros standard location for Apache config files containing +# VirtualHost definitions. +# +# This routine is called recursively until the user enters a valid integer. +# + +sub apacheconf_choice { + my ($max) = @_; + my $choice = ; + chomp($choice); + $choice =~ s/(^\s+|\s+$)//g; + my $configfile; + if (($choice =~ /^\d+$/) && ($choice >= 1) && ($choice <= $max)) { + $configfile = $choice; + } + while ($configfile eq '') { + print &mt('Invalid choice. Please enter a number between 1 and [_1].',$max)."\n"; + $configfile = &apacheconf_choice($max); + } + print "\n"; + return $configfile; +} + +# +# &modify_ssl_config() is called to modify the contents of an Apache SSL config +# file so that it has two blocks containing +# (a) the default VirtualHost with the block +# provided in sslrewrites.conf, and (b) an "internal" VirtualHost with the +# content provided in loncapassl.conf. +# +# This routine will prompted you to agree to insertion of lines present in the +# shipped conf file, but missing from the local config file, and also for +# deletion of lines present in the local config file, but not required in +# the shipped conf file. +# + +sub modify_ssl_config { + my ($targetdir,$filename,$hostname,$rewritenum,$by_linetext,$by_linenum,$type) = @_; + return unless ((ref($by_linetext) eq 'HASH') && (ref($by_linenum) eq 'HASH')); + if (-e "$targetdir/$filename") { + my (@lines,$virtualhost,$currname,$rewrite); + if (open(my $fh,'<',"$targetdir/$filename")) { + my %found; + my %possible; + my $currline = 0; + my $rewritecount = 0; + while (<$fh>) { + $currline ++; + push(@lines,$_); + chomp(); + s/(^\s+|\s+$)//g; + if (/^\s*\s*$/) { + $virtualhost = 1; + } + if ($virtualhost) { + if ((exists($by_linetext->{$_})) && (ref($by_linetext->{$_}) eq 'ARRAY') && + (@{$by_linetext->{$_}} > 0)) { + $possible{$currline} = shift(@{$by_linetext->{$_}}); + } + if (/^\s*<\/VirtualHost>/) { + if ((($currname eq 'internal-'.$hostname) && ($type eq 'int')) || + ((($currname eq $hostname) || ($currname eq '')) && ($type eq 'std') && + ($rewritecount == $rewritenum))) { + %found = (%found,%possible); + } else { + foreach my $line (sort {$b <=> $a } keys(%possible)) { + my $num = $possible{$line}; + if (ref($by_linetext->{$by_linenum->{$num}}) eq 'ARRAY') { + unshift(@{$by_linetext->{$by_linenum->{$num}}},$num); + } + } + } + undef(%possible); + $virtualhost = 0; + $currname = ''; + } elsif (/^\s*ServerName\s+([^\s]+)\s*$/) { + $currname = $1; + } elsif (/^\s*/) { + $rewrite = 1; + } elsif (/^\s*<\/IfModule>/) { + $rewritecount ++; + $rewrite = 0; + } + } + } + close($fh); + if (open(my $fout,'>',"$targetdir/$filename")) { + my $currline = 0; + my ($lastfound,$done); + my $numfound = 0; + foreach my $line (@lines) { + $currline ++; + if ($done) { + print $fout $line; + } elsif ($lastfound) { + if ($found{$currline}) { + for (my $i=$lastfound+1; $i<$found{$currline}; $i++) { + print &mt('The following line is missing from the current block:')."\n". + $by_linenum->{$i}."\n". + &mt('Add this line? ~[Y/n~]'); + if (&get_user_selection(1)) { + print $fout $by_linenum->{$i}."\n"; + } + } + $numfound ++; + $lastfound = $found{$currline}; + print $fout $line; + if ($numfound == scalar(keys(%found))) { + $done = 1; + for (my $i=$found{$currline}+1; $i<=scalar(keys(%{$by_linenum})); $i++) { + print &mt('The following line is missing from the current block:')."\n". + $by_linenum->{$i}."\n". + &mt('Add this line? ~[Y/n~]'); + if (&get_user_selection(1)) { + print $fout $by_linenum->{$i}."\n"; + } + } + } + } else { + print &mt('The following line found within a block does not match that expected by LON-CAPA:')."\n". + $line. + &mt('Delete this line? ~[Y/n~]'); + if (!&get_user_selection(1)) { + print $fout $line; + } + } + } elsif ($found{$currline}) { + $numfound ++; + $lastfound = $found{$currline}; + for (my $i=1; $i<$found{$currline}; $i++) { + print &mt('The following line is missing from the current block:')."\n". + $by_linenum->{$i}."\n". + &mt('Add this line? ~[Y/n~]'); + if (&get_user_selection(1)) { + print $fout $by_linenum->{$i}."\n"; + } + } + print $fout $line; + } else { + print $fout $line; + } + } + close($fout); + } + } + } + return $filename; +} + ######################################################### ## ## Ubuntu/Debian -- copy our loncapa configuration file to @@ -1737,19 +3125,7 @@ sub copy_httpd_conf { ######################################################### sub copy_apache2_debconf { - my ($instdir) = @_; - print_and_log(&mt('Copying loncapa [_1] config file to [_2] and pointing [_3] to it from sites-enabled.',"'apache2'","'/etc/apache2/sites-available'","'000-default symlink'")."\n"); - my $apache2_sites_enabled_dir = '/etc/apache2/sites-enabled'; - my $apache2_sites_available_dir = '/etc/apache2/sites-available'; - if (-l "$apache2_sites_enabled_dir/000-default") { - unlink("$apache2_sites_enabled_dir/000-default"); - } - if (-e "$apache2_sites_available_dir/loncapa") { - copy("$apache2_sites_available_dir/loncapa","$apache2_sites_available_dir/loncapa.original"); - } - copy("$instdir/debian-ubuntu/loncapa","$apache2_sites_available_dir/loncapa"); - chmod(0444,"$apache2_sites_available_dir/loncapa"); - symlink("$apache2_sites_available_dir/loncapa","$apache2_sites_enabled_dir/000-default"); + my ($instdir,$distro,$hostname) = @_; my $apache2_mods_enabled_dir = '/etc/apache2/mods-enabled'; my $apache2_mods_available_dir = '/etc/apache2/mods-available'; foreach my $module ('headers.load','expires.load') { @@ -1758,6 +3134,47 @@ sub copy_apache2_debconf { print_and_log(&mt('Enabling "[_1]" Apache module.',$module)."\n"); } } + my $apache2_sites_enabled_dir = '/etc/apache2/sites-enabled'; + my $apache2_sites_available_dir = '/etc/apache2/sites-available'; + my $defaultconfig = "$apache2_sites_enabled_dir/000-default"; + my ($distname,$version); + if ($distro =~ /^(debian|ubuntu)(\d+)$/) { + $distname = $1; + $version = $2; + } + if (($distname eq 'ubuntu') && ($version > 12)) { + $defaultconfig = "$apache2_sites_enabled_dir/000-default.conf"; + } + if (-l $defaultconfig) { + unlink($defaultconfig); + } + if (($distname eq 'ubuntu') && ($version > 12)) { + print_and_log(&mt('Copying loncapa [_1] config file to [_2] and pointing [_3] to it from conf-enabled.',"'apache2'","'/etc/apache2/conf-available'","'loncapa.conf symlink'")."\n"); + my $apache2_conf_enabled_dir = '/etc/apache2/conf-enabled'; + my $apache2_conf_available_dir = '/etc/apache2/conf-available'; + if (-e "$apache2_conf_available_dir/loncapa") { + copy("$apache2_conf_available_dir/loncapa","$apache2_conf_available_dir/loncapa.original"); + } + my $defaultconf = $apache2_conf_enabled_dir.'/loncapa.conf'; + copy("$instdir/debian-ubuntu/ubuntu14/loncapa_conf","$apache2_conf_available_dir/loncapa"); + chmod(0444,"$apache2_conf_available_dir/loncapa"); + if (-l $defaultconf) { + unlink($defaultconf); + } + symlink("$apache2_conf_available_dir/loncapa","$defaultconf"); + print_and_log(&mt('Copying loncapa [_1] site file to [_2] and pointing [_3] to it from sites-enabled.',"'apache2'","'/etc/apache2/sites-available'","'000-default.conf symlink'")."\n"); + copy("$instdir/debian-ubuntu/ubuntu14/loncapa_site","$apache2_sites_available_dir/loncapa"); + chmod(0444,"$apache2_sites_available_dir/loncapa"); + symlink("$apache2_sites_available_dir/loncapa","$defaultconfig"); + } else { + print_and_log(&mt('Copying loncapa [_1] config file to [_2] and pointing [_3] to it from sites-enabled.',"'apache2'","'/etc/apache2/sites-available'","'000-default symlink'")."\n"); + if (-e "$apache2_sites_available_dir/loncapa") { + copy("$apache2_sites_available_dir/loncapa","$apache2_sites_available_dir/loncapa.original"); + } + copy("$instdir/debian-ubuntu/loncapa","$apache2_sites_available_dir/loncapa"); + chmod(0444,"$apache2_sites_available_dir/loncapa"); + symlink("$apache2_sites_available_dir/loncapa","$apache2_sites_enabled_dir/000-default"); + } print_and_log("\n"); } @@ -1770,14 +3187,19 @@ sub copy_apache2_debconf { ########################################################### sub copy_apache2_suseconf { - my ($instdir) = @_; + my ($instdir,$hostname,$distro) = @_; + my ($name,$version) = ($distro =~ /^(suse|sles)([\d\.]+)$/); + my $conf_file = "$instdir/sles-suse/default-server.conf"; + if (($name eq 'sles') && ($version >= 12)) { + $conf_file = "$instdir/sles-suse/apache2.4/default-server.conf"; + } print_and_log(&mt('Copying the LON-CAPA [_1] to [_2].', "'default-server.conf'", "'/etc/apache2/default-server.conf'")."\n"); if (!-e "/etc/apache2/default-server.conf.original") { copy "/etc/apache2/default-server.conf","/etc/apache2/default-server.conf.original"; } - copy "$instdir/sles-suse/default-server.conf","/etc/apache2/default-server.conf"; + copy $conf_file,"/etc/apache2/default-server.conf"; chmod(0444,"/etc/apache2/default-server.conf"); # Make symlink for conf directory (included in loncapa_apache.conf) my $can_symlink = (eval { symlink('/etc/apache2','/srv/www/conf'); }, $@ eq ''); @@ -1788,7 +3210,7 @@ sub copy_apache2_suseconf { &print_and_log(&mt('Symlink creation failed for [_1] to [_2]. You will need to perform this action from the command line.',"'/srv/www/conf'","'/etc/apache2'")."\n"); } ©_apache2_conf_files($instdir); - ©_sysconfig_apache2_file($instdir); + ©_sysconfig_apache2_file($instdir,$name,$version); print_and_log("\n"); } @@ -1814,12 +3236,16 @@ sub copy_apache2_conf_files { ## ############################################### sub copy_sysconfig_apache2_file { - my ($instdir) = @_; + my ($instdir,$name,$version) = @_; print_and_log(&mt('Copying the LON-CAPA [_1] to [_2].',"'sysconfig/apache2'","'/etc/sysconfig/apache2'")."\n"); if (!-e "/etc/sysconfig/apache2.original") { copy "/etc/sysconfig/apache2","/etc/sysconfig/apache2.original"; } - copy "$instdir/sles-suse/sysconfig_apache2","/etc/sysconfig/apache2"; + my $sysconf_file = "$instdir/sles-suse/sysconfig_apache2"; + if (($name eq 'sles') && ($version >= 12)) { + $sysconf_file = "$instdir/sles-suse/apache2.4/sysconfig_apache2"; + } + copy $sysconf_file,"/etc/sysconfig/apache2"; chmod(0444,"/etc/sysconfig/apache2"); }