[LON-CAPA-cvs] cvs: doc /install/linux install.pl

raeburn raeburn at source.lon-capa.org
Mon Jul 16 20:19:51 EDT 2018


raeburn		Tue Jul 17 00:19:51 2018 EDT

  Modified files:              
    /doc/install/linux	install.pl 
  Log:
  - Modification/creation of configuration files for Apache/SSL for SNI (port 443)
    when replicating content from /raw/, and for redirects to http://. 
  
  
-------------- next part --------------
Index: doc/install/linux/install.pl
diff -u doc/install/linux/install.pl:1.46 doc/install/linux/install.pl:1.47
--- doc/install/linux/install.pl:1.46	Wed Jul 11 01:58:41 2018
+++ doc/install/linux/install.pl	Tue Jul 17 00:19:51 2018
@@ -26,6 +26,7 @@
 use strict;
 use File::Copy;
 use Term::ReadKey;
+use Socket;
 use Sys::Hostname::FQDN();
 use DBI;
 use Cwd();
@@ -76,7 +77,7 @@
           &mt('Stopping execution.')."\n";
     exit;
 } else {
-    print LOG '$Id: install.pl,v 1.46 2018/07/11 01:58:41 raeburn Exp $'."\n";
+    print LOG '$Id: install.pl,v 1.47 2018/07/17 00:19:51 raeburn Exp $'."\n";
 }
 
 #
@@ -281,6 +282,13 @@
     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");
@@ -301,6 +309,41 @@
     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 = <STDIN>;
+    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;
@@ -411,7 +454,7 @@
     }
     my ($mysqlon,$mysqlsetup,$mysqlrestart,$dbh,$has_pass,$has_lcdb,%recommended,
         $downloadstatus,$filetouse,$production,$testing,$apachefw,$tostop,
-        $uses_systemctl,$hostname);
+        $uses_systemctl,$hostname,$hostip);
     my $wwwuid = &uid_of_www();
     my $wwwgid = getgrnam('www');
     if (($wwwuid eq '') || ($wwwgid eq '')) {
@@ -420,7 +463,7 @@
     unless( -e "/usr/local/sbin/pwauth") {
         $recommended{'pwauth'} = 1;
     }
-    my $hostname = Sys::Hostname::FQDN::fqdn();
+    $hostname = Sys::Hostname::FQDN::fqdn();
     if ($hostname eq '') {
         $hostname =&get_hostname();
     } else {
@@ -429,7 +472,17 @@
             $hostname =&get_hostname();
         }
     }
-    print_and_log(&mt('Hostname is [_1]',$hostname)."\n");
+    $hostname = Sys::Hostname::FQDN::fqdn();
+    $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();
@@ -458,16 +511,20 @@
             }
         }
     }
+    my ($sslhostsfilesref,$has_std,$has_int,$rewritenum,$nochgstd,$nochgint);
     ($recommended{'firewall'},$apachefw) = &chkfirewall($distro);
     ($recommended{'runlevels'},$tostop,$uses_systemctl) = &chkconfig($distro,$instdir);
     $recommended{'apache'} = &chkapache($distro,$instdir);
