From abb8d8ee023494cee25efa1cffdb4d43db76f3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bi=C4=8Di=C5=A1t=C4=9B?= Date: Fri, 17 Mar 2023 12:46:58 +0100 Subject: [PATCH 1/3] nginx reverseproxy plugin --- .../web/admin/form/server_config.tform.php | 2 +- .../templates/server_config_web_edit.htm | 7 +- .../templates/web_vhost_domain_advanced.htm | 5 +- .../sites/templates/web_vhost_domain_edit.htm | 10 +- interface/web/sites/web_vhost_domain_edit.php | 10 +- ...inx_reverse_proxy_plugin.vhost.conf.master | 125 -- .../conf/nginx_reverseproxy_vhost.conf.master | 325 ++++ server/mods-available/web_module.inc.php | 48 + .../plugins-available/apache2_plugin.inc.php | 66 +- .../nginx_reverseproxy_plugin.inc.php | 1472 ++++++++++++++--- 10 files changed, 1727 insertions(+), 343 deletions(-) delete mode 100644 server/conf/nginx_reverse_proxy_plugin.vhost.conf.master create mode 100644 server/conf/nginx_reverseproxy_vhost.conf.master diff --git a/interface/web/admin/form/server_config.tform.php b/interface/web/admin/form/server_config.tform.php index 679ebb2b7f..39db1e77a3 100644 --- a/interface/web/admin/form/server_config.tform.php +++ b/interface/web/admin/form/server_config.tform.php @@ -848,7 +848,7 @@ $form["tabs"]['web'] = array( 'datatype' => 'VARCHAR', 'formtype' => 'SELECT', 'default' => 'apache', - 'value' => array('apache' => 'Apache', 'nginx' => 'Nginx') + 'value' => array('apache' => 'Apache', 'nginx' => 'Nginx', 'apachenginx' => 'Apache with Nginx as reverse proxy') ), 'website_basedir' => array( 'datatype' => 'VARCHAR', diff --git a/interface/web/admin/templates/server_config_web_edit.htm b/interface/web/admin/templates/server_config_web_edit.htm index 2161907cb5..f83438fb60 100644 --- a/interface/web/admin/templates/server_config_web_edit.htm +++ b/interface/web/admin/templates/server_config_web_edit.htm @@ -426,7 +426,7 @@ diff --git a/interface/web/sites/templates/web_vhost_domain_advanced.htm b/interface/web/sites/templates/web_vhost_domain_advanced.htm index 4451545e13..ac05109e48 100644 --- a/interface/web/sites/templates/web_vhost_domain_advanced.htm +++ b/interface/web/sites/templates/web_vhost_domain_advanced.htm @@ -201,9 +201,12 @@ if(data.servertype == "nginx"){ jQuery('.nginx').show(); jQuery('.apache').hide(); - } else { + } else if(data.servertype == "apache"){ jQuery('.nginx').hide(); jQuery('.apache').show(); + } else if(data.servertype == "apachenginx"){ + jQuery('.nginx').show(); + jQuery('.apache').show(); } }); jQuery.getJSON('sites/ajax_get_json.php'+ '?' + Math.round(new Date().getTime()), {web_id : webId, type : "getphptype"}, function(data) { diff --git a/interface/web/sites/templates/web_vhost_domain_edit.htm b/interface/web/sites/templates/web_vhost_domain_edit.htm index c4421aac74..9af68725b5 100644 --- a/interface/web/sites/templates/web_vhost_domain_edit.htm +++ b/interface/web/sites/templates/web_vhost_domain_edit.htm @@ -405,7 +405,7 @@ jQuery('#php option[value="cgi"]').hide(); jQuery('#php option[value="mod"]').hide(); jQuery('#php option[value="suphp"]').hide(); - } else { + } else if(data.servertype == "apache") { serverType = 'apache'; jQuery('.nginx').hide(); jQuery('.apache').show(); @@ -413,6 +413,14 @@ jQuery('#php option[value="cgi"]').show(); jQuery('#php option[value="mod"]').show(); jQuery('#php option[value="suphp"]').show(); + } else if(data.servertype == "apachenginx") { + serverType = 'apachenginx'; + jQuery('.nginx').show(); + jQuery('.apache').show(); + jQuery('#php option[value="fast-cgi"]').show(); + jQuery('#php option[value="cgi"]').show(); + jQuery('#php option[value="mod"]').show(); + jQuery('#php option[value="suphp"]').show(); } if(noFormChange) { ISPConfig.resetFormChanged(); diff --git a/interface/web/sites/web_vhost_domain_edit.php b/interface/web/sites/web_vhost_domain_edit.php index 365582e9af..5b9a3ae1ec 100644 --- a/interface/web/sites/web_vhost_domain_edit.php +++ b/interface/web/sites/web_vhost_domain_edit.php @@ -431,7 +431,7 @@ class page_action extends tform_actions { if($php_directive_snippets_txt == '') $php_directive_snippets_txt = '------'; $app->tpl->setVar("php_directive_snippets_txt", $php_directive_snippets_txt); - if($server_type == 'apache'){ + if($server_type == 'apache' || $server_type == 'apachenginx'){ $apache_directive_snippets_txt = ''; $apache_directive_snippets = $app->db->queryAllRecords("SELECT * FROM directive_snippets WHERE type = 'apache' AND active = 'y' ORDER BY name"); if(is_array($apache_directive_snippets) && !empty($apache_directive_snippets)){ @@ -445,7 +445,7 @@ class page_action extends tform_actions { $app->tpl->setVar("apache_directive_snippets_txt", $apache_directive_snippets_txt); } - if($server_type == 'nginx'){ + if($server_type == 'nginx' || $server_type == 'apachenginx'){ $nginx_directive_snippets_txt = ''; $nginx_directive_snippets = $app->db->queryAllRecords("SELECT * FROM directive_snippets WHERE type = 'nginx' AND active = 'y' ORDER BY name"); if(is_array($nginx_directive_snippets) && !empty($nginx_directive_snippets)){ @@ -605,7 +605,7 @@ class page_action extends tform_actions { if($php_directive_snippets_txt == '') $php_directive_snippets_txt = '------'; $app->tpl->setVar("php_directive_snippets_txt", $php_directive_snippets_txt); - if($server_type == 'apache'){ + if($server_type == 'apache' || $server_type == 'apachenginx'){ $apache_directive_snippets_txt = ''; $apache_directive_snippets = $app->db->queryAllRecords("SELECT * FROM directive_snippets WHERE type = 'apache' AND active = 'y' ORDER BY name"); if(is_array($apache_directive_snippets) && !empty($apache_directive_snippets)){ @@ -619,7 +619,7 @@ class page_action extends tform_actions { $app->tpl->setVar("apache_directive_snippets_txt", $apache_directive_snippets_txt); } - if($server_type == 'nginx'){ + if($server_type == 'nginx' || $server_type == 'apachenginx'){ $nginx_directive_snippets_txt = ''; $nginx_directive_snippets = $app->db->queryAllRecords("SELECT * FROM directive_snippets WHERE type = 'nginx' AND active = 'y' ORDER BY name"); if(is_array($nginx_directive_snippets) && !empty($nginx_directive_snippets)){ @@ -1129,7 +1129,7 @@ class page_action extends tform_actions { // Check rewrite rules $server_type = $web_config['server_type']; - if($server_type == 'nginx' && isset($this->dataRecord['rewrite_rules']) && trim($this->dataRecord['rewrite_rules']) != '') { + if(($server_type == 'nginx' || $server_type == 'apachenginx') && isset($this->dataRecord['rewrite_rules']) && trim($this->dataRecord['rewrite_rules']) != '') { $rewrite_rules = trim($this->dataRecord['rewrite_rules']); $rewrites_are_valid = true; // use this counter to make sure all curly brackets are properly closed diff --git a/server/conf/nginx_reverse_proxy_plugin.vhost.conf.master b/server/conf/nginx_reverse_proxy_plugin.vhost.conf.master deleted file mode 100644 index e1073e8051..0000000000 --- a/server/conf/nginx_reverse_proxy_plugin.vhost.conf.master +++ /dev/null @@ -1,125 +0,0 @@ - -server { - - ###################################################################### - ## Server configuration - ###################################################################### - - # Tell nginx to listen on port (default http(s) port, IPv4) - listen :; - - - # Tell nginx to listen on port (default http(s) port, IPv6) - listen []: ipv6only=on; - - - server_name ; - - ###################################################################### - ## Log configuration - ###################################################################### - - access_log /var/log/ispconfig/httpd//access.log combined; - error_log /var/log/ispconfig/httpd//error.log; - - - ###################################################################### - ## SSL configuration - ###################################################################### - - ssl on; - ssl_certificate /.nginx.crt; - ssl_certificate_key /.nginx.key; - - - ###################################################################### - ## Redirects configuration - ###################################################################### - - - # SEO Redirect - if ($http_host = "") { - rewrite ^ $scheme://$request_uri permanent; - } - - - - - # Redirects - #if ($http_host ~* "$") { - # rewrite ^/(.+)$ $1 ; - #} - - - - ###################################################################### - ## Error configuration - ###################################################################### - - error_page 400 /error/400.html; - error_page 401 /error/401.html; - error_page 403 /error/403.html; - error_page 404 /error/404.html; - error_page 405 /error/405.html; - error_page 500 /error/500.html; - error_page 502 /error/502.html; - error_page 503 /error/503.html; - - ###################################################################### - ## Locations configuration - ###################################################################### - - location / { - - # disable access log (we already have it for nginx) - access_log off; - - # set the document root - root ; - - # cache apache2's answers in the cache - proxy_cache nginx_cache; - - # pass all requests to apache2 - - proxy_pass http://:; - - proxy_pass http://:; - - - } - - location ~ /\. { - - # Don't allow any access - deny all; - - # Don't log access - access_log off; - - } - - ###################################################################### - ## Directives configuration - ###################################################################### - - - - - - ###################################################################### - ## CP configuration - ###################################################################### - - # If domain.tld/ispconfig is requested, redirect to the secure ISPConfig URL - location = /ispconfig { rewrite ^ / permanent; } - - # If domain.tld/phpmyadmin is requested, redirect to the secure phpMyAdmin URL - location = /phpmyadmin { rewrite ^ /phpmyadmin/ permanent; } - - # If domain.tld/webmail is requested, redirect to the secure RoundCube Webmail URL - location = /webmail { rewrite ^ /webmail/ permanent; } - -} - - \ No newline at end of file diff --git a/server/conf/nginx_reverseproxy_vhost.conf.master b/server/conf/nginx_reverseproxy_vhost.conf.master new file mode 100644 index 0000000000..373dba5bd6 --- /dev/null +++ b/server/conf/nginx_reverseproxy_vhost.conf.master @@ -0,0 +1,325 @@ +server { + listen :; + + listen []:; + + + listen [::]:; + + + listen : ssl http2; + + + listen : ssl http2 proxy_protocol; + + + + + ssl_protocols TLSv1.3 TLSv1.2; + + ssl_protocols TLSv1.2; + + # ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; + # ssl_prefer_server_ciphers on; + + listen []: ssl http2; + + + listen [::]: ssl http2; + + ssl_certificate ; + ssl_certificate_key ; + + + server_name ; + + root ; + disable_symlinks if_not_owner from=$document_root; + + + + if ($scheme != "https") { + rewrite ^(?!/\.well-known/acme-challenge)/ https://$http_host$request_uri? permanent; + } + + + + if ($http_host "") { + rewrite ^(?!/\.well-known/acme-challenge)/ $scheme://$request_uri? permanent; + } + + + if ($http_host "") { + rewrite ^(?!/\.well-known/acme-challenge)/ $scheme://$request_uri? permanent; + } + + + if ($http_host "") { + rewrite ^(.*)$ $2 ; + } + + + + + if ($http_host != "") { rewrite ^(.*)$ $2 ; } + + + location / { + proxy_pass ; + rewrite ^/(.*) /$1; + + + + } + + + + index index.html index.htm index.php index.cgi index.pl index.xhtml standard_index.html; + + + location ~ \.shtml$ { + ssi on; + } + + + + error_page 400 /error/400.html; + error_page 401 /error/401.html; + error_page 403 /error/403.html; + error_page 404 /error/404.html; + error_page 405 /error/405.html; + error_page 500 /error/500.html; + error_page 502 /error/502.html; + error_page 503 /error/503.html; + recursive_error_pages on; + location = /error/400.html { + + internal; + auth_basic off; + } + location = /error/401.html { + + internal; + auth_basic off; + } + location = /error/403.html { + + internal; + auth_basic off; + } + location = /error/404.html { + + internal; + auth_basic off; + } + location = /error/405.html { + + internal; + auth_basic off; + } + location = /error/500.html { + + internal; + auth_basic off; + } + location = /error/502.html { + + internal; + auth_basic off; + } + location = /error/503.html { + + internal; + auth_basic off; + } + + + + error_log /var/log/ispconfig/httpd//error.log; + access_log /var/log/ispconfig/httpd//access.log combined; + + + error_log /var/log/ispconfig/httpd//error.log; + access_log /var/log/ispconfig/httpd//access.log anonymized; + + + location @fallback { + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_pass http://127.0.0.1:6080; + proxy_redirect http://127.0.0.1:6080 /; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + } + + location / { + location ~ [^/]\.ph(p\d*|tml)$ { + try_files /does_not_exists @fallback; + } + location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar|swf)$ { + expires 1s; + try_files $uri $uri/ @fallback; + } + location / { + try_files /does_not_exists @fallback; + } + } + + + ## Disable .htaccess and other hidden files + location ~ /\. { + deny all; + } + + ## Allow access for .well-known/acme-challenge + location ^~ /.well-known/acme-challenge/ { + access_log off; + log_not_found off; + auth_basic off; + root /usr/local/ispconfig/interface/acme/; + autoindex off; + index index.html; + try_files $uri $uri/ =404; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + expires max; + add_header Cache-Control "public, must-revalidate, proxy-revalidate"; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location /stats/ { + + index index.html index.php; + auth_basic "Members Only"; + auth_basic_user_file ; + add_header Content-Security-Policy "default-src * 'self' 'unsafe-inline' 'unsafe-eval' data:;"; + } + + location ^~ /awstats-icon { + alias /usr/share/awstats/icon; + } + + + + + + + + + + + pagespeed on; + pagespeed FileCachePath /var/ngx_pagespeed_cache; + pagespeed FetchHttps enable,allow_self_signed; + + + # let's speed up PageSpeed by storing it in the super duper fast memcached + pagespeed MemcachedThreads 1; + pagespeed MemcachedServers "localhost:11211"; + + # Filter settings + pagespeed RewriteLevel CoreFilters; + pagespeed EnableFilters collapse_whitespace,remove_comments; + + # Ensure requests for pagespeed optimized resources go to the pagespeed + # handler and no extraneous headers get set. + location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { + add_header "" ""; + access_log off; + } + location ~ "^/ngx_pagespeed_static/" { + access_log off; + } + location ~ "^/ngx_pagespeed_beacon$" { + access_log off; + } + location /ngx_pagespeed_statistics { + allow 127.0.0.1; + deny all; + access_log off; + } + location /ngx_pagespeed_global_statistics { + allow 127.0.0.1; + deny all; + access_log off; + } + location /ngx_pagespeed_message { + allow 127.0.0.1; + deny all; + access_log off; + } + location /pagespeed_console { + allow 127.0.0.1; + deny all; + access_log off; + } + + + + location { ##merge## + auth_basic "Members Only"; + auth_basic_user_file .htpasswd; + } + + +} + + +server { + listen :; + + listen []:; + + + listen [::]:; + + + + + listen : ssl http2; + + listen []: ssl http2; + + + listen [::]: ssl http2; + + ssl_certificate ; + ssl_certificate_key ; + + + server_name ; + + + + if ($http_host "") { + rewrite ^(?!/\.well-known/acme-challenge)/ $scheme://$request_uri? permanent; + } + + + ## no redirect for acme + location ^~ /.well-known/acme-challenge/ { + access_log off; + log_not_found off; + root /usr/local/ispconfig/interface/acme/; + autoindex off; + index index.html; + try_files $uri $uri/ =404; + } + + location / { + rewrite ^ $request_uri? ; + } + +} + diff --git a/server/mods-available/web_module.inc.php b/server/mods-available/web_module.inc.php index bb2de0d1de..3eb847ad18 100644 --- a/server/mods-available/web_module.inc.php +++ b/server/mods-available/web_module.inc.php @@ -123,6 +123,7 @@ class web_module { // Register service $app->services->registerService('httpd', 'web_module', 'restartHttpd'); + $app->services->registerService('nginx', 'web_module', 'restartNginx'); $app->services->registerService('php-fpm', 'web_module', 'restartPHP_FPM'); } @@ -264,6 +265,53 @@ class web_module { return $retval; } + function restartNginx($action = 'restart') { + global $app, $conf; + + // load the server configuration options + $app->uses('getconf,system'); + + $daemon = 'nginx'; + + $retval = array('output' => '', 'retval' => 0); + if($action == 'restart') { + $cmd = $app->system->getinitcommand($daemon, 'restart'); + } elseif($action == 'force-reload') { + $cmd = $app->system->getinitcommand($daemon, 'force-reload'); + } else { + $cmd = $app->system->getinitcommand($daemon, 'reload'); + } + + $app->log("Checking nginx configuration...", LOGLEVEL_DEBUG); + exec('nginx -t 2>&1', $retval['output'], $retval['retval']); + if($retval['retval'] == 0){ + $app->log("nginx configuration ok!", LOGLEVEL_DEBUG); + } else { + $app->log("nginx config test failed!", LOGLEVEL_DEBUG); + return $retval; + } + + $app->log("Restarting nginx: $cmd", LOGLEVEL_DEBUG); + + if($cmd != '') { + exec($cmd.' 2>&1', $retval['output'], $retval['retval']); + } else { + $app->log('We got no init command, restart or reload of service aborted.',LOGLEVEL_WARN); + } + + // if restart failed despite successful syntax check => try again + if($retval['retval'] > 0){ + sleep(2); + exec($cmd.' 2>&1', $retval['output'], $retval['retval']); + } + + // nginx: do a syntax check because on some distributions, the init script always returns 0 - even if the syntax is not ok (how stupid is that?) + //if($retval['retval'] == 0){ + //exec('nginx -t 2>&1', $retval['output'], $retval['retval']); + //} + return $retval; + } + function restartPHP_FPM($action = 'restart') { global $app, $conf; diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php index d411cc74dc..9181b7cfb6 100644 --- a/server/plugins-available/apache2_plugin.inc.php +++ b/server/plugins-available/apache2_plugin.inc.php @@ -266,7 +266,7 @@ class apache2_plugin { // load the server configuration options $app->uses('getconf'); $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); - if (isset($web_config['CA_path']) && $web_config['CA_path'] !='' && !file_exists($web_config['CA_path'].'/openssl.cnf')) + if ($web_config['CA_path']!='' && !file_exists($web_config['CA_path'].'/openssl.cnf')) $app->log("CA path error, file does not exist:".$web_config['CA_path'].'/openssl.cnf', LOGLEVEL_ERROR); //* Only vhosts can have a ssl cert @@ -1169,7 +1169,7 @@ class apache2_plugin { if(!is_dir($web_config['website_basedir'].'/conf')) $app->system->mkdir($web_config['website_basedir'].'/conf'); //* add open_basedir restriction to custom php.ini content, required for suphp only - if(isset($data['new']['custom_php_ini']) && !stristr($data['new']['custom_php_ini'], 'open_basedir') && $data['new']['php'] == 'suphp') { + if(!stristr($data['new']['custom_php_ini'], 'open_basedir') && $data['new']['php'] == 'suphp') { $data['new']['custom_php_ini'] .= "\nopen_basedir = '".$data['new']['php_open_basedir']."'\n"; } @@ -1194,7 +1194,7 @@ class apache2_plugin { //* Create custom php.ini # Because of custom default PHP directives from snippet # php.ini custom values order os: 1. general settings 2. Directive Snippets settings 3. custom php.ini settings defined in domain settings - if((isset($data['new']['custom_php_ini']) && trim($data['new']['custom_php_ini']) != '') || $data['new']['directive_snippets_id'] > "0") { + if(trim($data['new']['custom_php_ini']) != '' || $data['new']['directive_snippets_id'] > "0") { $has_custom_php_ini = true; $custom_sendmail_path = false; if(!is_dir($custom_php_ini_dir)) $app->system->mkdirpath($custom_php_ini_dir); @@ -1400,12 +1400,14 @@ class apache2_plugin { $server_alias = array(); - if(isset($web_config['website_autoalias']) && $web_config['website_autoalias'] != '') { + // get autoalias + $auto_alias = $web_config['website_autoalias']; + if($auto_alias != '') { // get the client username $client = $app->db->queryOneRecord("SELECT `username` FROM `client` WHERE `client_id` = ?", $client_id); $aa_search = array('[client_id]', '[website_id]', '[client_username]', '[website_domain]'); $aa_replace = array($client_id, $data['new']['domain_id'], $client['username'], $data['new']['domain']); - $auto_alias = str_replace($aa_search, $aa_replace, $web_config['website_autoalias']); + $auto_alias = str_replace($aa_search, $aa_replace, $auto_alias); unset($client); unset($aa_search); unset($aa_replace); @@ -1518,13 +1520,13 @@ class apache2_plugin { if (count($rewrite_wildcard_rules) > 0) $rewrite_rules = array_merge($rewrite_rules, $rewrite_wildcard_rules); // Append wildcard rules to the end of rules - if(count($rewrite_rules) > 0 || $vhost_data['seo_redirect_enabled'] > 0 || count($alias_seo_redirects) > 0 || $data['new']['rewrite_to_https'] == 'y') { + if((count($rewrite_rules) > 0 || $vhost_data['seo_redirect_enabled'] > 0 || count($alias_seo_redirects) > 0 || $data['new']['rewrite_to_https'] == 'y')) { $tpl->setVar('rewrite_enabled', 1); } else { $tpl->setVar('rewrite_enabled', 0); } - if($data['new']['ssl'] == 'n') { + if($data['new']['ssl'] == 'n' || $this->nginx_reverseproxy_enable()) { $tpl->setVar('rewrite_to_https', 'n'); } @@ -1784,14 +1786,15 @@ class apache2_plugin { unset($newip); } - $tmp_vhost_arr = array('ip_address' => $data['new']['ip_address'], 'ssl_enabled' => 0, 'port' => 80); + $tmp_vhost_arr = array('ip_address' => $data['new']['ip_address'], 'ssl_enabled' => 0, 'port' => $this->get_apache_port('http')); + if($this->nginx_reverseproxy_enable()) $tmp_vhost_arr = $tmp_vhost_arr + array('use_proxy_protocol' => 'y'); if(count($rewrite_rules) > 0) $tmp_vhost_arr = $tmp_vhost_arr + array('redirects' => $rewrite_rules); if(count($alias_seo_redirects) > 0) $tmp_vhost_arr = $tmp_vhost_arr + array('alias_seo_redirects' => $alias_seo_redirects); $vhosts[] = $tmp_vhost_arr; //if proxy protocol is enabled we need to add a new port to lsiten to if($web_config['vhost_proxy_protocol_enabled'] == 'y' && $data['new']['proxy_protocol'] == 'y'){ - if(isset($web_config['vhost_proxy_protocol_http_port']) && (int)$web_config['vhost_proxy_protocol_http_port'] > 0) { + if((int)$web_config['vhost_proxy_protocol_http_port'] > 0) { $tmp_vhost_arr['port'] = (int)$web_config['vhost_proxy_protocol_http_port']; $tmp_vhost_arr['use_proxy_protocol'] = $data['new']['proxy_protocol']; $vhosts[] = $tmp_vhost_arr; @@ -1801,8 +1804,8 @@ class apache2_plugin { unset($tmp_vhost_arr); //* Add vhost for ipv4 IP with SSL - if($data['new']['ssl_domain'] != '' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { - $tmp_vhost_arr = array('ip_address' => $data['new']['ip_address'], 'ssl_enabled' => 1, 'port' => '443'); + if(!$this->nginx_reverseproxy_enable() && $data['new']['ssl_domain'] != '' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { + $tmp_vhost_arr = array('ip_address' => $data['new']['ip_address'], 'ssl_enabled' => 1, 'port' => $this->get_apache_port('https')); if(count($rewrite_rules) > 0) $tmp_vhost_arr = $tmp_vhost_arr + array('redirects' => $rewrite_rules); $ipv4_ssl_alias_seo_redirects = $alias_seo_redirects; if(is_array($ipv4_ssl_alias_seo_redirects) && !empty($ipv4_ssl_alias_seo_redirects)){ @@ -1848,8 +1851,8 @@ class apache2_plugin { unset($tmp_vhost_arr); //* Add vhost for ipv6 IP with SSL - if($data['new']['ssl_domain'] != '' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { - $tmp_vhost_arr = array('ip_address' => '['.$data['new']['ipv6_address'].']', 'ssl_enabled' => 1, 'port' => '443'); + if(!$this->nginx_reverseproxy_enable() && $data['new']['ssl_domain'] != '' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { + $tmp_vhost_arr = array('ip_address' => '['.$data['new']['ipv6_address'].']', 'ssl_enabled' => 1, 'port' => $this->get_apache_port('https')); if(count($rewrite_rules) > 0) $tmp_vhost_arr = $tmp_vhost_arr + array('redirects' => $rewrite_rules); $ipv6_ssl_alias_seo_redirects = $alias_seo_redirects; if(is_array($ipv6_ssl_alias_seo_redirects) && !empty($ipv6_ssl_alias_seo_redirects)){ @@ -1940,7 +1943,7 @@ class apache2_plugin { unset($ht_file); if(!is_file($data['new']['document_root'].'/web/stats/.htpasswd_stats') || $data['new']['stats_password'] != $data['old']['stats_password']) { - if(isset($data['new']['stats_password']) && trim($data['new']['stats_password']) != '') { + if(trim($data['new']['stats_password']) != '') { $htp_file = 'admin:'.trim($data['new']['stats_password']); $app->system->web_folder_protection($data['new']['document_root'], false); $app->system->file_put_contents($data['new']['document_root'].'/web/stats/.htpasswd_stats', $htp_file); @@ -1986,7 +1989,7 @@ class apache2_plugin { if($web_config['check_apache_config'] == 'y') { //* Test if apache starts with the new configuration file - $apache_online_status_before_restart = $this->_checkTcp('localhost', 80); + $apache_online_status_before_restart = $this->_checkTcp('localhost', $this->get_apache_port('http')); $app->log('Apache status is: '.($apache_online_status_before_restart === true? 'running' : 'down'), LOGLEVEL_DEBUG); $retval = $app->services->restartService('httpd', 'restart'); // $retval['retval'] is 0 on success and > 0 on failure @@ -1996,7 +1999,7 @@ class apache2_plugin { $apache_online_status_after_restart = false; sleep(2); for($i = 0; $i < 5; $i++) { - $apache_online_status_after_restart = $this->_checkTcp('localhost', 80); + $apache_online_status_after_restart = $this->_checkTcp('localhost', $this->get_apache_port('http')); if($apache_online_status_after_restart) break; sleep(1); } @@ -3650,6 +3653,37 @@ class apache2_plugin { return $seo_redirects; } + private function nginx_reverseproxy_enable() { + //* Check if the Nginx reverseproxy is enabled + if(@is_link('/usr/local/ispconfig/server/plugins-enabled/nginx_reverseproxy_plugin.inc.php')) { + return true; + } + + return false; + } + + private function get_apache_port($protocol = 'http') { + if (!$this->nginx_reverseproxy_enable()) { + switch ($protocol) { + case "http": + return 80; + break; + case "https": + return 443; + break; + } + } else { + switch ($protocol) { + case "http": + return 6080; + break; + case "https": + return 6443; + break; + } + } + } + function _setup_jailkit_chroot() { global $app, $conf; diff --git a/server/plugins-available/nginx_reverseproxy_plugin.inc.php b/server/plugins-available/nginx_reverseproxy_plugin.inc.php index f35f40d34a..860c5ac49f 100644 --- a/server/plugins-available/nginx_reverseproxy_plugin.inc.php +++ b/server/plugins-available/nginx_reverseproxy_plugin.inc.php @@ -1,64 +1,97 @@ + */ class nginx_reverseproxy_plugin { + /** + * Stores the internal plugin name. + * + * @var string + */ var $plugin_name = 'nginx_reverseproxy_plugin'; + + /** + * Stores the internal class name. + * + * Needs to be the same as $plugin_name. + * + * @var string + */ var $class_name = 'nginx_reverseproxy_plugin'; - // private variables + /** + * Stores the current vhost action. + * + * When ISPConfig triggers the vhost event, it passes either create,update,delete etc. + * + * @see onLoad() + * + * @var string + */ var $action = ''; - //* This function is called during ispconfig installation to determine - // if a symlink shall be created for this plugin. + + /** + * ISPConfig onInstall hook. + * + * Called during ISPConfig installation to determine if a symlink shall be created. + * + * @return bool create symlink if true + */ function onInstall() { global $conf; - - if(isset($conf['services']['proxy']) && $conf['services']['proxy'] == true && isset($conf['nginx']['installed']) && $conf['nginx']['installed'] == true) { - return true; - } else { - return false; - } - + return $conf['services']['web'] == true; } - - /* - This function is called when the plugin is loaded - */ - + /** + * ISPConfig onLoad hook. + * + * Register the plugin for some site related events. + */ function onLoad() { global $app; - /* - Register for the events - */ - - $app->plugins->registerEvent('web_domain_insert', $this->plugin_name, 'ssl'); - $app->plugins->registerEvent('web_domain_update', $this->plugin_name, 'ssl'); - $app->plugins->registerEvent('web_domain_delete', $this->plugin_name, 'ssl'); - $app->plugins->registerEvent('web_domain_insert', $this->plugin_name, 'insert'); $app->plugins->registerEvent('web_domain_update', $this->plugin_name, 'update'); $app->plugins->registerEvent('web_domain_delete', $this->plugin_name, 'delete'); - - // $app->plugins->registerEvent('proxy_reverse_insert',$this->plugin_name,'rewrite_insert'); - // $app->plugins->registerEvent('proxy_reverse_update',$this->plugin_name,'rewrite_update'); - // $app->plugins->registerEvent('proxy_reverse_delete',$this->plugin_name,'rewrite_delete'); - - - } - - function insert($event_name, $data) { + /** + * ISPConfig insert hook. + * + * Called every time a new site is created. + * + * @uses update() + * + * @param string $event_name the event/action name + * @param array $data the vhost data + */ + function insert($event_name, $data) { global $app, $conf; - // just run the update function + $this->action = 'insert'; $this->update($event_name, $data); } - - function update($event_name, $data) { + /** + * ISPConfig update hook. + * + * Called every time a site gets updated from within ISPConfig. + * + * @see insert() + * @see delete() + * + * @param string $event_name the event/action name + * @param array $data the vhost data + */ + function update($event_name, $data) { global $app, $conf; if($this->action != 'insert') $this->action = 'update'; @@ -68,9 +101,9 @@ class nginx_reverseproxy_plugin { $old_parent_domain_id = intval($data['old']['parent_domain_id']); $new_parent_domain_id = intval($data['new']['parent_domain_id']); - // If the parent_domain_id has been chenged, we will have to update the old site as well. + // If the parent_domain_id has been changed, we will have to update the old site as well. if($this->action == 'update' && $data['new']['parent_domain_id'] != $data['old']['parent_domain_id']) { - $tmp = $app->dbmaster->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ? AND active = 'y'", $old_parent_domain_id); + $tmp = $app->db->queryOneRecord('SELECT * FROM web_domain WHERE domain_id = ? AND active = ?', $old_parent_domain_id, 'y'); $data['new'] = $tmp; $data['old'] = $tmp; $this->action = 'update'; @@ -78,21 +111,79 @@ class nginx_reverseproxy_plugin { } // This is not a vhost, so we need to update the parent record instead. - $tmp = $app->dbmaster->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ? AND active = 'y'", $new_parent_domain_id); + $tmp = $app->db->queryOneRecord('SELECT * FROM web_domain WHERE domain_id = ? AND active = ?', $new_parent_domain_id, 'y'); $data['new'] = $tmp; $data['old'] = $tmp; $this->action = 'update'; } + // load the server configuration options + $app->uses('getconf'); + $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); + //* Check if nginx is using a chrooted setup + if($web_config['website_basedir'] != '' && @is_file($web_config['website_basedir'].'/etc/passwd')) { + $nginx_chrooted = true; + $app->log('Info: nginx is chrooted.', LOGLEVEL_DEBUG); + } else { + $nginx_chrooted = false; + } + if($data['new']['document_root'] == '') { + if($data['new']['type'] == 'vhost' || $data['new']['type'] == 'vhostsubdomain' || $data['new']['type'] == 'vhostalias') $app->log('document_root not set', LOGLEVEL_WARN); + return 0; + } + if($app->system->is_allowed_user($data['new']['system_user'], $app->system->is_user($data['new']['system_user']), true) == false + || $app->system->is_allowed_group($data['new']['system_group'], $app->system->is_group($data['new']['system_group']), true) == false) { + $app->log('Problem with website user or group. Websites cannot be owned by root or an existing user/group. User: '.$data['new']['system_user'].' Group: '.$data['new']['system_group'], LOGLEVEL_WARN); + return 0; + } + if(trim($data['new']['domain']) == '') { + $app->log('domain is empty', LOGLEVEL_WARN); + return 0; + } - // load the server configuration options - $app->uses('getconf'); - $nginx_config = $app->getconf->get_server_config($conf['server_id'], 'web'); + $web_folder = 'web'; + $log_folder = 'log'; + $old_web_folder = 'web'; + $old_log_folder = 'log'; + if($data['new']['type'] == 'vhost'){ + if($data['new']['web_folder'] != ''){ + if(substr($data['new']['web_folder'],0,1) == '/') $data['new']['web_folder'] = substr($data['new']['web_folder'],1); + if(substr($data['new']['web_folder'],-1) == '/') $data['new']['web_folder'] = substr($data['new']['web_folder'],0,-1); + } + $web_folder .= '/'.$data['new']['web_folder']; - // Create group and user, if not exist - $app->uses('system'); + if($data['old']['web_folder'] != ''){ + if(substr($data['old']['web_folder'],0,1) == '/') $data['old']['web_folder'] = substr($data['old']['web_folder'],1); + if(substr($data['old']['web_folder'],-1) == '/') $data['old']['web_folder'] = substr($data['old']['web_folder'],0,-1); + } + $old_web_folder .= '/'.$data['old']['web_folder']; + } + if($data['new']['type'] == 'vhostsubdomain' || $data['new']['type'] == 'vhostalias') { + // new one + $tmp = $app->db->queryOneRecord('SELECT `domain` FROM web_domain WHERE domain_id = ?', $data['new']['parent_domain_id']); + $subdomain_host = preg_replace('/^(.*)\.' . preg_quote($tmp['domain'], '/') . '$/', '$1', $data['new']['domain']); + if($subdomain_host == '') $subdomain_host = 'web'.$data['new']['domain_id']; + $web_folder = $data['new']['web_folder']; + $log_folder .= '/' . $subdomain_host; + unset($tmp); + + if($app->system->is_blacklisted_web_path($web_folder)) { + $app->log('Vhost ' . $subdomain_host . ' is using a blacklisted web folder: ' . $web_folder, LOGLEVEL_ERROR); + return 0; + } + + if(isset($data['old']['parent_domain_id'])) { + // old one + $tmp = $app->db->queryOneRecord('SELECT `domain` FROM web_domain WHERE domain_id = ?', $data['old']['parent_domain_id']); + $subdomain_host = preg_replace('/^(.*)\.' . preg_quote($tmp['domain'], '/') . '$/', '$1', $data['old']['domain']); + if($subdomain_host == '') $subdomain_host = 'web'.$data['old']['domain_id']; + $old_web_folder = $data['old']['web_folder']; + $old_log_folder .= '/' . $subdomain_host; + unset($tmp); + } + } //* Create the vhost config file $app->load('tpl'); @@ -100,63 +191,718 @@ class nginx_reverseproxy_plugin { $tpl = new tpl(); $tpl->newTemplate('nginx_reverseproxy_vhost.conf.master'); + // IPv4 + if($data['new']['ip_address'] == '') $data['new']['ip_address'] = '*'; + + //* use ip-mapping for web-mirror + if($data['new']['ip_address'] != '*' && $conf['mirror_server_id'] > 0) { + $sql = "SELECT destination_ip FROM server_ip_map WHERE server_id = ? AND source_ip = ?"; + $newip = $app->db->queryOneRecord($sql, $conf['server_id'], $data['new']['ip_address']); + $data['new']['ip_address'] = $newip['destination_ip']; + unset($newip); + } + $vhost_data = $data['new']; - $vhost_data['config_dir'] = $config['nginx']['config_dir']; - $vhost_data['ssl_domain'] = $data['new']['ssl_domain']; - // Check if a SSL cert exists - $ssl_dir = $config['nginx']['config_dir'].'/ssl'; - $domain = $data['new']['ssl_domain']; - $key_file = $ssl_dir.'/'.$domain.'.key'; - $crt_file = $ssl_dir.'/'.$domain.'.crt'; - $bundle_file = $ssl_dir.'/'.$domain.'.bundle'; + //unset($vhost_data['ip_address']); + $vhost_data['web_document_root'] = $data['new']['document_root'].'/' . $web_folder; + $vhost_data['web_document_root_www'] = $web_config['website_basedir'].'/'.$data['new']['domain'].'/' . $web_folder; + $vhost_data['web_basedir'] = $web_config['website_basedir']; + + // IPv6 + if($data['new']['ipv6_address'] != ''){ + $tpl->setVar('ipv6_enabled', 1); + if ($conf['serverconfig']['web']['vhost_rewrite_v6'] == 'y') { + if (isset($conf['serverconfig']['server']['v6_prefix']) && $conf['serverconfig']['server']['v6_prefix'] <> '') { + $explode_v6prefix=explode(':', $conf['serverconfig']['server']['v6_prefix']); + $explode_v6=explode(':', $data['new']['ipv6_address']); + + for ( $i = 0; $i <= count($explode_v6prefix)-1; $i++ ) { + $explode_v6[$i] = $explode_v6prefix[$i]; + } + $data['new']['ipv6_address'] = implode(':', $explode_v6); + $vhost_data['ipv6_address'] = $data['new']['ipv6_address']; + } + } + } + if($data['new']['ip_address'] == '*' && $data['new']['ipv6_address'] == '') $tpl->setVar('ipv6_wildcard', 1); + + // Custom rewrite rules + /* + $final_rewrite_rules = array(); + $custom_rewrite_rules = $data['new']['rewrite_rules']; + // Make sure we only have Unix linebreaks + $custom_rewrite_rules = str_replace("\r\n", "\n", $custom_rewrite_rules); + $custom_rewrite_rules = str_replace("\r", "\n", $custom_rewrite_rules); + $custom_rewrite_rule_lines = explode("\n", $custom_rewrite_rules); + if(is_array($custom_rewrite_rule_lines) && !empty($custom_rewrite_rule_lines)){ + foreach($custom_rewrite_rule_lines as $custom_rewrite_rule_line){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + } + } + $tpl->setLoop('rewrite_rules', $final_rewrite_rules); + */ + + // Custom rewrite rules + $final_rewrite_rules = array(); + + if(isset($data['new']['rewrite_rules']) && trim($data['new']['rewrite_rules']) != '') { + $custom_rewrite_rules = trim($data['new']['rewrite_rules']); + $custom_rewrites_are_valid = true; + // use this counter to make sure all curly brackets are properly closed + $if_level = 0; + // Make sure we only have Unix linebreaks + $custom_rewrite_rules = str_replace("\r\n", "\n", $custom_rewrite_rules); + $custom_rewrite_rules = str_replace("\r", "\n", $custom_rewrite_rules); + $custom_rewrite_rule_lines = explode("\n", $custom_rewrite_rules); + if(is_array($custom_rewrite_rule_lines) && !empty($custom_rewrite_rule_lines)){ + foreach($custom_rewrite_rule_lines as $custom_rewrite_rule_line){ + // ignore comments + if(substr(ltrim($custom_rewrite_rule_line), 0, 1) == '#'){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // empty lines + if(trim($custom_rewrite_rule_line) == ''){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // rewrite + if(preg_match('@^\s*rewrite\s+(^/)?\S+(\$)?\s+\S+(\s+(last|break|redirect|permanent|))?\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + if(preg_match('@^\s*rewrite\s+(^/)?(\'[^\']+\'|"[^"]+")+(\$)?\s+(\'[^\']+\'|"[^"]+")+(\s+(last|break|redirect|permanent|))?\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + if(preg_match('@^\s*rewrite\s+(^/)?(\'[^\']+\'|"[^"]+")+(\$)?\s+\S+(\s+(last|break|redirect|permanent|))?\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + if(preg_match('@^\s*rewrite\s+(^/)?\S+(\$)?\s+(\'[^\']+\'|"[^"]+")+(\s+(last|break|redirect|permanent|))?\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // if + if(preg_match('@^\s*if\s+\(\s*\$\S+(\s+(\!?(=|~|~\*))\s+(\S+|\".+\"))?\s*\)\s*\{\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + $if_level += 1; + continue; + } + // if - check for files, directories, etc. + if(preg_match('@^\s*if\s+\(\s*\!?-(f|d|e|x)\s+\S+\s*\)\s*\{\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + $if_level += 1; + continue; + } + // break + if(preg_match('@^\s*break\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // return code [ text ] + if(preg_match('@^\s*return\s+\d\d\d.*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // return code URL + // return URL + if(preg_match('@^\s*return(\s+\d\d\d)?\s+(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*\@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // set + if(preg_match('@^\s*set\s+\$\S+\s+\S+\s*;\s*$@', $custom_rewrite_rule_line)){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + continue; + } + // closing curly bracket + if(trim($custom_rewrite_rule_line) == '}'){ + $final_rewrite_rules[] = array('rewrite_rule' => $custom_rewrite_rule_line); + $if_level -= 1; + continue; + } + $custom_rewrites_are_valid = false; + break; + } + } + if(!$custom_rewrites_are_valid || $if_level != 0){ + $final_rewrite_rules = array(); + } + } + $tpl->setLoop('rewrite_rules', $final_rewrite_rules); + + // Custom nginx directives + $final_nginx_directives = array(); + if($data['new']['enable_pagespeed'] == 'y'){ + // if PageSpeed is already enabled, don't add configuration again + if(stripos($nginx_directives, 'pagespeed') !== false){ + $vhost_data['enable_pagespeed'] = false; + } else { + $vhost_data['enable_pagespeed'] = true; + } + } else { + $vhost_data['enable_pagespeed'] = false; + } + if(intval($data['new']['directive_snippets_id']) > 0){ + $snippet = $app->db->queryOneRecord("SELECT * FROM directive_snippets WHERE directive_snippets_id = ? AND type = 'nginx' AND active = 'y' AND customer_viewable = 'y'", $data['new']['directive_snippets_id']); + if(isset($snippet['snippet'])){ + $nginx_directives = $snippet['snippet']; + } else { + $nginx_directives = $data['new']['nginx_directives']; + } +/* + if($data['new']['enable_pagespeed'] == 'y'){ + // if PageSpeed is already enabled, don't add configuration again + if(stripos($nginx_directives, 'pagespeed') !== false){ + $vhost_data['enable_pagespeed'] = false; + } else { + $vhost_data['enable_pagespeed'] = true; + } + } else { + $vhost_data['enable_pagespeed'] = false; + } +*/ + } else { + $nginx_directives = $data['new']['nginx_directives']; +// $vhost_data['enable_pagespeed'] = false; + } + if(!$nginx_directives) { + $nginx_directives = ''; // ensure it is not null + } - if($vhost_data['nginx_directives']) { - $vhost_data['nginx_directives'] = preg_replace("/\[IP\]/", $vhost_data['ip_address'], $vhost_data['nginx_directives']); + // folder_directive_snippets + if(trim($data['new']['folder_directive_snippets']) != ''){ + $data['new']['folder_directive_snippets'] = trim($data['new']['folder_directive_snippets']); + $data['new']['folder_directive_snippets'] = str_replace("\r\n", "\n", $data['new']['folder_directive_snippets']); + $data['new']['folder_directive_snippets'] = str_replace("\r", "\n", $data['new']['folder_directive_snippets']); + $folder_directive_snippets_lines = explode("\n", $data['new']['folder_directive_snippets']); + + if(is_array($folder_directive_snippets_lines) && !empty($folder_directive_snippets_lines)){ + foreach($folder_directive_snippets_lines as $folder_directive_snippets_line){ + list($folder_directive_snippets_folder, $folder_directive_snippets_snippets_id) = explode(':', $folder_directive_snippets_line); + + $folder_directive_snippets_folder = trim($folder_directive_snippets_folder); + $folder_directive_snippets_snippets_id = trim($folder_directive_snippets_snippets_id); + + if($folder_directive_snippets_folder != '' && intval($folder_directive_snippets_snippets_id) > 0 && preg_match('@^((?!(.*\.\.)|(.*\./)|(.*//))[^/][\w/_\.\-]{1,100})?$@', $folder_directive_snippets_folder)){ + if(substr($folder_directive_snippets_folder, -1) != '/') $folder_directive_snippets_folder .= '/'; + if(substr($folder_directive_snippets_folder, 0, 1) == '/') $folder_directive_snippets_folder = substr($folder_directive_snippets_folder, 1); + + $master_snippet = $app->db->queryOneRecord("SELECT * FROM directive_snippets WHERE directive_snippets_id = ? AND type = 'nginx' AND active = 'y' AND customer_viewable = 'y'", intval($folder_directive_snippets_snippets_id)); + if(isset($master_snippet['snippet'])){ + $folder_directive_snippets_trans = array('{FOLDER}' => $folder_directive_snippets_folder, '{FOLDERMD5}' => md5($folder_directive_snippets_folder)); + $master_snippet['snippet'] = strtr($master_snippet['snippet'], $folder_directive_snippets_trans); + $nginx_directives .= "\n\n".$master_snippet['snippet']; + } + } + } + } } + // use vLib for template logic + if(trim($nginx_directives) != '') { + $nginx_directives_new = ''; + $ngx_conf_tpl = new tpl(); + $ngx_conf_tpl_tmp_file = tempnam($conf['temppath'], "ngx"); + file_put_contents($ngx_conf_tpl_tmp_file, $nginx_directives); + $ngx_conf_tpl->newTemplate($ngx_conf_tpl_tmp_file); + $ngx_conf_tpl->setVar($vhost_data); + $nginx_directives_new = $ngx_conf_tpl->grab(); + if(is_file($ngx_conf_tpl_tmp_file)) unlink($ngx_conf_tpl_tmp_file); + if($nginx_directives_new != '') $nginx_directives = $nginx_directives_new; + unset($nginx_directives_new); + } - if($data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file)) { + // Make sure we only have Unix linebreaks + $nginx_directives = str_replace("\r\n", "\n", $nginx_directives); + $nginx_directives = str_replace("\r", "\n", $nginx_directives); + $nginx_directive_lines = explode("\n", $nginx_directives); + if(is_array($nginx_directive_lines) && !empty($nginx_directive_lines)){ + $trans = array( + '{DOCROOT}' => $vhost_data['web_document_root_www'], + '{DOCROOT_CLIENT}' => $vhost_data['web_document_root'], + '{DOMAIN}' => $vhost_data['domain'] + ); + foreach($nginx_directive_lines as $nginx_directive_line){ + $final_nginx_directives[] = array('nginx_directive' => strtr($nginx_directive_line, $trans)); + } + } + $tpl->setLoop('nginx_directives', $final_nginx_directives); + + $app->uses('letsencrypt'); + // Check if a SSL cert exists + $tmp = $app->letsencrypt->get_website_certificate_paths($data); + $domain = $tmp['domain']; + $key_file = $tmp['key']; + $csr_file = $tmp['csr']; + $crt_file = $tmp['crt']; + $bundle_file = $tmp['bundle']; + unset($tmp); + + $data['new']['ssl_domain'] = $domain; + $vhost_data['ssl_domain'] = $domain; + $vhost_data['ssl_crt_file'] = $crt_file; + $vhost_data['ssl_key_file'] = $key_file; + $vhost_data['ssl_bundle_file'] = $bundle_file; + + if($domain!='' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { $vhost_data['ssl_enabled'] = 1; $app->log('Enable SSL for: '.$domain, LOGLEVEL_DEBUG); } else { $vhost_data['ssl_enabled'] = 0; - $app->log('Disable SSL for: '.$domain, LOGLEVEL_DEBUG); + $app->log('SSL Disabled. '.$domain, LOGLEVEL_DEBUG); } - if(@is_file($bundle_file)) $vhost_data['has_bundle_cert'] = 1; + // Set SEO Redirect + if($data['new']['seo_redirect'] != ''){ + $vhost_data['seo_redirect_enabled'] = 1; + $tmp_seo_redirects = $this->get_seo_redirects($data['new']); + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + foreach($tmp_seo_redirects as $key => $val){ + $vhost_data[$key] = $val; + } + } else { + $vhost_data['seo_redirect_enabled'] = 0; + } + } else { + $vhost_data['seo_redirect_enabled'] = 0; + } + + // Rewrite rules + $own_rewrite_rules = array(); + $rewrite_rules = array(); + $local_rewrite_rules = array(); + if($data['new']['redirect_type'] != '' && $data['new']['redirect_path'] != '') { + if(substr($data['new']['redirect_path'], -1) != '/') $data['new']['redirect_path'] .= '/'; + if(substr($data['new']['redirect_path'], 0, 8) == '[scheme]'){ + if($data['new']['redirect_type'] != 'proxy'){ + $data['new']['redirect_path'] = '$scheme'.substr($data['new']['redirect_path'], 8); + } else { + $data['new']['redirect_path'] = 'http'.substr($data['new']['redirect_path'], 8); + } + } + // Custom proxy directives + if($data['new']['redirect_type'] == 'proxy' && trim($data['new']['proxy_directives'] != '')){ + $final_proxy_directives = array(); + $proxy_directives = $data['new']['proxy_directives']; + // Make sure we only have Unix linebreaks + $proxy_directives = str_replace("\r\n", "\n", $proxy_directives); + $proxy_directives = str_replace("\r", "\n", $proxy_directives); + $proxy_directive_lines = explode("\n", $proxy_directives); + if(is_array($proxy_directive_lines) && !empty($proxy_directive_lines)){ + foreach($proxy_directive_lines as $proxy_directive_line){ + $final_proxy_directives[] = array('proxy_directive' => $proxy_directive_line); + } + } + } else { + $final_proxy_directives = false; + } + + switch($data['new']['subdomain']) { + case 'www': + $exclude_own_hostname = ''; + if(substr($data['new']['redirect_path'], 0, 1) == '/'){ // relative path + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= substr($data['new']['redirect_path'], 0, -1); + break; + } + $rewrite_exclude = '(?!/('.substr($data['new']['redirect_path'], 1, -1).(substr($data['new']['redirect_path'], 1, -1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + } else { // URL - check if URL is local + $tmp_redirect_path = $data['new']['redirect_path']; + if(substr($tmp_redirect_path, 0, 7) == '$scheme') $tmp_redirect_path = 'http'.substr($tmp_redirect_path, 7); + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + if(($tmp_redirect_path_parts['host'] == $data['new']['domain'] || $tmp_redirect_path_parts['host'] == 'www.'.$data['new']['domain']) && ($tmp_redirect_path_parts['port'] == '80' || $tmp_redirect_path_parts['port'] == '443' || !isset($tmp_redirect_path_parts['port']))){ + // URL is local + if(substr($tmp_redirect_path_parts['path'], -1) == '/') $tmp_redirect_path_parts['path'] = substr($tmp_redirect_path_parts['path'], 0, -1); + if(substr($tmp_redirect_path_parts['path'], 0, 1) != '/') $tmp_redirect_path_parts['path'] = '/'.$tmp_redirect_path_parts['path']; + //$rewrite_exclude = '((?!'.$tmp_redirect_path_parts['path'].'))'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= $tmp_redirect_path_parts['path']; + break; + } else { + $rewrite_exclude = '(?!/('.substr($tmp_redirect_path_parts['path'], 1).(substr($tmp_redirect_path_parts['path'], 1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + $exclude_own_hostname = $tmp_redirect_path_parts['host']; + } + } else { + // external URL + $rewrite_exclude = '(?!/(\.well-known/acme-challenge))/'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['use_proxy'] = 'y'; + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + } + unset($tmp_redirect_path); + unset($tmp_redirect_path_parts); + } + $own_rewrite_rules[] = array( 'rewrite_domain' => '^'.$this->_rewrite_quote($data['new']['domain']), + 'rewrite_type' => ($data['new']['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($data['new']['redirect_type']), + 'rewrite_target' => $data['new']['redirect_path'], + 'rewrite_exclude' => $rewrite_exclude, + 'rewrite_subdir' => $rewrite_subdir, + 'exclude_own_hostname' => $exclude_own_hostname, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($data['new']['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($data['new']['redirect_type'] == 'proxy' ? true:false)); + break; + case '*': + $exclude_own_hostname = ''; + if(substr($data['new']['redirect_path'], 0, 1) == '/'){ // relative path + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= substr($data['new']['redirect_path'], 0, -1); + break; + } + $rewrite_exclude = '(?!/('.substr($data['new']['redirect_path'], 1, -1).(substr($data['new']['redirect_path'], 1, -1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + } else { // URL - check if URL is local + $tmp_redirect_path = $data['new']['redirect_path']; + if(substr($tmp_redirect_path, 0, 7) == '$scheme') $tmp_redirect_path = 'http'.substr($tmp_redirect_path, 7); + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + + //if($is_serveralias && ($tmp_redirect_path_parts['port'] == '80' || $tmp_redirect_path_parts['port'] == '443' || !isset($tmp_redirect_path_parts['port']))){ + if($this->url_is_local($tmp_redirect_path_parts['host'], $data['new']['domain_id']) && ($tmp_redirect_path_parts['port'] == '80' || $tmp_redirect_path_parts['port'] == '443' || !isset($tmp_redirect_path_parts['port']))){ + // URL is local + if(substr($tmp_redirect_path_parts['path'], -1) == '/') $tmp_redirect_path_parts['path'] = substr($tmp_redirect_path_parts['path'], 0, -1); + if(substr($tmp_redirect_path_parts['path'], 0, 1) != '/') $tmp_redirect_path_parts['path'] = '/'.$tmp_redirect_path_parts['path']; + //$rewrite_exclude = '((?!'.$tmp_redirect_path_parts['path'].'))'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= $tmp_redirect_path_parts['path']; + break; + } else { + $rewrite_exclude = '(?!/('.substr($tmp_redirect_path_parts['path'], 1).(substr($tmp_redirect_path_parts['path'], 1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + $exclude_own_hostname = $tmp_redirect_path_parts['host']; + } + } else { + // external URL + $rewrite_exclude = '(?!/(\.well-known/acme-challenge))/'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['use_proxy'] = 'y'; + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + } + unset($tmp_redirect_path); + unset($tmp_redirect_path_parts); + } + $own_rewrite_rules[] = array( 'rewrite_domain' => '(^|\.)'.$this->_rewrite_quote($data['new']['domain']), + 'rewrite_type' => ($data['new']['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($data['new']['redirect_type']), + 'rewrite_target' => $data['new']['redirect_path'], + 'rewrite_exclude' => $rewrite_exclude, + 'rewrite_subdir' => $rewrite_subdir, + 'exclude_own_hostname' => $exclude_own_hostname, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($data['new']['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($data['new']['redirect_type'] == 'proxy' ? true:false)); + break; + default: + if(substr($data['new']['redirect_path'], 0, 1) == '/'){ // relative path + $exclude_own_hostname = ''; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= substr($data['new']['redirect_path'], 0, -1); + break; + } + $rewrite_exclude = '(?!/('.substr($data['new']['redirect_path'], 1, -1).(substr($data['new']['redirect_path'], 1, -1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + } else { // URL - check if URL is local + $tmp_redirect_path = $data['new']['redirect_path']; + if(substr($tmp_redirect_path, 0, 7) == '$scheme') $tmp_redirect_path = 'http'.substr($tmp_redirect_path, 7); + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + if($tmp_redirect_path_parts['host'] == $data['new']['domain'] && ($tmp_redirect_path_parts['port'] == '80' || $tmp_redirect_path_parts['port'] == '443' || !isset($tmp_redirect_path_parts['port']))){ + // URL is local + if(substr($tmp_redirect_path_parts['path'], -1) == '/') $tmp_redirect_path_parts['path'] = substr($tmp_redirect_path_parts['path'], 0, -1); + if(substr($tmp_redirect_path_parts['path'], 0, 1) != '/') $tmp_redirect_path_parts['path'] = '/'.$tmp_redirect_path_parts['path']; + //$rewrite_exclude = '((?!'.$tmp_redirect_path_parts['path'].'))'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['web_document_root_www_proxy'] = 'root '.$vhost_data['web_document_root_www'].';'; + $vhost_data['web_document_root_www'] .= $tmp_redirect_path_parts['path']; + break; + } else { + $rewrite_exclude = '(?!/('.substr($tmp_redirect_path_parts['path'], 1).(substr($tmp_redirect_path_parts['path'], 1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + $exclude_own_hostname = $tmp_redirect_path_parts['host']; + } + } else { + // external URL + $rewrite_exclude = '(?!/(\.well-known/acme-challenge))/'; + if($data['new']['redirect_type'] == 'proxy'){ + $vhost_data['use_proxy'] = 'y'; + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + } + unset($tmp_redirect_path); + unset($tmp_redirect_path_parts); + } + $own_rewrite_rules[] = array( 'rewrite_domain' => '^'.$this->_rewrite_quote($data['new']['domain']), + 'rewrite_type' => ($data['new']['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($data['new']['redirect_type']), + 'rewrite_target' => $data['new']['redirect_path'], + 'rewrite_exclude' => $rewrite_exclude, + 'rewrite_subdir' => $rewrite_subdir, + 'exclude_own_hostname' => $exclude_own_hostname, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($data['new']['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($data['new']['redirect_type'] == 'proxy' ? true:false)); + } + } + + // set logging variable + $vhost_data['logging'] = $web_config['logging']; + + // Provide TLS 1.3 support if Nginx version is >= 1.13.0 and when it was linked against OpenSSL(>=1.1.1) at build time and when it was linked against OpenSSL(>=1.1.1) at runtime. + $nginx_openssl_build_ver = $app->system->exec_safe('nginx -V 2>&1 | grep \'built with OpenSSL\' | sed \'s/.*built\([a-zA-Z ]*\)OpenSSL \([0-9.]*\).*/\2/\''); + $nginx_openssl_running_ver = $app->system->exec_safe('nginx -V 2>&1 | grep \'running with OpenSSL\' | sed \'s/.*running\([a-zA-Z ]*\)OpenSSL \([0-9.]*\).*/\2/\''); + if(version_compare($app->system->getnginxversion(true), '1.13.0', '>=') + && version_compare($nginx_openssl_build_ver, '1.1.1', '>=') + && (empty($nginx_openssl_running_ver) || version_compare($nginx_openssl_running_ver, '1.1.1', '>='))) { + $app->log('Enable TLS 1.3 for: '.$domain, LOGLEVEL_DEBUG); + $vhost_data['tls13_supported'] = "y"; + } $tpl->setVar($vhost_data); + $server_alias = array(); + // get autoalias + $auto_alias = $web_config['website_autoalias']; + if($auto_alias != '') { + // get the client username + $client = $app->db->queryOneRecord("SELECT `username` FROM `client` WHERE `client_id` = ?", $client_id); + $aa_search = array('[client_id]', '[website_id]', '[client_username]', '[website_domain]'); + $aa_replace = array($client_id, $data['new']['domain_id'], $client['username'], $data['new']['domain']); + $auto_alias = str_replace($aa_search, $aa_replace, $auto_alias); + unset($client); + unset($aa_search); + unset($aa_replace); + $server_alias[] .= $auto_alias.' '; + } // get alias domains (co-domains and subdomains) - $aliases = $app->dbmaster->queryAllRecords("SELECT * FROM web_domain WHERE parent_domain_id = ? AND (type != 'vhostsubdomain' OR type != 'vhostalias') AND active = 'y'", $data['new']['domain_id']); - $server_alias = array(); + $aliases = $app->db->queryAllRecords("SELECT * FROM web_domain WHERE parent_domain_id = ? AND active = 'y' AND (type != 'vhostsubdomain' AND type != 'vhostalias')", $data['new']['domain_id']); + $alias_seo_redirects = array(); switch($data['new']['subdomain']) { case 'www': - $server_alias[] .= 'www.'.$data['new']['domain'].' '; + $server_alias[] = 'www.'.$data['new']['domain'].' '; break; case '*': - $server_alias[] .= '*.'.$data['new']['domain'].' '; + $server_alias[] = '*.'.$data['new']['domain'].' '; break; } if(is_array($aliases)) { foreach($aliases as $alias) { - switch($alias['subdomain']) { - case 'www': - $server_alias[] .= 'www.'.$alias['domain'].' '.$alias['domain'].' '; - break; - case '*': - $server_alias[] .= '*.'.$alias['domain'].' '.$alias['domain'].' '; - break; - default: - $server_alias[] .= $alias['domain'].' '; - break; + + // Custom proxy directives + if($alias['redirect_type'] == 'proxy' && trim($alias['proxy_directives'] != '')){ + $final_proxy_directives = array(); + $proxy_directives = $alias['proxy_directives']; + // Make sure we only have Unix linebreaks + $proxy_directives = str_replace("\r\n", "\n", $proxy_directives); + $proxy_directives = str_replace("\r", "\n", $proxy_directives); + $proxy_directive_lines = explode("\n", $proxy_directives); + if(is_array($proxy_directive_lines) && !empty($proxy_directive_lines)){ + foreach($proxy_directive_lines as $proxy_directive_line){ + $final_proxy_directives[] = array('proxy_directive' => $proxy_directive_line); + } + } + } else { + $final_proxy_directives = false; + } + + if($alias['redirect_type'] == '' || $alias['redirect_path'] == '' || substr($alias['redirect_path'], 0, 1) == '/') { + switch($alias['subdomain']) { + case 'www': + $server_alias[] = 'www.'.$alias['domain'].' '.$alias['domain'].' '; + break; + case '*': + $server_alias[] = '*.'.$alias['domain'].' '.$alias['domain'].' '; + break; + default: + $server_alias[] = $alias['domain'].' '; + break; + } + $app->log('Add server alias: '.$alias['domain'], LOGLEVEL_DEBUG); + + // Add SEO redirects for alias domains + if($alias['seo_redirect'] != '' && $data['new']['seo_redirect'] != '*_to_www_domain_tld' && $data['new']['seo_redirect'] != '*_to_domain_tld' && ($alias['type'] == 'alias' || ($alias['type'] == 'subdomain' && $data['new']['seo_redirect'] != '*_domain_tld_to_www_domain_tld' && $data['new']['seo_redirect'] != '*_domain_tld_to_domain_tld'))){ + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_'); + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + $alias_seo_redirects[] = $tmp_seo_redirects; + } + } } - $app->log('Add server alias: '.$alias['domain'], LOGLEVEL_DEBUG); + // Local Rewriting (inside vhost server {} container) + if($alias['redirect_type'] != '' && substr($alias['redirect_path'], 0, 1) == '/' && $alias['redirect_type'] != 'proxy') { // proxy makes no sense with local path + if(substr($alias['redirect_path'], -1) != '/') $alias['redirect_path'] .= '/'; + $rewrite_exclude = '(?!/('.substr($alias['redirect_path'], 1, -1).(substr($alias['redirect_path'], 1, -1) != ''? '|': '').'stats'.($vhost_data['errordocs'] == 1 ? '|error' : '').'|\.well-known/acme-challenge))/'; + switch($alias['subdomain']) { + case 'www': + // example.com + $local_rewrite_rules[] = array( 'local_redirect_origin_domain' => $alias['domain'], + 'local_redirect_operator' => '=', + 'local_redirect_exclude' => $rewrite_exclude, + 'local_redirect_target' => $alias['redirect_path'], + 'local_redirect_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type'])); + + // www.example.com + $local_rewrite_rules[] = array( 'local_redirect_origin_domain' => 'www.'.$alias['domain'], + 'local_redirect_operator' => '=', + 'local_redirect_exclude' => $rewrite_exclude, + 'local_redirect_target' => $alias['redirect_path'], + 'local_redirect_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type'])); + break; + case '*': + $local_rewrite_rules[] = array( 'local_redirect_origin_domain' => '^('.str_replace('.', '\.', $alias['domain']).'|.+\.'.str_replace('.', '\.', $alias['domain']).')$', + 'local_redirect_operator' => '~*', + 'local_redirect_exclude' => $rewrite_exclude, + 'local_redirect_target' => $alias['redirect_path'], + 'local_redirect_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type'])); + break; + default: + $local_rewrite_rules[] = array( 'local_redirect_origin_domain' => $alias['domain'], + 'local_redirect_operator' => '=', + 'local_redirect_exclude' => $rewrite_exclude, + 'local_redirect_target' => $alias['redirect_path'], + 'local_redirect_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type'])); + } + } + + // External Rewriting (extra server {} containers) + if($alias['redirect_type'] != '' && $alias['redirect_path'] != '' && substr($alias['redirect_path'], 0, 1) != '/') { + if(substr($alias['redirect_path'], -1) != '/') $alias['redirect_path'] .= '/'; + if(substr($alias['redirect_path'], 0, 8) == '[scheme]'){ + if($alias['redirect_type'] != 'proxy'){ + $alias['redirect_path'] = '$scheme'.substr($alias['redirect_path'], 8); + } else { + $alias['redirect_path'] = 'http'.substr($alias['redirect_path'], 8); + } + } + + switch($alias['subdomain']) { + case 'www': + if($alias['redirect_type'] == 'proxy'){ + $tmp_redirect_path = $alias['redirect_path']; + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + + if($alias['redirect_type'] != 'proxy'){ + if(substr($alias['redirect_path'], -1) == '/') $alias['redirect_path'] = substr($alias['redirect_path'], 0, -1); + } + // Add SEO redirects for alias domains + $alias_seo_redirects2 = array(); + if($alias['seo_redirect'] != ''){ + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_', 'none'); + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + $alias_seo_redirects2[] = $tmp_seo_redirects; + } + } + $rewrite_rules[] = array( 'rewrite_domain' => $alias['domain'], + 'rewrite_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type']), + 'rewrite_target' => $alias['redirect_path'], + 'rewrite_subdir' => $rewrite_subdir, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($alias['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($alias['redirect_type'] == 'proxy' ? true:false), + 'alias_seo_redirects2' => (count($alias_seo_redirects2) > 0 ? $alias_seo_redirects2 : false)); + + // Add SEO redirects for alias domains + $alias_seo_redirects2 = array(); + if($alias['seo_redirect'] != ''){ + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_', 'www'); + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + $alias_seo_redirects2[] = $tmp_seo_redirects; + } + } + $rewrite_rules[] = array( 'rewrite_domain' => 'www.'.$alias['domain'], + 'rewrite_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type']), + 'rewrite_target' => $alias['redirect_path'], + 'rewrite_subdir' => $rewrite_subdir, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($alias['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($alias['redirect_type'] == 'proxy' ? true:false), + 'alias_seo_redirects2' => (count($alias_seo_redirects2) > 0 ? $alias_seo_redirects2 : false)); + break; + case '*': + if($alias['redirect_type'] == 'proxy'){ + $tmp_redirect_path = $alias['redirect_path']; + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + + if($alias['redirect_type'] != 'proxy'){ + if(substr($alias['redirect_path'], -1) == '/') $alias['redirect_path'] = substr($alias['redirect_path'], 0, -1); + } + // Add SEO redirects for alias domains + $alias_seo_redirects2 = array(); + if($alias['seo_redirect'] != ''){ + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_'); + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + $alias_seo_redirects2[] = $tmp_seo_redirects; + } + } + $rewrite_rules[] = array( 'rewrite_domain' => $alias['domain'].' *.'.$alias['domain'], + 'rewrite_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type']), + 'rewrite_target' => $alias['redirect_path'], + 'rewrite_subdir' => $rewrite_subdir, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($alias['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($alias['redirect_type'] == 'proxy' ? true:false), + 'alias_seo_redirects2' => (count($alias_seo_redirects2) > 0 ? $alias_seo_redirects2 : false)); + break; + default: + if($alias['redirect_type'] == 'proxy'){ + $tmp_redirect_path = $alias['redirect_path']; + $tmp_redirect_path_parts = parse_url($tmp_redirect_path); + $rewrite_subdir = $tmp_redirect_path_parts['path']; + if(substr($rewrite_subdir, 0, 1) == '/') $rewrite_subdir = substr($rewrite_subdir, 1); + if(substr($rewrite_subdir, -1) != '/') $rewrite_subdir .= '/'; + if($rewrite_subdir == '/') $rewrite_subdir = ''; + } + + if($alias['redirect_type'] != 'proxy'){ + if(substr($alias['redirect_path'], -1) == '/') $alias['redirect_path'] = substr($alias['redirect_path'], 0, -1); + } + if(substr($alias['domain'], 0, 2) === '*.') $domain_rule = '*.'.substr($alias['domain'], 2); + else $domain_rule = $alias['domain']; + // Add SEO redirects for alias domains + $alias_seo_redirects2 = array(); + if($alias['seo_redirect'] != ''){ + if(substr($alias['domain'], 0, 2) === '*.'){ + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_'); + } else { + $tmp_seo_redirects = $this->get_seo_redirects($alias, 'alias_', 'none'); + } + if(is_array($tmp_seo_redirects) && !empty($tmp_seo_redirects)){ + $alias_seo_redirects2[] = $tmp_seo_redirects; + } + } + $rewrite_rules[] = array( 'rewrite_domain' => $domain_rule, + 'rewrite_type' => ($alias['redirect_type'] == 'no')?'':$this->rewrite_flag_apache_to_nginx($alias['redirect_type']), + 'rewrite_target' => $alias['redirect_path'], + 'rewrite_subdir' => $rewrite_subdir, + 'proxy_directives' => $final_proxy_directives, + 'use_rewrite' => ($alias['redirect_type'] == 'proxy' ? false:true), + 'use_proxy' => ($alias['redirect_type'] == 'proxy' ? true:false), + 'alias_seo_redirects2' => (count($alias_seo_redirects2) > 0 ? $alias_seo_redirects2 : false)); + } + } } } @@ -165,9 +911,7 @@ class nginx_reverseproxy_plugin { $server_alias_str = ''; $n = 0; - // begin a new ServerAlias line after 30 alias domains foreach($server_alias as $tmp_alias) { - if($n % 30 == 0) $server_alias_str .= " "; $server_alias_str .= $tmp_alias; } unset($tmp_alias); @@ -177,187 +921,531 @@ class nginx_reverseproxy_plugin { $tpl->setVar('alias', ''); } + if(count($rewrite_rules) > 0) { + $tpl->setLoop('redirects', $rewrite_rules); + } + if(count($own_rewrite_rules) > 0) { + $tpl->setLoop('own_redirects', $own_rewrite_rules); + } + if(count($local_rewrite_rules) > 0) { + $tpl->setLoop('local_redirects', $local_rewrite_rules); + } + if(count($alias_seo_redirects) > 0) { + $tpl->setLoop('alias_seo_redirects', $alias_seo_redirects); + } - $vhost_file = $nginx_config['nginx_vhost_conf_dir'].'/'.$data['new']['domain'].'.vhost'; + $stats_web_folder = 'web'; + if($data['new']['type'] == 'vhost'){ + if($data['new']['web_folder'] != ''){ + if(substr($data['new']['web_folder'], 0, 1) == '/') $data['new']['web_folder'] = substr($data['new']['web_folder'],1); + if(substr($data['new']['web_folder'], -1) == '/') $data['new']['web_folder'] = substr($data['new']['web_folder'],0,-1); + } + $stats_web_folder .= '/'.$data['new']['web_folder']; + } elseif($data['new']['type'] == 'vhostsubdomain' || $data['new']['type'] == 'vhostalias') { + $stats_web_folder = $data['new']['web_folder']; + } + + //* Create basic http auth for website statistics + $tpl->setVar('stats_auth_passwd_file', $data['new']['document_root']."/" . $stats_web_folder . "/stats/.htpasswd_stats"); + + // Create basic http auth for other directories + $basic_auth_locations = $this->_create_web_folder_auth_configuration($data['new']); + if(is_array($basic_auth_locations) && !empty($basic_auth_locations)) $tpl->setLoop('basic_auth_locations', $basic_auth_locations); + + $vhost_file = $web_config['nginx_vhost_conf_dir'].'/'.$data['new']['domain'].'.vhost'; //* Make a backup copy of vhost file - copy($vhost_file, $vhost_file.'~'); + if(file_exists($vhost_file)) copy($vhost_file, $vhost_file.'~'); //* Write vhost file - file_put_contents($vhost_file, $tpl->grab()); + $app->system->file_put_contents($vhost_file, $this->nginx_merge_locations($tpl->grab())); $app->log('Writing the vhost file: '.$vhost_file, LOGLEVEL_DEBUG); unset($tpl); + //* Set the symlink to enable the vhost + //* First we check if there is a old type of symlink and remove it + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/'.$data['new']['domain'].'.vhost'; + if(is_link($vhost_symlink)) $app->system->unlink($vhost_symlink); + + //* Remove old or changed symlinks + if($data['new']['subdomain'] != $data['old']['subdomain'] or $data['new']['active'] == 'n') { + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/900-'.$data['new']['domain'].'.vhost'; + if(is_link($vhost_symlink)) { + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/100-'.$data['new']['domain'].'.vhost'; + if(is_link($vhost_symlink)) { + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + } - // Set the symlink to enable the vhost - $vhost_symlink = $nginx_config['nginx_vhost_conf_enabled_dir'].'/'.$data['new']['domain'].'.vhost'; + //* New symlink + if($data['new']['subdomain'] == '*') { + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/900-'.$data['new']['domain'].'.vhost'; + } else { + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/100-'.$data['new']['domain'].'.vhost'; + } if($data['new']['active'] == 'y' && !is_link($vhost_symlink)) { symlink($vhost_file, $vhost_symlink); $app->log('Creating symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); } - // Remove the symlink, if site is inactive - if($data['new']['active'] == 'n' && is_link($vhost_symlink)) { - unlink($vhost_symlink); - $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); - } - - if(!is_dir('/var/log/ispconfig/nginx/'.$data['new']['domain'])) $app->system->exec_safe('mkdir -p ?', '/var/log/ispconfig/nginx/'.$data['new']['domain']); - // remove old symlink and vhost file, if domain name of the site has changed if($this->action == 'update' && $data['old']['domain'] != '' && $data['new']['domain'] != $data['old']['domain']) { - $vhost_symlink = $nginx_config['nginx_vhost_conf_enabled_dir'].'/'.$data['old']['domain'].'.vhost'; - unlink($vhost_symlink); - $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); - $vhost_file = $nginx_config['nginx_vhost_conf_dir'].'/'.$data['old']['domain'].'.vhost'; - unlink($vhost_file); + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/900-'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)) { + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/100-'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)) { + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)) { + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_file = $web_config['nginx_vhost_conf_dir'].'/'.$data['old']['domain'].'.vhost'; + $app->system->unlink($vhost_file); $app->log('Removing file: '.$vhost_file, LOGLEVEL_DEBUG); - - if(is_dir('/var/log/ispconfig/nginx/'.$data['old']['domain'])) $app->system->exec_safe('rm -rf ?', '/var/log/ispconfig/nginx/'.$data['old']['domain']); } - // request a httpd reload when all records have been processed - $app->services->restartServiceDelayed('nginx', 'restart'); + if($web_config['check_apache_config'] == 'y') { + //* Test if nginx starts with the new configuration file + $nginx_online_status_before_restart = $this->_checkTcp('localhost', 80); + $app->log('nginx status is: '.($nginx_online_status_before_restart === true? 'running' : 'down'), LOGLEVEL_DEBUG); + + $retval = $app->services->restartService('nginx', 'restart'); // $retval['retval'] is 0 on success and > 0 on failure + $app->log('nginx restart return value is: '.$retval['retval'], LOGLEVEL_DEBUG); + + // wait a few seconds, before we test the nginx status again + sleep(2); + + //* Check if nginx restarted successfully if it was online before + $nginx_online_status_after_restart = $this->_checkTcp('localhost', 80); + $app->log('nginx online status after restart is: '.($nginx_online_status_after_restart === true? 'running' : 'down'), LOGLEVEL_DEBUG); + if($nginx_online_status_before_restart && !$nginx_online_status_after_restart || $retval['retval'] > 0) { + $app->log('nginx did not restart after the configuration change for website '.$data['new']['domain'].'. Reverting the configuration. Saved non-working config as '.$vhost_file.'.err', LOGLEVEL_WARN); + if(is_array($retval['output']) && !empty($retval['output'])){ + $app->log('Reason for nginx restart failure: '.implode("\n", $retval['output']), LOGLEVEL_WARN); + $app->dbmaster->datalogError(implode("\n", $retval['output'])); + } else { + // if no output is given, check again + exec('nginx -t 2>&1', $tmp_output, $tmp_retval); + if($tmp_retval > 0 && is_array($tmp_output) && !empty($tmp_output)){ + $app->log('Reason for nginx restart failure: '.implode("\n", $tmp_output), LOGLEVEL_WARN); + $app->dbmaster->datalogError(implode("\n", $tmp_output)); + } + unset($tmp_output, $tmp_retval); + } + $app->system->copy($vhost_file, $vhost_file.'.err'); + + if(is_file($vhost_file.'~')) { + //* Copy back the last backup file + $app->system->copy($vhost_file.'~', $vhost_file); + } else { + //* There is no backup file, so we create a empty vhost file with a warning message inside + $app->system->file_put_contents($vhost_file, "# nginx did not start after modifying this vhost file.\n# Please check file $vhost_file.err for syntax errors."); + } - // Remove the backup copy of the config file. - if(@is_file($vhost_file.'~')) unlink($vhost_file.'~'); + $app->services->restartService('nginx', 'restart'); + } + } else { + //* We do not check the nginx config after changes (is faster) + $app->services->restartServiceDelayed('nginx', 'reload'); + } + // Remove the backup copy of the config file. + if(@is_file($vhost_file.'~')) $app->system->unlink($vhost_file.'~'); //* Unset action to clean it for next processed vhost. $this->action = ''; - } + private function rewrite_flag_apache_to_nginx($flag) { + // Convert Apache RewriteRule Flags to Nginx RewriteRule Flags + switch($flag) { + case 'R': + $flag = 'redirect'; + break; + case 'L': + $flag = 'break'; + break; + case 'R=301,L': + $flag = 'permanent'; + break; + case 'R,L': + $flag = 'last'; + break; + } + return $flag; + } + private function _rewrite_quote($string) { + return str_replace(array('.', '*', '?', '+'), array('\\.', '\\*', '\\?', '\\+'), $string); + } + private function url_is_local($hostname, $domain_id){ + global $app; - // Handle the creation of SSL certificates - function ssl($event_name, $data) { - global $app, $conf; - - if(!is_dir($conf['nginx']['config_dir'].'/ssl')) $app->system->exec_safe('mkdir -p ?', $conf['nginx']['config_dir'].'/ssl'); - $ssl_dir = $conf['nginx']['config_dir'].'/ssl'; - $domain = $data['new']['ssl_domain']; - $key_file = $ssl_dir.'/'.$domain.'.key.org'; - $key_file2 = $ssl_dir.'/'.$domain.'.key'; - $csr_file = $ssl_dir.'/'.$domain.'.csr'; - $crt_file = $ssl_dir.'/'.$domain.'.crt'; + // ORDER BY clause makes sure wildcard subdomains (*) are listed last in the result array so that we can find direct matches first + $webs = $app->db->queryAllRecords("SELECT * FROM web_domain WHERE active = 'y' ORDER BY subdomain ASC"); + if(is_array($webs) && !empty($webs)){ + foreach($webs as $web){ + // web domain doesn't match hostname + if(substr($hostname, -strlen($web['domain'])) != $web['domain']) continue; + // own vhost and therefore server {} container of its own + //if($web['type'] == 'vhostsubdomain' || $web['type'] == 'vhostalias') continue; + // alias domains/subdomains using rewrites and therefore a server {} container of their own + //if(($web['type'] == 'alias' || $web['type'] == 'subdomain') && $web['redirect_type'] != '' && $web['redirect_path'] != '') continue; + + if($web['subdomain'] == '*'){ + $pattern = '/\.?'.str_replace('.', '\.', $web['domain']).'$/i'; + } + if($web['subdomain'] == 'none'){ + if($web['domain'] == $hostname){ + if($web['domain_id'] == $domain_id || $web['parent_domain_id'] == $domain_id){ + // own vhost and therefore server {} container of its own + if($web['type'] == 'vhostsubdomain' || $web['type'] == 'vhostalias') return false; + // alias domains/subdomains using rewrites and therefore a server {} container of their own + if(($web['type'] == 'alias' || $web['type'] == 'subdomain') && $web['redirect_type'] != '' && $web['redirect_path'] != '') return false; + return true; + } else { + return false; + } + } + $pattern = '/^'.str_replace('.', '\.', $web['domain']).'$/i'; + } + if($web['subdomain'] == 'www'){ + if($web['domain'] == $hostname || $web['subdomain'].'.'.$web['domain'] == $hostname){ + if($web['domain_id'] == $domain_id || $web['parent_domain_id'] == $domain_id){ + // own vhost and therefore server {} container of its own + if($web['type'] == 'vhostsubdomain' || $web['type'] == 'vhostalias') return false; + // alias domains/subdomains using rewrites and therefore a server {} container of their own + if(($web['type'] == 'alias' || $web['type'] == 'subdomain') && $web['redirect_type'] != '' && $web['redirect_path'] != '') return false; + return true; + } else { + return false; + } + } + $pattern = '/^(www\.)?'.str_replace('.', '\.', $web['domain']).'$/i'; + } + if(preg_match($pattern, $hostname)){ + if($web['domain_id'] == $domain_id || $web['parent_domain_id'] == $domain_id){ + // own vhost and therefore server {} container of its own + if($web['type'] == 'vhostsubdomain' || $web['type'] == 'vhostalias') return false; + // alias domains/subdomains using rewrites and therefore a server {} container of their own + if(($web['type'] == 'alias' || $web['type'] == 'subdomain') && $web['redirect_type'] != '' && $web['redirect_path'] != '') return false; + return true; + } else { + return false; + } + } + } + } + return false; + } - //* Save a SSL certificate to disk - if($data["new"]["ssl_action"] == 'save') { - $web = $app->masterdb->queryOneRecord("select wd.document_root, sp.ip_address from web_domain wd INNER JOIN server_ip sp USING(server_id) WHERE domain = ?", $data['new']['domain']); + private function get_seo_redirects($web, $prefix = '', $force_subdomain = false){ + // $force_subdomain = 'none|www' + $seo_redirects = array(); - $src_ssl_dir = $web["document_root"]."/ssl"; - //$domain = $data["new"]["ssl_domain"]; - //$csr_file = $ssl_dir.'/'.$domain.".csr"; - //$crt_file = $ssl_dir.'/'.$domain.".crt"; - //$bundle_file = $ssl_dir.'/'.$domain.".bundle"; - $app->system->exec_safe('rsync -v -e ssh root@?:? ?', $web['ip_address'], '~/'.$src_ssl_dir, $ssl_dir); + if(substr($web['domain'], 0, 2) === '*.') $web['subdomain'] = '*'; - $app->log('Syncing SSL Cert for: '.$domain, LOGLEVEL_DEBUG); + if(($web['subdomain'] == 'www' || $web['subdomain'] == '*') && $force_subdomain != 'www'){ + if($web['seo_redirect'] == 'non_www_to_www'){ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = $web['domain']; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = 'www.'.$web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '='; + } + if($web['seo_redirect'] == '*_domain_tld_to_www_domain_tld'){ + // ^(example\.com|(?!\bwww\b)\.example\.com)$ + // ^(example\.com|((?:\w+(?:-\w+)*\.)*)((?!www\.)\w+(?:-\w+)*)(\.example\.com))$ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = '^('.str_replace('.', '\.', $web['domain']).'|((?:\w+(?:-\w+)*\.)*)((?!www\.)\w+(?:-\w+)*)(\.'.str_replace('.', '\.', $web['domain']).'))$'; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = 'www.'.$web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '~*'; + } + if($web['seo_redirect'] == '*_to_www_domain_tld'){ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = 'www.'.$web['domain']; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = 'www.'.$web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '!='; + } } - - //* Delete a SSL certificate - if($data['new']['ssl_action'] == 'del') { - //$ssl_dir = $data['new']['document_root'].'/ssl'; - $domain = $data['new']['ssl_domain']; - $csr_file = $ssl_dir.'/'.$domain.'.csr'; - $crt_file = $ssl_dir.'/'.$domain.'.crt'; - $bundle_file = $ssl_dir.'/'.$domain.'.bundle'; - unlink($csr_file); - unlink($crt_file); - unlink($bundle_file); - $app->log('Deleting SSL Cert for: '.$domain, LOGLEVEL_DEBUG); + if($force_subdomain != 'none'){ + if($web['seo_redirect'] == 'www_to_non_www'){ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = 'www.'.$web['domain']; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = $web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '='; + } + if($web['seo_redirect'] == '*_domain_tld_to_domain_tld'){ + // ^(.+)\.example\.com$ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = '^(.+)\.'.str_replace('.', '\.', $web['domain']).'$'; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = $web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '~*'; + } + if($web['seo_redirect'] == '*_to_domain_tld'){ + $seo_redirects[$prefix.'seo_redirect_origin_domain'] = $web['domain']; + $seo_redirects[$prefix.'seo_redirect_target_domain'] = $web['domain']; + $seo_redirects[$prefix.'seo_redirect_operator'] = '!='; + } } - - + return $seo_redirects; } - - function delete($event_name, $data) { + function _create_web_folder_auth_configuration($website){ global $app, $conf; - - // load the server configuration options + //* Create the domain.auth file which is included in the vhost configuration file $app->uses('getconf'); - $nginx_config = $app->getconf->get_server_config($conf['server_id'], 'web'); - + $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); + $basic_auth_file = $web_config['nginx_vhost_conf_dir'].'/'.$website['domain'].'.auth'; + //$app->load('tpl'); + //$tpl = new tpl(); + //$tpl->newTemplate('nginx_http_authentication.auth.master'); + $website_auth_locations = $app->db->queryAllRecords("SELECT * FROM web_folder WHERE active = 'y' AND parent_domain_id = ?", $website['domain_id']); + $basic_auth_locations = array(); + if(is_array($website_auth_locations) && !empty($website_auth_locations)){ + foreach($website_auth_locations as $website_auth_location){ + if(substr($website_auth_location['path'], 0, 1) == '/') $website_auth_location['path'] = substr($website_auth_location['path'], 1); + if(substr($website_auth_location['path'], -1) == '/') $website_auth_location['path'] = substr($website_auth_location['path'], 0, -1); + if($website_auth_location['path'] != ''){ + $website_auth_location['path'] .= '/'; + } + $basic_auth_locations[] = array('htpasswd_location' => '/'.$website_auth_location['path'], + 'htpasswd_path' => $website['document_root'].'/' . (($website['type'] == 'vhostsubdomain' || $website['type'] == 'vhostalias') ? $website['web_folder'] : 'web') . '/'.$website_auth_location['path']); + } + } + return $basic_auth_locations; + //$tpl->setLoop('basic_auth_locations', $basic_auth_locations); + //file_put_contents($basic_auth_file,$tpl->grab()); + //$app->log('Writing the http basic authentication file: '.$basic_auth_file,LOGLEVEL_DEBUG); + //unset($tpl); + //$app->services->restartServiceDelayed('httpd','reload'); + } - if($data['old']['type'] == 'vhost' || $data['old']['type'] == 'vhostsubdomain' || $data['old']['type'] == 'vhostalias') { + private function nginx_replace($matches){ + $location = 'location'.($matches[1] != '' ? ' '.$matches[1] : '').' '.$matches[2].' '.$matches[3]; + if($matches[4] == '##merge##' || $matches[7] == '##merge##') $location .= ' ##merge##'; + if($matches[4] == '##delete##' || $matches[7] == '##delete##') $location .= ' ##delete##'; + $location .= "\n"; + $location .= $matches[5]."\n"; + $location .= $matches[6]; + return $location; + } - //* This is a website - // Deleting the vhost file, symlink and the data directory - $vhost_symlink = $nginx_config['nginx_vhost_conf_enabled_dir'].'/'.$data['old']['domain'].'.vhost'; - unlink($vhost_symlink); - $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + private function nginx_merge_locations($vhost_conf) { + global $app, $conf; - $vhost_file = $nginx_config['nginx_vhost_conf_dir'].'/'.$data['old']['domain'].'.vhost'; - unlink($vhost_file); - $app->log('Removing vhost file: '.$vhost_file, LOGLEVEL_DEBUG); + if(preg_match('/##subroot (.+?)\s*##/', $vhost_conf, $subroot)) { + if(!preg_match('/^(?:[a-z0-9\/_-]|\.(?!\.))+$/iD', $subroot[1])) { + $app->log('Token ##subroot is unsecure (server ID: '.$conf['server_id'].').', LOGLEVEL_WARN); + } else { + $insert_pos = strpos($vhost_conf, ';', strpos($vhost_conf, 'root ')); + $vhost_conf = substr_replace($vhost_conf, ltrim($subroot[1], '/'), $insert_pos, 0); + } + } + + $lines = explode("\n", $vhost_conf); + + // if whole location block is in one line, split it up into multiple lines + if(is_array($lines) && !empty($lines)){ + $linecount = sizeof($lines); + for($h=0;$h<$linecount;$h++){ + // remove comments + if(substr(trim($lines[$h]), 0, 1) == '#'){ + unset($lines[$h]); + continue; + } + $lines[$h] = rtrim($lines[$h]); + /* + if(substr(ltrim($lines[$h]), 0, 8) == 'location' && strpos($lines[$h], '{') !== false && strpos($lines[$h], ';') !== false){ + $lines[$h] = str_replace("{", "{\n", $lines[$h]); + $lines[$h] = str_replace(";", ";\n", $lines[$h]); + if(strpos($lines[$h], '##merge##') !== false){ + $lines[$h] = str_replace('##merge##', '', $lines[$h]); + $lines[$h] = substr($lines[$h],0,strpos($lines[$h], '{')).' ##merge##'.substr($lines[$h],strpos($lines[$h], '{')+1); + } + } + if(substr(ltrim($lines[$h]), 0, 8) == 'location' && strpos($lines[$h], '{') !== false && strpos($lines[$h], '}') !== false && strpos($lines[$h], ';') === false){ + $lines[$h] = str_replace("{", "{\n", $lines[$h]); + if(strpos($lines[$h], '##merge##') !== false){ + $lines[$h] = str_replace('##merge##', '', $lines[$h]); + $lines[$h] = substr($lines[$h],0,strpos($lines[$h], '{')).' ##merge##'.substr($lines[$h],strpos($lines[$h], '{')+1); + } + } + */ + $pattern = '/^[^\S\n]*location[^\S\n]+(?:(.+)[^\S\n]+)?(.+)[^\S\n]*(\{)[^\S\n]*(##merge##|##delete##)?[^\S\n]*(.+)[^\S\n]*(\})[^\S\n]*(##merge##|##delete##)?[^\S\n]*$/'; + $lines[$h] = preg_replace_callback($pattern, array($this, 'nginx_replace') , $lines[$h]); + } + } + $vhost_conf = implode("\n", $lines); + unset($lines); + unset($linecount); + + $lines = explode("\n", $vhost_conf); + + if(is_array($lines) && !empty($lines)){ + $locations = array(); + $locations_to_delete = array(); + $islocation = false; + $linecount = sizeof($lines); + $server_count = 0; + + for($i=0;$i<$linecount;$i++){ + $l = trim($lines[$i]); + if(substr($l, 0, 8) == 'server {') $server_count += 1; + if($server_count > 1) break; + if(substr($l, 0, 8) == 'location' && !$islocation){ + + $islocation = true; + $level = 0; + + // Remove unnecessary whitespace + $l = preg_replace('/\s\s+/', ' ', $l); + + $loc_parts = explode(' ', $l); + // see http://wiki.nginx.org/HttpCoreModule#location + if($loc_parts[1] == '=' || $loc_parts[1] == '~' || $loc_parts[1] == '~*' || $loc_parts[1] == '^~'){ + $location = $loc_parts[1].' '.$loc_parts[2]; + } else { + $location = $loc_parts[1]; + } + unset($loc_parts); + + if(!isset($locations[$location]['action'])) $locations[$location]['action'] = 'replace'; + if(substr($l, -9) == '##merge##') $locations[$location]['action'] = 'merge'; + if(substr($l, -10) == '##delete##') $locations[$location]['action'] = 'delete'; + + if(!isset($locations[$location]['open_tag'])) $locations[$location]['open_tag'] = ' location '.$location.' {'; + if(!isset($locations[$location]['location']) || $locations[$location]['action'] == 'replace') $locations[$location]['location'] = ''; + if($locations[$location]['action'] == 'delete') $locations_to_delete[] = $location; + if(!isset($locations[$location]['end_tag'])) $locations[$location]['end_tag'] = ' }'; + if(!isset($locations[$location]['start_line'])) $locations[$location]['start_line'] = $i; + + unset($lines[$i]); + + } else { + + if($islocation){ + $openingbracketpos = strrpos($l, '{'); + if($openingbracketpos !== false){ + $level += 1; + } + $closingbracketpos = strrpos($l, '}'); + if($closingbracketpos !== false && $level > 0 && $closingbracketpos >= intval($openingbracketpos)){ + $level -= 1; + $locations[$location]['location'] .= $lines[$i]."\n"; + } elseif($closingbracketpos !== false && $level == 0 && $closingbracketpos >= intval($openingbracketpos)){ + $islocation = false; + } else { + $locations[$location]['location'] .= $lines[$i]."\n"; + } + unset($lines[$i]); + } + } + } - // Delete the log file directory - $vhost_logfile_dir = '/var/log/ispconfig/nginx/'.$data['old']['domain']; - if($data['old']['domain'] != '' && !stristr($vhost_logfile_dir, '..')) $app->system->exec_safe('rm -rf ?', $vhost_logfile_dir); - $app->log('Removing website logfile directory: '.$vhost_logfile_dir, LOGLEVEL_DEBUG); + if(is_array($locations) && !empty($locations)){ + if(is_array($locations_to_delete) && !empty($locations_to_delete)){ + foreach($locations_to_delete as $location_to_delete){ + if(isset($locations[$location_to_delete])) unset($locations[$location_to_delete]); + } + } + foreach($locations as $key => $val){ + $new_location = $val['open_tag']."\n".$val['location'].$val['end_tag']; + $lines[$val['start_line']] = $new_location; + } + } + ksort($lines); + $vhost_conf = implode("\n", $lines); } + + return trim($vhost_conf); } - function rewrite_insert($event_name, $data) { - global $app, $conf; + private function _checkTcp ($host, $port) { - // just run the update function - $this->update($event_name, $data); + $fp = @fsockopen($host, $port, $errno, $errstr, 2); + + if ($fp) { + fclose($fp); + return true; + } else { + return false; + } } - function rewrite_update($event_name, $data) { + /** + * ISPConfig delete hook. + * + * Called every time, a site get's removed. + * + * @uses update() + * + * @param string $event_name the event/action name + * @param array $data the vhost data + */ + function delete($event_name, $data) { global $app, $conf; - $rules = $this->_getRewriteRules($app); - + // load the server configuration options $app->uses('getconf'); - $nginx_config = $app->getconf->get_server_config($conf['server_id'], 'web'); - - $app->load('tpl'); - $tpl = new tpl(); - $tpl->newTemplate("nginx_reverseproxy_rewrites.conf.master"); - if (!empty($rules))$tpl->setLoop('nginx_rewrite_rules', $rules); + $app->uses('system'); + $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); - $rewrites_file = $nginx_config['nginx_vhost_conf_dir'].'/default.rewrites.conf'; - //* Make a backup copy of vhost file - copy($rewrites_file, $rewrites_file.'~'); + if($data['old']['type'] == 'vhost' || $data['old']['type'] == 'vhostsubdomain' || $data['old']['type'] == 'vhostalias') $app->system->web_folder_protection($data['old']['document_root'], false); - //* Write vhost file - file_put_contents($rewrites_file, $tpl->grab()); - $app->log('Writing the nginx rewrites file: '.$rewrites_file, LOGLEVEL_DEBUG); - unset($tpl); + //* Check if nginx is using a chrooted setup + if($web_config['website_basedir'] != '' && @is_file($web_config['website_basedir'].'/etc/passwd')) { + $nginx_chrooted = true; + } else { + $nginx_chrooted = false; + } + if($data['old']['type'] != 'vhost' && $data['old']['type'] != 'vhostsubdomain' && $data['old']['type'] != 'vhostalias' && $data['old']['parent_domain_id'] > 0) { + //* This is a alias domain or subdomain, so we have to update the website instead + $parent_domain_id = intval($data['old']['parent_domain_id']); + $tmp = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ? AND active = 'y'", $parent_domain_id); + $data['new'] = $tmp; + $data['old'] = $tmp; + $this->action = 'update'; + // just run the update function + $this->update($event_name, $data); - // Set the symlink to enable the vhost - $rewrite_symlink = $nginx_config['nginx_vhost_conf_enabled_dir'].'/default.rewrites.conf'; + } else { + //* This is a website + // Deleting the vhost file, symlink and the data directory + $vhost_file = $web_config['nginx_vhost_conf_dir'].'/'.$data['old']['domain'].'.vhost'; - if(!is_link($rewrite_symlink)) { - symlink($rewrites_file, $rewrite_symlink); - $app->log('Creating symlink for nginx rewrites: '.$rewrite_symlink.'->'.$rewrites_file, LOGLEVEL_DEBUG); - } - } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)){ + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/900-'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)){ + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } + $vhost_symlink = $web_config['nginx_vhost_conf_enabled_dir'].'/100-'.$data['old']['domain'].'.vhost'; + if(is_link($vhost_symlink)){ + $app->system->unlink($vhost_symlink); + $app->log('Removing symlink: '.$vhost_symlink.'->'.$vhost_file, LOGLEVEL_DEBUG); + } - function rewrite_delete($event_name, $data) { - global $app, $conf; + $app->system->unlink($vhost_file); + $app->log('Removing vhost file: '.$vhost_file, LOGLEVEL_DEBUG); - // just run the update function - $this->rewrite_update($event_name, $data); - } + $app->services->restartServiceDelayed('nginx', 'reload'); + } - function _getRewriteRules($app) - { - $rules = array(); - $rules = $app->db->queryAllRecords("SELECT rewrite_url_src, rewrite_url_dst FROM proxy_reverse ORDER BY rewrite_id ASC"); - return $rules; } -} // end class - -?> +} \ No newline at end of file -- GitLab From ae705f607e262b554836bd77dab7f707b0c5bba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bi=C4=8Di=C5=A1t=C4=9B?= Date: Sat, 25 Mar 2023 14:32:26 +0100 Subject: [PATCH 2/3] fix behind changes --- server/plugins-available/apache2_plugin.inc.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php index 9181b7cfb6..e3d1fe15ee 100644 --- a/server/plugins-available/apache2_plugin.inc.php +++ b/server/plugins-available/apache2_plugin.inc.php @@ -266,7 +266,7 @@ class apache2_plugin { // load the server configuration options $app->uses('getconf'); $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); - if ($web_config['CA_path']!='' && !file_exists($web_config['CA_path'].'/openssl.cnf')) + if (isset($web_config['CA_path']) && $web_config['CA_path'] !='' && !file_exists($web_config['CA_path'].'/openssl.cnf')) $app->log("CA path error, file does not exist:".$web_config['CA_path'].'/openssl.cnf', LOGLEVEL_ERROR); //* Only vhosts can have a ssl cert @@ -1169,7 +1169,7 @@ class apache2_plugin { if(!is_dir($web_config['website_basedir'].'/conf')) $app->system->mkdir($web_config['website_basedir'].'/conf'); //* add open_basedir restriction to custom php.ini content, required for suphp only - if(!stristr($data['new']['custom_php_ini'], 'open_basedir') && $data['new']['php'] == 'suphp') { + if(isset($data['new']['custom_php_ini']) && !stristr($data['new']['custom_php_ini'], 'open_basedir') && $data['new']['php'] == 'suphp') { $data['new']['custom_php_ini'] .= "\nopen_basedir = '".$data['new']['php_open_basedir']."'\n"; } @@ -1194,7 +1194,7 @@ class apache2_plugin { //* Create custom php.ini # Because of custom default PHP directives from snippet # php.ini custom values order os: 1. general settings 2. Directive Snippets settings 3. custom php.ini settings defined in domain settings - if(trim($data['new']['custom_php_ini']) != '' || $data['new']['directive_snippets_id'] > "0") { + if((isset($data['new']['custom_php_ini']) && trim($data['new']['custom_php_ini']) != '') || $data['new']['directive_snippets_id'] > "0") { $has_custom_php_ini = true; $custom_sendmail_path = false; if(!is_dir($custom_php_ini_dir)) $app->system->mkdirpath($custom_php_ini_dir); @@ -1400,14 +1400,12 @@ class apache2_plugin { $server_alias = array(); - // get autoalias - $auto_alias = $web_config['website_autoalias']; - if($auto_alias != '') { + if(isset($web_config['website_autoalias']) && $web_config['website_autoalias'] != '') { // get the client username $client = $app->db->queryOneRecord("SELECT `username` FROM `client` WHERE `client_id` = ?", $client_id); $aa_search = array('[client_id]', '[website_id]', '[client_username]', '[website_domain]'); $aa_replace = array($client_id, $data['new']['domain_id'], $client['username'], $data['new']['domain']); - $auto_alias = str_replace($aa_search, $aa_replace, $auto_alias); + $auto_alias = str_replace($aa_search, $aa_replace, $web_config['website_autoalias']); unset($client); unset($aa_search); unset($aa_replace); @@ -1520,7 +1518,7 @@ class apache2_plugin { if (count($rewrite_wildcard_rules) > 0) $rewrite_rules = array_merge($rewrite_rules, $rewrite_wildcard_rules); // Append wildcard rules to the end of rules - if((count($rewrite_rules) > 0 || $vhost_data['seo_redirect_enabled'] > 0 || count($alias_seo_redirects) > 0 || $data['new']['rewrite_to_https'] == 'y')) { + if(count($rewrite_rules) > 0 || $vhost_data['seo_redirect_enabled'] > 0 || count($alias_seo_redirects) > 0 || $data['new']['rewrite_to_https'] == 'y') { $tpl->setVar('rewrite_enabled', 1); } else { $tpl->setVar('rewrite_enabled', 0); @@ -1943,7 +1941,7 @@ class apache2_plugin { unset($ht_file); if(!is_file($data['new']['document_root'].'/web/stats/.htpasswd_stats') || $data['new']['stats_password'] != $data['old']['stats_password']) { - if(trim($data['new']['stats_password']) != '') { + if(isset($data['new']['stats_password']) && trim($data['new']['stats_password']) != '') { $htp_file = 'admin:'.trim($data['new']['stats_password']); $app->system->web_folder_protection($data['new']['document_root'], false); $app->system->file_put_contents($data['new']['document_root'].'/web/stats/.htpasswd_stats', $htp_file); -- GitLab From 8fdfbe1197851de4ccb3fd33b6d41966d2b95210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bi=C4=8Di=C5=A1t=C4=9B?= Date: Sat, 25 Mar 2023 14:35:30 +0100 Subject: [PATCH 3/3] fix behind changes #2 --- server/plugins-available/apache2_plugin.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php index e3d1fe15ee..53e3230c93 100644 --- a/server/plugins-available/apache2_plugin.inc.php +++ b/server/plugins-available/apache2_plugin.inc.php @@ -1792,7 +1792,7 @@ class apache2_plugin { //if proxy protocol is enabled we need to add a new port to lsiten to if($web_config['vhost_proxy_protocol_enabled'] == 'y' && $data['new']['proxy_protocol'] == 'y'){ - if((int)$web_config['vhost_proxy_protocol_http_port'] > 0) { + if(isset($web_config['vhost_proxy_protocol_http_port']) && (int)$web_config['vhost_proxy_protocol_http_port'] > 0) { $tmp_vhost_arr['port'] = (int)$web_config['vhost_proxy_protocol_http_port']; $tmp_vhost_arr['use_proxy_protocol'] = $data['new']['proxy_protocol']; $vhosts[] = $tmp_vhost_arr; -- GitLab