-    $recommended{'apachessl'} = &chkapachessl($distro,$instdir,$hostname);
+    ($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,
             $mysqlrestart,\%recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus,
-            $filetouse,$production,$testing,$apachefw,$uses_systemctl,$hostname);
+            $filetouse,$production,$testing,$apachefw,$uses_systemctl,$hostname,
+            $hostip,$sslhostsfilesref,$has_std,$has_int,$rewritenum,$nochgstd,
+            $nochgint);
 }
 
 sub check_mysql_running {
@@ -882,42 +939,292 @@
     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:
+#
+# <IfModule mod_rewrite.c>
+# LON-CAPA rewrite rules defined in sslrewrite.conf
+# </IfModule>
+#
+# (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 
+# <IfModule mod_rewrite.c> </IfModule> rewrite blocks.
+#
+# The best candidate is the one containing a block:
+# <VirtualHost ????? :443> 
+# (where ????? might be _default_ or * or an IP address)
+# <IfModule mod_rewrite.c>
+# </IfModule>
+# </VirtualHost>
+# 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
+# <VirtualHost *:443> </VirtualHost> 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) = @_;
+    my ($distro,$instdir,$hostname,$hostip) = @_;
     my $fixapachessl = 1;
-    my $stdconf = "$instdir/loncapassl.conf";
-    if (!-e $stdconf) {
+    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: No LON-CAPA SSL Apache configuration file found for installation check.')."\n";
+        print &mt('Warning: LON-CAPA SSL Apache configuration file [_1] needed for installation check is missing.',$sslrewriteconf)."\n";
     } else {
-        my $sslfile; 
+        my $ssldir;
         if ($distro =~ /^(debian|ubuntu)(\d+)$/) {
-            $sslfile = '/etc/apache2/sites-available/loncapassl.conf';
+            $ssldir = '/etc/apache2/sites-available';
         } elsif ($distro =~ /(suse|sles)/) {
-            $sslfile = '/etc/apache2/vhosts.d/loncapassl.conf';
+            $ssldir = '/etc/apache2/vhosts.d';
         } else {
-            $sslfile = '/etc/httpd/conf.d/loncapassl.conf';
+            $ssldir = '/etc/httpd/conf.d';
         }
-        if ((-e $sslfile) && (-e $stdconf))  {
-            if (open(PIPE, "diff -y -bi --suppress-common-lines $stdconf $sslfile |")) {
-                my $diffres = <PIPE>;
-                close(PIPE);
-                chomp($diffres);
-                if ($diffres =~ /^\QServerName internal-{[[[[Hostname]]]]}\E\s+\|\s+\QServerName internal-\E$hostname$/) {
-                    $fixapachessl = 0;
+        my @rewritessl = ();
+        if (open(my $fh,'<',$sslrewriteconf)) {
+            my $skipnext = 0;
+            while (<$fh>) {
+                chomp();
+                s/(^\s+|\s+$)//g;
+                next if ($_ eq '');
+                next if ($_ eq '<IfModule mod_rewrite.c>');
+                next if ($_ eq '</IfModule>');
+                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 '<VirtualHost *:443>');
+                next if ($_ eq '</VirtualHost>');
+                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*<VirtualHost\s+[^:]*\:443>\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*<IfModule\s+mod_rewrite\.c>/) {
+                                            $rewrite = 1;
+                                            $has_rewrite = 1;
+                                            if ($currname eq '') {
+                                                $currname = $hostname;
+                                            }
+                                            $rewrites{$file}[$num] = [];
+                                        }
+                                    }
+                                } elsif (/^\s*<VirtualHost\s+[^:]*\:443>\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+)$/) {
-                unless ((-l '/etc/apache2/sites-enabled/loncapassl.conf') &&
-                        (readlink('/etc/apache2/sites-enabled/loncapassl.conf') eq '/etc/apache2/sites-available/loncapassl.conf')) {
-                    print_and_log(&mt("Warning, use: 'sudo a2ensite loncapassl.conf' to activate LON-CAPA SSL Apache config\n"));
+                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;
+    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 {
@@ -1365,7 +1672,8 @@
 my $dsn = "DBI:mysql:database=mysql";
 my ($distro,$gotprereqs,$localecmd,$packagecmd,$updatecmd,$installnow,$mysqlrestart,
     $recommended,$dbh,$has_pass,$has_lcdb,$downloadstatus,$filetouse,$production,
-    $testing,$apachefw,$uses_systemctl,$hostname) = &check_required($instdir,$dsn);
+    $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].',
@@ -1543,19 +1851,35 @@
 }
 
 if ($callsub{'apachessl'}) {
+    my $targetdir = '/etc/httpd/conf.d';
     if ($distro =~ /^(suse|sles)/) {
-        &copy_apache_sslconf_file($instdir,'/etc/apache2/vhosts.d',$hostname);
+        $targetdir = '/etc/apache2/vhosts.d';
     } elsif ($distro =~ /^(debian|ubuntu)/) {
-        my $apache2_sites_available_dir = '/etc/apache2/sites-available';
-        if (&copy_apache_sslconf_file($instdir,$apache2_sites_available_dir,$hostname)) {
-            my $apache2_sites_enabled_dir = '/etc/apache2/sites-enabled';
-            my $made_symlink =  eval { symlink("$apache2_sites_available_dir/loncapassl.conf","$apache2_sites_enabled_dir/loncapassl.conf"); 1 };
-            if ($made_symlink) {
-                print_and_log(&mt('Enabling "[_1]" Apache SSL configuration.','loncapassl.conf')."\n");  
+        $targetdir = '/etc/apache2/sites-available';
+    }
+    my ($new_rewrite,$new_int) = 
+        &copy_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");
+                    }
+                }
             }
         }
-    } else {
-        &copy_apache_sslconf_file($instdir,'/etc/httpd/conf.d',$hostname);
     }
     print_and_log("\n");
 } else {
@@ -2061,43 +2385,479 @@
 
 ###############################################
 ##
-## Copy/Modify loncapassl.conf
+## Copy loncapassl.conf and sslrewrite.conf
 ##
 ###############################################
 
-sub copy_apache_sslconf_file {
-    my ($instdir,$targetdir,$hostname) = @_;
-    my ($success,$error);
+#
+# 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-<hostname> where <hostname>
+# is the server's hostname. There is no need to create a DNS entry 
+# for internal-<hostname>, 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
+# <VirtualHost *:443> </VirtualHost> 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, at 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")) {
-            if (open(my $out,'>',"$targetdir/loncapassl.conf")) {
-                while (<$fh>) {
-                    if (/^\QServerName internal-\E/) {
-                        chomp();
-                        s/^(\QServerName internal-\E)(.*)$/$1$hostname\n/;
+            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 "<VirtualHost *:443>\n".
+                                     "ServerName $hostname\n".
+                                     join("\n", at standard)."\n".
+                                     "</VirtualHost>\n\n".
+                                     join('', at 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", at 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 "<VirtualHost *:443>\n".
+                             "ServerName $hostname\n".
+                             join("\n", at standard)."\n".
+                             "</VirtualHost>\n\n".
+                             join("\n", at 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", at 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 
+# <IfModule +mod_rewrite.c> </IfModule> 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\s+[^:]*\:443>/) {
+                    $virtualhost = 1;
+                    unless ($endvhost) {
+                        $startvhost = $currline;
                     }
-                    print $out $_;
                 }
-                $success = 1;
+                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, at 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$/, at 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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 {
-                $error = "Could not write to $targetdir/loncapassl.conf";
+                $numfiles = -1;
             }
-        } else {
-            $error = "Could not read from $instdir/loncapassl.conf";
         }
-    } else {
-        $error = "File to copy from: $instdir/loncapassl.conf does not exist";
     }
-    if ($success) {
-        print_and_log(&mt('Successfully copied [_1] to [_2].',"'loncapassl.conf'","'$targetdir/loncapassl.conf'")."\n");
-        chmod(0444,"$targetdir/loncapassl.conf");
-    } else {
-        print_and_log(&mt('Failed to copy [_1] to [_2].',"'loncapassl.conf'","'$targetdir/loncapassl.conf'")."\n");
-        if ($error) {
-            print_and_log("$error\n");
+    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 = <STDIN>;
+    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 <VirtualHost *:443> </VirtualHost> blocks containing
+# (a) the default VirtualHost with the <IfModule mod_rewrite.c> </IfModule> 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*<VirtualHost\s+[^:]*\:443>\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*<IfModule\s+mod_rewrite\.c>/) {
+                        $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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 <VirtualHost *:443> </VirtualHost> 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 $success;
+    return $filename;
 }
 
 #########################################################


More information about the LON-CAPA-cvs mailing list