diff options
Diffstat (limited to 'config/haproxy-devel/haproxy.inc')
-rw-r--r-- | config/haproxy-devel/haproxy.inc | 1260 |
1 files changed, 1260 insertions, 0 deletions
diff --git a/config/haproxy-devel/haproxy.inc b/config/haproxy-devel/haproxy.inc new file mode 100644 index 00000000..f8434327 --- /dev/null +++ b/config/haproxy-devel/haproxy.inc @@ -0,0 +1,1260 @@ +<?php +/* + haproxy.inc + Copyright (C) 2009 Scott Ullrich <sullrich@pfsense.com> + Copyright (C) 2008 Remco Hoef + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* include all configuration functions */ +require_once("functions.inc"); +require_once("pkg-utils.inc"); +require_once("notices.inc"); + +global $haproxy_sni_ssloffloading; +$haproxy_sni_ssloffloading=true;// can only be used with recent 1.5-dev17 builds. + +$d_haproxyconfdirty_path = $g['varrun_path'] . "/haproxy.conf.dirty"; + +$a_acltypes = array(); +$a_acltypes[] = array('name' => 'host_starts_with', 'descr' => 'Host starts with', + 'mode' => 'http', 'syntax' => 'hdr_beg(host) -i %1$s'); +$a_acltypes[] = array('name' => 'host_ends_with', 'descr' => 'Host ends with', + 'mode' =>'http', 'syntax' => 'hdr_end(host) -i %1$s'); +$a_acltypes[] = array('name' => 'host_matches', 'descr' => 'Host matches', + 'mode' =>'http', 'syntax' => 'hdr(host) -i %1$s'); +$a_acltypes[] = array('name' => 'host_regex', 'descr' => 'Host regex', + 'mode' =>'http', 'syntax' => 'hdr_reg(host) -i %1$s'); +$a_acltypes[] = array('name' => 'host_contains', 'descr' => 'Host contains', + 'mode' => 'http', 'syntax' => 'hdr_dir(host) -i %1$s'); +$a_acltypes[] = array('name' => 'path_starts_with', 'descr' => 'Path starts with', + 'mode' => 'http', 'syntax' => 'path_beg -i %1$s'); +$a_acltypes[] = array('name' => 'path_ends_with', 'descr' => 'Path ends with', + 'mode' => 'http', 'syntax' => 'path_end -i %1$s'); +$a_acltypes[] = array('name' => 'path_matches', 'descr' => 'Path matches', + 'mode' => 'http', 'syntax' => 'path -i %1$s'); +$a_acltypes[] = array('name' => 'path_regex', 'descr' => 'Path regex', + 'mode' => 'http', 'syntax' => 'path_reg -i %1$s'); +$a_acltypes[] = array('name' => 'path_contains', 'descr' => 'Path contains', + 'mode' => 'http', 'syntax' => 'path_dir -i %1$s'); +$a_acltypes[] = array('name' => 'source_ip', 'descr' => 'Source IP', + 'mode' => '', 'syntax' => 'src %1$s'); +$a_acltypes[] = array('name' => 'backendservercount', 'descr' => 'Minimum count usable servers', + 'mode' => '', 'syntax' => 'nbsrv(%2$s) ge %1$d', 'parameters' => 'value,backendname'); +if ($haproxy_sni_ssloffloading) { + $a_acltypes[] = array('name' => 'ssl_sni_matches', 'descr' => 'Server Name Indication TLS extension matches', + 'mode' => 'https', 'syntax' => 'req_ssl_sni -i %1$s', 'advancedoptions' => "tcp-request inspect-delay 5s\n\ttcp-request content accept if { req_ssl_hello_type 1 }"); +} + +$a_checktypes['none'] = array('name' => 'none', 'syntax' => '', + 'descr' => 'No health checks will be performed.'); +$a_checktypes['Basic'] = array('name' => 'Basic', 'syntax' => '', + 'descr' => 'Basic socket connection check'); +$a_checktypes['HTTP'] = array('name' => 'HTTP', 'syntax' => 'httpchk', + 'descr' => 'HTTP protocol to check on the servers health, can also be used for HTTPS servers(requirs checking the SSL box for the servers).', 'parameters' => "uri,method,version"); +/* 'Agent' was added in HAProxy1.5dev18 */ +$a_checktypes['Agent'] = array('name' => 'Agent', 'syntax' => 'lb-agent-chk', 'usedifferenport' => 'yes', + 'descr' => 'Use a TCP connection to read an ASCII string of the form 100%,75%,drain,down (others in haproxy manual)'); +$a_checktypes['LDAP'] = array('name' => 'LDAP', 'syntax' => 'ldap-check', + 'descr' => 'Use LDAPv3 health checks for server testing'); +$a_checktypes['MySQL'] = array('name' => 'MySQL', 'syntax' => 'mysql-check', + 'descr' => 'Use MySQL health checks for server testing', 'parameters' => 'username'); +$a_checktypes['PostgreSQL'] = array('name' => 'PostgreSQL', 'syntax' => 'pgsql-check', + 'descr' => 'Use PostgreSQL health checks for server testing', 'parameters' => 'username'); +$a_checktypes['Redis'] = array('name' => 'Redis', 'syntax' => 'redis-check', + 'descr' => 'Test that the server correctly talks REDIS protocol.'); +$a_checktypes['SMTP'] = array('name' => 'SMTP', 'syntax' => 'smtpchk HELO', + 'descr' => 'Use SMTP HELO health checks for server testing', 'parameters' => 'domain'); +$a_checktypes['ESMTP'] = array('name' => 'ESMTP', 'syntax' => 'smtpchk EHLO', + 'descr' => 'Use ESMTP EHLO health checks for server testing', 'parameters' => 'domain'); +$a_checktypes['SSL'] = array('name' => 'SSL', 'syntax' => 'ssl-hello-chk', + 'descr' => 'Use SSLv3 client hello health checks for server testing.'); + +$a_httpcheck_method['OPTIONS'] = array('name' => 'OPTIONS', 'syntax' => 'OPTIONS'); +$a_httpcheck_method['HEAD'] = array('name' => 'HEAD', 'syntax' => 'HEAD'); +$a_httpcheck_method['GET'] = array('name' => 'GET', 'syntax' => 'GET'); +$a_httpcheck_method['POST'] = array('name' => 'POST', 'syntax' => 'POST'); +$a_httpcheck_method['PUT'] = array('name' => 'PUT', 'syntax' => 'PUT'); +$a_httpcheck_method['DELETE'] = array('name' => 'DELETE', 'syntax' => 'DELETE'); +$a_httpcheck_method['TRACE'] = array('name' => 'TRACE', 'syntax' => 'TRACE'); + +function haproxy_custom_php_deinstall_command() { + exec("cd /var/db/pkg && pkg_delete `ls | grep haproxy`"); + exec("rm /usr/local/pkg/haproxy.inc"); + exec("rm /usr/local/www/haproxy*"); + exec("rm /usr/local/etc/rc.d/haproxy.sh"); + exec("rm /etc/devd/haproxy.conf"); + exec("/etc/rc.d/devd restart"); + haproxy_install_cron(false); +} + +function haproxy_custom_php_install_command() { + global $g, $config; + conf_mount_rw(); + + $haproxy = <<<EOD +#!/bin/sh + +# PROVIDE: haproxy +# REQUIRE: LOGIN +# KEYWORD: FreeBSD + +. /etc/rc.subr + +name="haproxy" +rcvar=`set_rcvar` +command="/usr/local/bin/haproxy" +haproxy_enable=\${haproxy-"YES"} + +start_cmd="haproxy_start" +stop_postcmd="haproxy_stop" +check_cmd="haproxy_check" +extra_commands="check" + +load_rc_config \$name + +haproxy_start () { + echo "Starting haproxy." + /usr/bin/env \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + /usr/local/bin/php -q -d auto_prepend_file=config.inc <<ENDOFF + <?php + require_once("globals.inc"); + require_once("functions.inc"); + require_once("haproxy.inc"); + haproxy_configure(); + ?> +ENDOFF +} + +haproxy_check () { + echo "Checking haproxy." + /usr/bin/env \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + /usr/local/bin/php -q -d auto_prepend_file=config.inc <<ENDOFF + <?php + require_once("globals.inc"); + require_once("functions.inc"); + require_once("haproxy.inc"); + haproxy_check_run(0); + ?> +ENDOFF +} + +haproxy_stop () { + echo "Stopping haproxy." + killall haproxy +} + +run_rc_command "\$1" + +EOD; + + $fd = fopen("/usr/local/etc/rc.d/haproxy.sh", "w"); + fwrite($fd, $haproxy); + fclose($fd); + exec("chmod a+rx /usr/local/etc/rc.d/haproxy.sh"); + + $devd = <<<EOD +notify 0 { + match "system" "IFNET"; + match "subsystem" "carp[0-9]+"; + match "type" "LINK_UP"; + action "/usr/local/etc/rc.d/haproxy.sh check"; +}; +notify 0 { + match "system" "IFNET"; + match "subsystem" "carp[0-9]+"; + match "type" "LINK_DOWN"; + action "/usr/local/etc/rc.d/haproxy.sh check"; +}; + +EOD; + exec("mkdir -p /etc/devd"); + $fd = fopen("/etc/devd/haproxy.conf", "w"); + fwrite($fd, $devd); + fclose($fd); + exec("/etc/rc.d/devd restart"); + + /* Do XML upgrade from haproxy 0.31 to haproxy-dev */ + if (is_array($config['installedpackages']['haproxy']['ha_servers'])) { + /* We have an old config */ + $config['installedpackages']['haproxy']['ha_pools']['item'] = array(); + $a_global = &$config['installedpackages']['haproxy']; + $a_backends = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $a_oldservers = &$config['installedpackages']['haproxy']['ha_servers']['item']; + $a_pools = &$config['installedpackages']['haproxy']['ha_pools']['item']; + + foreach ($a_backends as $id => $be) { + $a_backends[$id]['status'] = 'active'; + } + $id = 0; + foreach ($a_oldservers as $oldserver) { + $pool=$oldserver; + /* make server sub array */ + $server=array(); + $server['name'] = $oldserver['name']; + $server['address'] = $oldserver['address']; + $server['port'] = $oldserver['port']; + $server['weight'] = $oldserver['weight']; + $a_servers=array(); + $a_servers[]=$server; + /* set new pool */ + $pool['name'] = "pool$id"; + $id++; + $pool['ha_servers']['item']=$a_servers; + /* link to frontend */ + foreach ($a_backends as $id => $be) { + if ($a_backends[$id]['name'] == $oldserver['backend']) { + $a_backends[$id]['backend_serverpool'] = $pool['name']; + $pool['monitor_uri'] = $be['monitor_uri']; + unset($a_backends[$id]['monitor_uri']); + break; + } + } + unset($pool['backend']); + unset($pool['address']); + unset($pool['port']); + unset($pool['weight']); + $a_pools[] = $pool; + } + unset($config['installedpackages']['haproxy']['ha_servers']); + write_config(); + } + + /* XML update to: pkg v1.3 and 'pool' changed to 'backend_serverpool' because 'pool' was added to listtags() in xmlparse.inc */ + if (is_array($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) + { + foreach($config['installedpackages']['haproxy']['ha_backends']['item'] as &$frontend) + { + $backend_serverpool = $frontend['pool'][0]; + $frontend['backend_serverpool'] = $backend_serverpool; + unset($frontend['pool']); + } + write_config(); + } + //also move setting for existing 2.0 installations as only the new variable is used + if (isset($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) + { + foreach($config['installedpackages']['haproxy']['ha_backends']['item'] as &$frontend) + { + $backend_serverpool = $frontend['pool']; + $frontend['backend_serverpool'] = $backend_serverpool; + unset($frontend['pool']); + } + write_config(); + } + + conf_mount_ro(); + + exec("/usr/local/etc/rc.d/haproxy.sh start"); +} + +function haproxy_install_cron($should_install) { + global $config, $g; + if($g['booting']==true) + return; + $is_installed = false; + if(!$config['cron']['item']) + return; + $x=0; + foreach($config['cron']['item'] as $item) { + if(strstr($item['command'], "/usr/local/etc/rc.d/haproxy.sh")) { + $is_installed = true; + break; + } + $x++; + } + switch($should_install) { + case true: + if(!$is_installed) { + $cron_item = array(); + $cron_item['minute'] = "*/2"; + $cron_item['hour'] = "*"; + $cron_item['mday'] = "*"; + $cron_item['month'] = "*"; + $cron_item['wday'] = "*"; + $cron_item['who'] = "root"; + $cron_item['command'] = "/usr/local/etc/rc.d/haproxy.sh check"; + $config['cron']['item'][] = $cron_item; + parse_config(true); + write_config(); + configure_cron(); + } + break; + case false: + if($is_installed == true) { + if($x > 0) { + unset($config['cron']['item'][$x]); + parse_config(true); + write_config(); + } + configure_cron(); + } + break; + } +} + +function haproxy_find_acl($name) { + global $a_acltypes; + + /* XXX why is this broken from xmlsync? */ + if (!$a_acltypes) { + $a_acltypes = array(); + $a_acltypes[] = array('name' => 'host_starts_with', 'descr' => 'Host starts with', + 'mode' => 'http', 'syntax' => 'hdr_beg(host) -i'); + $a_acltypes[] = array('name' => 'host_ends_with', 'descr' => 'Host ends with', + 'mode' =>'http', 'syntax' => 'hdr_end(host) -i'); + $a_acltypes[] = array('name' => 'host_matches', 'descr' => 'Host matches', + 'mode' =>'http', 'syntax' => 'hdr(host) -i'); + $a_acltypes[] = array('name' => 'host_regex', 'descr' => 'Host regex', + 'mode' =>'http', 'syntax' => 'hdr_reg(host) -i'); + $a_acltypes[] = array('name' => 'host_contains', 'descr' => 'Host contains', + 'mode' => 'http', 'syntax' => 'hdr_dir(host) -i'); + $a_acltypes[] = array('name' => 'path_starts_with', 'descr' => 'Path starts with', + 'mode' => 'http', 'syntax' => 'path_beg -i'); + $a_acltypes[] = array('name' => 'path_ends_with', 'descr' => 'Path ends with', + 'mode' => 'http', 'syntax' => 'path_end -i'); + $a_acltypes[] = array('name' => 'path_matches', 'descr' => 'Path matches', + 'mode' => 'http', 'syntax' => 'path -i'); + $a_acltypes[] = array('name' => 'path_regex', 'descr' => 'Path regex', + 'mode' => 'http', 'syntax' => 'path_reg -i'); + $a_acltypes[] = array('name' => 'path_contains', 'descr' => 'Path contains', + 'mode' => 'http', 'syntax' => 'path_dir -i'); + $a_acltypes[] = array('name' => 'source_ip', 'descr' => 'Source IP', + 'mode' => '', 'syntax' => 'src'); + } + + if($a_acltypes) { + foreach ($a_acltypes as $acl) { + if ($acl['name'] == $name) + return $acl; + } + } +} + +function write_backend($fd, $name, $pool, $frontend) { + if(!is_array($pool['ha_servers']['item']) && !$pool['stats_enabled']=='yes') + return; + global $a_checktypes; + + $a_servers = &$pool['ha_servers']['item']; + + unset($sslserverpresent); + if (is_array($a_servers)) + { + foreach($a_servers as $be) { + if (!$be['status'] == "inactive") + continue; + if ($be['ssl']) + $sslserverpresent = true; + } + } + + fwrite ($fd, "backend " . $name . "\n"); + if($pool['cookie_name'] && strtolower($frontend['type']) == "http") + fwrite ($fd, "\tcookie\t\t\t" . $pool['cookie_name'] . " insert indirect\n"); + + // https is an alias for tcp for clarity purpouses + if(strtolower($frontend['type']) == "https") { + $backend_type = "tcp"; + } else { + $backend_type = $frontend['type']; + } + + fwrite ($fd, "\tmode\t\t\t" . $backend_type . "\n"); + + unset($checkport); + $check_type = $pool['check_type']; + if ($check_type != 'none') + { + $optioncheck = $a_checktypes[$check_type]['syntax']; + if ($check_type == "MySQL" || $check_type == "PostgreSQL") + $optioncheck .= " user " . $pool['monitor_username']; + if ($check_type == "SMTP" || $check_type == "ESMTP") + $optioncheck .= " " . $pool['monitor_domain']; + if ($check_type == "HTTP") { + $uri = $pool['monitor_uri']; + if (!$uri) + $uri = "/"; + $optioncheck .= " {$pool['httpcheck_method']} {$uri} {$pool['monitor_httpversion']}"; + } + if ($check_type == "Agent") { + $checkport = " port " . $pool['monitor_agentport']; + } + } else { + $optioncheck = "httpchk"; + } + + if($pool['balance']) + fwrite ($fd, "\tbalance\t\t\t" . $pool['balance'] . "\n"); + + if(!$pool['connection_timeout']) + $pool['connection_timeout'] = 30000; + fwrite ($fd, "\ttimeout connect\t\t" . $pool['connection_timeout'] . "\n"); + + if(!$pool['server_timeout']) + $pool['server_timeout'] = 30000; + fwrite ($fd, "\ttimeout server\t\t" . $pool['server_timeout'] . "\n"); + + if(!$pool['retries']) + $pool['retries'] = 3; + fwrite ($fd, "\tretries\t\t\t" . $pool['retries'] . "\n"); + + if ($pool['transparent_clientip']) + fwrite ($fd, "\tsource 0.0.0.0 usesrc clientip\n"); + + if($pool['stats_enabled']=='yes') { + fwrite ($fd, "\tstats\t\t\tenable\n"); + if($pool['stats_uri']) + fwrite ($fd, "\tstats\t\t\turi ".$pool['stats_uri']."\n"); + if($pool['stats_realm']) + fwrite ($fd, "\tstats\t\t\trealm " . haproxy_escapestring($pool['stats_realm']) . "\n"); + else + fwrite ($fd, "\tstats\t\t\trealm .\n"); + fwrite ($fd, "\tstats\t\t\tauth " . haproxy_escapestring($pool['stats_username']).":". haproxy_escapestring($pool['stats_password'])."\n"); + + if($pool['stats_admin']=='yes') + fwrite ($fd, "\tstats\t\t\tadmin if TRUE" . "\n"); + + if($pool['stats_node_enabled']=='yes') + fwrite ($fd, "\tstats\t\t\tshow-node " . $pool['stats_node'] . "\n"); + if($pool['stats_desc']) + fwrite ($fd, "\tstats\t\t\tshow-desc " . $pool['stats_desc'] . "\n"); + if($pool['stats_refresh']) + fwrite ($fd, "\tstats\t\t\trefresh " . $pool['stats_refresh'] . "\n"); + } + + $uri = $pool['monitor_uri']; + if ($pool['monitor_uri']) + $uri = $pool['monitor_uri']; + else + $uri = "/"; + + if ($optioncheck) + fwrite ($fd, "\toption\t\t\t{$optioncheck}\n"); + + if ($pool['advanced_backend']) { + $adv_be = explode("\n", base64_decode($pool['advanced_backend'])); + foreach($adv_be as $adv_line) { + if ($adv_line != "") { + fwrite($fd, "\t" . str_replace("\r", "", $adv_line) . "\n"); + } + } + } + + if($pool['cookie'] && strtolower($frontend['type']) == "http") + $cookie = " cookie {$pool['cookie']} "; + else + $cookie = ""; + if($pool['advanced']) { + $advanced = base64_decode($pool['advanced']); + $advanced_txt = " " . $advanced; + } else { + $advanced_txt = ""; + } + + if ($check_type != 'none') + { + if($pool['checkinter']) + $checkinter = "check inter {$pool['checkinter']}"; + else + $checkinter = "check inter 1000"; + } + + if (is_array($a_servers)) + { + foreach($a_servers as $be) { + if ($be['status'] == "inactive") + continue; + + if (!$be['name']) + $be['name'] = $be['address']; + if(!$be['status'] || $be['status'] != 'active') { + $isbackup = $be['status']; + } else { + $isbackup = ""; + } + $ssl = ""; + if ($be['ssl'] == 'yes') + { + $ssl = $backend_type == "http" ? ' ssl' : ' check-ssl'; + } + fwrite ($fd, "\tserver\t\t\t" . $be['name'] . " " . $be['address'].":" . $be['port'] . "$ssl $cookie $checkinter$checkport $isbackup weight " . $be['weight'] . "{$advanced_txt} {$be['advanced']}\n"); + } + } + fwrite ($fd, "\n"); +} + +function haproxy_configure() { + global $g; + // reload haproxy + haproxy_writeconf("{$g['varetc_path']}/haproxy.cfg"); + return haproxy_check_run(1); +} + +function haproxy_check_and_run(&$messages, $reload) { + global $g; + $configname = "{$g['varetc_path']}/haproxy.cfg"; + haproxy_writeconf("$configname.new"); + $retval = exec("haproxy -c -V -f $configname.new 2>&1", $output, $err); + $messages = ""; + if ($err > 1) + $messages = "<h2><strong>FATAL ERROR CODE: $err while starting haproxy</strong></h2>"; + elseif ($err == 1) + $messages = "Errors found while starting haproxy"; + + if ((count($output) > 1) && $output[0] != "Configuration file is valid") + { + foreach($output as $line) + $messages .= "<br/>" . htmlspecialchars($line) . "\n"; + } + $ok = strstr($retval, "Configuration file is valid"); + if ($ok && $reload) { + global $haproxy_run_message; + exec("mv $configname.new $configname"); + $ok = haproxy_check_run(1) == 0; + $messages = $haproxy_run_message; + } + return $ok; +} + +function haproxy_writeconf($configfile) { + global $config; + + $a_global = &$config['installedpackages']['haproxy']; + $a_backends = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $a_pools = &$config['installedpackages']['haproxy']['ha_pools']['item']; + + $fd = fopen($configfile, "w"); + + if(is_array($a_global)) { + fwrite ($fd, "global\n"); + fwrite ($fd, "\tmaxconn\t\t\t".$a_global['maxconn']."\n"); + if($a_global['remotesyslog']) + fwrite ($fd, "\tlog\t\t\t{$a_global['remotesyslog']}\t{$a_global['logfacility']}\t{$a_global['loglevel']}\n"); + fwrite ($fd, "\tstats socket /tmp/haproxy.socket level admin\n"); + + if(!use_transparent_clientip_proxying()) + fwrite ($fd, "\tuid\t\t\t80\n"); + + fwrite ($fd, "\tgid\t\t\t80\n"); + // Set numprocs if defined or use system default (#cores) + if($a_global['nbproc']) + $numprocs = $a_global['nbproc']; + else + $numprocs ="1"; + fwrite ($fd, "\tnbproc\t\t\t$numprocs\n"); + fwrite ($fd, "\tchroot\t\t\t/var/empty\n"); + fwrite ($fd, "\tdaemon\n"); + + // Keep the advanced options on the bottom of the global settings, to allow additional sections to be easely added + if($a_global['advanced']) { + $adv = explode("\n", base64_decode($a_global['advanced'])); + foreach($adv as $adv_line) { + fwrite($fd, "\t" . str_replace("\r", "", $adv_line) . "\n"); + + } + } + fwrite ($fd, "\n"); + } + + // Try and get a unique array for address:port as frontends can duplicate + $a_bind = array(); + if(is_array($a_backends)) { + foreach ($a_backends as $backend) { + if($backend['status'] != 'active') + { + unlink_if_exists("var/etc/{$backend['name']}.{$backend['port']}.crt"); + continue; + } + if(!$backend['backend_serverpool']) + { + unlink_if_exists("var/etc/{$backend['name']}.{$backend['port']}.crt"); + continue; + } + + //check ssl info + if (strtolower($backend['type']) == "http" && $backend['ssloffload']){ + //ssl crt ./server.pem ca-file ./ca.crt verify optional crt-ignore-err all crl-file ./ca_crl.pem + $ssl_crt=" crt /var/etc/{$backend['name']}.{$backend['port']}.crt"; + $cert = lookup_cert($backend['ssloffloadcert']); + $certcontent = base64_decode($cert['crt'])."\r\n".base64_decode($cert['prv']); + file_put_contents("/var/etc/{$backend['name']}.{$backend['port']}.crt", $certcontent); + unset($certcontent); + }else{ + $ssl_crt=""; + unlink_if_exists("var/etc/{$backend['name']}.{$backend['port']}.crt"); + } + + $bname = get_frontend_ipport($backend); + + if ($backend['extaddr']=='localhost') + $backend['extaddr'] = "127.0.0.1"; + + if (!is_array($a_bind[$bname])) { + $a_bind[$bname] = array(); + $a_bind[$bname]['config'] = array(); + // Settings which are constant for a merged frontend + $a_bind[$bname]['name'] = $backend['name']; + $a_bind[$bname]['extaddr'] = $backend['extaddr']; + $a_bind[$bname]['port'] = $backend['port']; + } + $b = &$a_bind[$bname]; + + // Overwrite ? + if ($backend['secondary'] != 'yes') { + if (isset($b['type'])) + $input_errors[] = "Multiple primary frondends for $bname"; + $b['type'] = $backend['type']; + $b['forwardfor'] = $backend['forwardfor']; + $b['httpclose'] = $backend['httpclose']; + $b['max_connections'] = $backend['max_connections']; + $b['client_timeout'] = $backend['client_timeout']; + $b['advanced'] = $backend['advanced']; + $b['ssloffload'] = $backend['ssloffload']; + $b['advanced_bind'] = $backend['advanced_bind']; + } + + if ($ssl_crt != "") { + if ($b['ssl_info'] == "") + $b['ssl_info'] = "ssl {$backend['dcertadv']}"; + $b['ssl_info'] .= $ssl_crt; + } + + // pointer to each backend + $b['config'][] = $backend; + } + } + + $a_pendingpl = array(); + + // Construct and write out configuration for each "frontend" + if(is_array($a_bind)) { + foreach ($a_bind as $bind) { + if (count($bind['config']) > 1) + $frontendinfo = "frontend {$bind['name']}-merged\n"; + else + $frontendinfo = "frontend {$bind['name']}\n"; + + $advancedextra = array(); + + // Prepare ports for processing by splitting + $portss = "{$bind['port']},"; + $ports = split(",", $portss); + $ssl_info = $bind['ssl_info']; + $advanced_bind = $bind['advanced_bind']; + // Initialize variable + $listenip = ""; + + // Process and add bind directives for ports + foreach($ports as $port) { + if($port) { + if($bind['extaddr'] == "any") + $listenip .= "\tbind\t\t\t0.0.0.0:{$port} {$ssl_info} {$advanced_bind}\n"; + elseif($bind['extaddr']) + $listenip .= "\tbind\t\t\t{$bind['extaddr']}:{$port} {$ssl_info} {$advanced_bind}\n"; + else + $listenip .= "\tbind\t\t\t" . get_current_wan_address('wan') . ":{$port} {$ssl_info} {$advanced_bind}\n"; + } + } + + fwrite ($fd, "{$frontendinfo}"); + fwrite ($fd, "{$listenip}"); + + // Advanced pass thru + if($bind['advanced']) { + $advanced = explode("\n", base64_decode($bind['advanced'])); + foreach($advanced as $adv_line) { + if ($adv_line != "") { + fwrite($fd, "\t" . str_replace("\r", "", $adv_line) . "\n"); + } + } + } + + // https is an alias for tcp for clarity purpouses + if($bind['type'] == "https") { + $backend_type = "tcp"; + } else { + $backend_type = $bind['type']; + } + + fwrite ($fd, "\tmode\t\t\t" . $backend_type . "\n"); + fwrite ($fd, "\tlog\t\t\tglobal\n"); + fwrite ($fd, "\toption\t\t\tdontlognull\n"); + + if ($backend_type == 'http') + { + if($bind['httpclose']) + fwrite ($fd, "\toption\t\t\thttpclose\n"); + + if($bind['forwardfor']) { + fwrite ($fd, "\toption\t\t\tforwardfor\n"); + if($bind['ssloffload'] == "yes") + fwrite ($fd, "\treqadd X-Forwarded-Proto:\ https\n"); + else + fwrite ($fd, "\treqadd X-Forwarded-Proto:\ http\n"); + } + } + + if($bind['max_connections']) + fwrite ($fd, "\tmaxconn\t\t\t" . $bind['max_connections'] . "\n"); + + if(!$bind['client_timeout']) + $bind['client_timeout'] = 30000; + + fwrite ($fd, "\ttimeout client\t\t" . $bind['client_timeout'] . "\n"); + + + // Combine the rest of the listener configs + $default_backend = ""; + $i = 0; + foreach ($bind['config'] as $bconfig) { + $a_acl=&$bconfig['ha_acls']['item']; + if(!is_array($a_acl)) + $a_acl=array(); + + $poolname = $bconfig['backend_serverpool'] . "_" . strtolower($bconfig['type']); + + // Create different pools if the svrport is set + if ($bconfig['svrport'] > 0) + $poolname .= "_" . $bconfig['svrport']; + + // Write this out once, and must be before any backend config text + if ($default_backend = "" || $bconfig['secondary'] != 'yes') { + $default_backend = $poolname; + } + + if (!isset($a_pendingpl[$poolname])) { + $a_pendingpl[$poolname] = array(); + $a_pendingpl[$poolname]['name'] = $poolname; + $a_pendingpl[$poolname]['frontend'] = $bconfig; + } + + if (strtolower($bind['type']) == "http" && $bconfig['ssloffload'] && $bconfig['ssloffloadacl']) { + $aclname = "SNI_" . $poolname; + $cert_cn = cert_get_cn($bconfig['ssloffloadcert'] ,true); + $a_acl[] = array('name' => $aclname,'expression' => 'host_matches', 'value' => $cert_cn); + } + + // combine acl's with same name to allow for 'combined checks' to check for example hostname and fileextension together.. + $a_acl_combine = array(); + foreach ($a_acl as $entry) { + $name = $entry['name']; + $a_acl_combine[$name][] = $entry; + } + + foreach ($a_acl_combine as $a_usebackend) { + $aclnames = ""; + foreach ($a_usebackend as $entry) { + $acl = haproxy_find_acl($entry['expression']); + if (!$acl) + continue; + + // Filter out acls for different modes + if ($acl['mode'] != '' && $acl['mode'] != strtolower($bind['type'])) + continue; + + $expr = sprintf($acl['syntax'],$entry['value'],$poolname); + + $aclname = $i . "_" . $entry['name']; + $aclnames .= $aclname." "; + fwrite ($fd, "\tacl\t\t\t" . $aclname . "\t" . $expr . "\n"); + + if ($acl['advancedoptions'] != '') + $advancedextra[$acl['syntax']] = $acl['advancedoptions']."\n"; + $i++; + } + fwrite ($fd, "\tuse_backend\t\t" . $poolname . " if " . $aclnames . "\n"); + } + } + fwrite ($fd, "\tdefault_backend\t\t" . $default_backend . "\n"); + + foreach($advancedextra as $extra) + fwrite ($fd, "\t".$extra."\n"); + fwrite ($fd, "\n"); + } + } + // Construct and write out configuration for each "backend" + if (is_array($a_pendingpl) && is_array($a_pools)) { + foreach ($a_pendingpl as $pending) { + foreach ($a_pools as $pool) { + if ($pending['frontend']['backend_serverpool'] == $pool['name']) { + write_backend($fd, $pending['name'], $pool, $pending['frontend']); + } + } + } + } + fwrite ($fd, "\n"); + + // Sync HAProxy configuration (if enabled) + if(isset($config['installedpackages']['haproxy']['enablesync'])) { + if($config['installedpackages']['haproxy']['synchost1']) { + haproxy_do_xmlrpc_sync($config['installedpackages']['haproxy']['synchost1'], + $config['installedpackages']['haproxy']['syncpassword']); + } + if($config['installedpackages']['haproxy']['synchost2']) { + haproxy_do_xmlrpc_sync($config['installedpackages']['haproxy']['synchost2'], + $config['installedpackages']['haproxy']['syncpassword']); + } + if($config['installedpackages']['haproxy']['synchost3']) { + haproxy_do_xmlrpc_sync($config['installedpackages']['haproxy']['synchost3'], + $config['installedpackages']['haproxy']['syncpassword']); + } + } + + // create config file + fclose($fd); + + if ($input_errors) + { + require_once("guiconfig.inc"); + print_input_errors($input_errors); + } + + if (isset($a_global['carpdev'])) + haproxy_install_cron(true); + else + haproxy_install_cron(false); + + $freebsd_version = substr(trim(`uname -r`), 0, 1); + if(!file_exists("/usr/bin/limits")) { + exec("fetch -q -o /usr/bin/limits http://files.pfsense.org/extras/{$freebsd_version}/limits"); + exec("chmod a+rx /usr/bin/limits"); + } +} + +function haproxy_is_running() { + $running = (shell_exec("/bin/pgrep -x haproxy") != ''); + return $running; +} + + +function haproxy_load_modules() { + // On FreeBSD 8 ipfw is needed to allow 'transparent' proxying (getting reply's to a non-local ip to pass back to the client-socket).. + // On FreeBSD 9 it is probably possible to do the same with the pf option "divert-reply" + mute_kernel_msgs(); + if (!is_module_loaded("ipfw.ko")) { + mwexec("/sbin/kldload ipfw"); + /* make sure ipfw is not on pfil hooks */ + mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"pf\" net.inet6.ip6.pfil.inbound=\"pf\"" . + " net.inet.ip.pfil.outbound=\"pf\" net.inet6.ip6.pfil.outbound=\"pf\""); + } + /* Activate layer2 filtering */ + mwexec("/sbin/sysctl net.link.ether.ipfw=1"); + unmute_kernel_msgs(); +} + +function use_transparent_clientip_proxying() { + global $config; + $a_backends = &$config['installedpackages']['haproxy']['ha_pools']['item']; + if (is_array($a_backends)) { + foreach ($a_backends as $backend) { + if ($backend["transparent_clientip"] == 'yes') { + return true; + break; + } + } + } + return false; +} + +function load_ipfw_rules() { + // On FreeBSD 8 pf does not support "divert-reply" so ipfw is needed. + global $g, $config; + $ipfw_zone_haproxy = "haproxy"; + + $a_backends = &$config['installedpackages']['haproxy']['ha_pools']['item']; + + haproxy_load_modules(); + + $transparent_interfaces = array(); + $transparent_backends = array(); + foreach ($a_backends as $backend) { + if ($backend["transparent_clientip"] != 'yes') + continue; + $real_if = get_real_interface($backend["transparent_interface"]); + $a_servers = &$backend['ha_servers']['item']; + foreach($a_servers as $server) { + if (is_array($a_servers)) { + + foreach($a_servers as $be) { + if (!$be['status'] == "inactive") + continue; + if (!is_ipaddr($be['address'])) + continue; + $item = array(); + $item['address'] = $be['address']; + $item['port'] = $be['port']; + $item['interface'] = $real_if; + $transparent_backends[] = $item; + $transparent_interfaces[$real_if] = 1; + } + } + } + } + mwexec("/usr/local/sbin/ipfw_context -a $ipfw_zone_haproxy", true); + + foreach($transparent_interfaces as $transparent_if => $value) { + mwexec("/usr/local/sbin/ipfw_context -a $ipfw_zone_haproxy -n $transparent_if", true); + } + + $rulenum = 64000; // why that high? captiveportal.inc also does it... + $rules = "flush\n"; + foreach($transparent_backends as $transparent_be) { + $rules .= "add $rulenum fwd localhost tcp from {$transparent_be["address"]} {$transparent_be["port"]} to any in recv {$transparent_be["interface"]}\n"; + $rulenum++; + } + + + file_put_contents("{$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", $rules); + mwexec("/usr/local/sbin/ipfw_context -s $ipfw_zone_haproxy", true); + mwexec("/sbin/ipfw -x $ipfw_zone_haproxy -q {$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", true); +} + +function haproxy_check_run($reload) { + global $config, $g, $haproxy_run_message; + + $a_global = &$config['installedpackages']['haproxy']; + + exec("/usr/bin/limits -n 300014"); + + if(use_transparent_clientip_proxying()) + load_ipfw_rules(); + else + mwexec("/usr/local/sbin/ipfw_context -d haproxy", true); + + if(isset($a_global['enable'])) { + if (isset($a_global['carpdev'])) { + $status = get_carp_interface_status($a_global['carpdev']); + if ($status != "MASTER") { + if (haproxy_is_running()) { + log_error("Stopping haproxy on CARP backup."); + //exec("/bin/pkill -F /var/run/haproxy.pid haproxy");//doesnt work for multiple pid's in a pidfile + haproxy_kill(); + } + return (0); + } else if (haproxy_is_running() && $reload == 0) { + return (0); + } + log_error("Starting haproxy on CARP master."); + /* fallthrough */ + } else if ($reload == 0) + return (0); + + if (haproxy_is_running()) { + if (isset($a_global['terminate_on_reload'])) + $sf_st = "-st";//terminate old process as soon as the new process is listening + else + $sf_st = "-sf";//finish serving existing connections exit when done, and the new process is listening + exec("/usr/local/sbin/haproxy -f /var/etc/haproxy.cfg -p /var/run/haproxy.pid $sf_st `cat /var/run/haproxy.pid` 2>&1", $output, $errcode); + } else { + exec("/usr/local/sbin/haproxy -f /var/etc/haproxy.cfg -p /var/run/haproxy.pid -D 2>&1", $output, $errcode); + } + foreach($output as $line) + $haproxy_run_message .= "<br/>" . htmlspecialchars($line) . "\n"; + return ($errcode); + } else { + if ($reload && haproxy_is_running()) { + //exec("/bin/pkill -F /var/run/haproxy.pid haproxy");//doesnt work for multiple pid's in a pidfile + haproxy_kill(); + } + return (0); + } +} + +function haproxy_kill($killimmediately = true) { + if ($killimmediately) + $signal = "KILL"; // stop now + else + $signal = "USR1"; // stop when all connections are closed + killprocesses("haproxy", "/var/run/haproxy.pid", $signal); +} + +function killprocesses($processname, $pidfile, $signal = "KILL") { + exec("kill -$signal `pgrep -x $processname | grep -w -f $pidfile`"); +} + +function haproxy_do_xmlrpc_sync($sync_to_ip, $password) { + global $config, $g; + + if(!$password) + return; + + if(!$sync_to_ip) + return; + + // Do not allow syncing to self. + $donotsync = false; + $lanip = find_interface_ip($config['interfaces']['lan']['if']); + if($lanip == $sync_to_ip) + $donotsync = true; + $wanip = find_interface_ip($config['interfaces']['wan']['if']); + if($wanip == $sync_to_ip) + $donotsync = true; + for ($j = 1; isset($config['interfaces']['opt' . $j]); $j++) { + $optip = find_interface_ip($config['interfaces']['opt' . $j]['if']); + if($optip == $sync_to_ip) + $donotsync = true; + } + if($donotsync) { + log_error("Disallowing sync loop for HAProxy sync."); + return; + } + + $xmlrpc_sync_neighbor = $sync_to_ip; + if($config['system']['webgui']['protocol'] != "") { + $synchronizetoip = $config['system']['webgui']['protocol']; + $synchronizetoip .= "://"; + } + $port = $config['system']['webgui']['port']; + /* if port is empty lets rely on the protocol selection */ + if($port == "") { + if($config['system']['webgui']['protocol'] == "http") + $port = "80"; + else + $port = "443"; + } + $synchronizetoip .= $sync_to_ip; + + /* xml will hold the sections to sync */ + $xml = array(); + $xml['haproxy'] = $config['installedpackages']['haproxy']; + + // Prevent sync loops + unset($xml['synchost1']); + unset($xml['synchost2']); + unset($xml['synchost3']); + unset($xml['syncpassword']); + + /* assemble xmlrpc payload */ + $params = array( + XML_RPC_encode($password), + XML_RPC_encode($xml) + ); + + /* set a few variables needed for sync code borrowed from filter.inc */ + $url = $synchronizetoip; + log_error("Beginning HAProxy XMLRPC sync to {$url}:{$port}."); + $method = 'pfsense.merge_installedpackages_section_xmlrpc'; + $msg = new XML_RPC_Message($method, $params); + $cli = new XML_RPC_Client('/xmlrpc.php', $url, $port); + $cli->setCredentials('admin', $password); + if($g['debug']) + $cli->setDebug(1); + /* send our XMLRPC message and timeout after 250 seconds */ + $resp = $cli->send($msg, "250"); + if(!$resp) { + $error = "A communications error occurred while attempting HAProxy XMLRPC sync with {$url}:{$port}."; + log_error($error); + file_notice("sync_settings", $error, "HAProxy Settings Sync", ""); + } elseif($resp->faultCode()) { + $cli->setDebug(1); + $resp = $cli->send($msg, "250"); + $error = "An error code was received while attempting HAProxy XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString(); + log_error($error); + file_notice("sync_settings", $error, "HAProxy Settings Sync", ""); + } else { + log_error("HAProxy XMLRPC sync successfully completed with {$url}:{$port}."); + } + + /* tell haproxy to reload our settings on the destionation sync host. */ + $method = 'pfsense.exec_php'; + $execcmd = "require_once('/usr/local/pkg/haproxy.inc');\n"; + $execcmd .= "haproxy_configure();\n"; + + /* assemble xmlrpc payload */ + $params = array( + XML_RPC_encode($password), + XML_RPC_encode($execcmd) + ); + + log_error("HAProxy XMLRPC reload data {$url}:{$port}."); + $msg = new XML_RPC_Message($method, $params); + $cli = new XML_RPC_Client('/xmlrpc.php', $url, $port); + $cli->setCredentials('admin', $password); + $resp = $cli->send($msg, "250"); + if(!$resp) { + $error = "A communications error occurred while attempting HAProxy XMLRPC sync with {$url}:{$port} (exec_php)."; + log_error($error); + file_notice("sync_settings", $error, "HAProxy Settings Reload", ""); + } elseif($resp->faultCode()) { + $cli->setDebug(1); + $resp = $cli->send($msg, "250"); + $error = "An error code was received while attempting HAProxy XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString(); + log_error($error); + file_notice("sync_settings", $error, "HAProxy Settings Sync", ""); + } else { + log_error("HAProxy XMLRPC reload data success with {$url}:{$port} (exec_php)."); + } +} + +function get_frontend_id($name) { + global $config; + $a_backend = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $i = 0; + foreach($a_backend as $backend) + { + if ($backend['name'] == $name) + return $i; + $i++; + } + return null; +} + +function get_frontend_ipport($fontend) { + global $config; + $a_backend = &$config['installedpackages']['haproxy']['ha_backends']['item']; + if ($fontend['secondary'] == 'yes') + $mainfontend = $a_backend[get_frontend_id($fontend['primary_frontend'])]; + else + $mainfontend = $fontend; + if($mainfontend['extaddr'] == "any") + $result = "0.0.0.0"; + elseif($mainfontend['extaddr']) + $result = $mainfontend['extaddr']; + else + $result = get_current_wan_address('wan'); + return $result . ":" . $mainfontend['port']; +} + +function haproxy_check_config() { + global $config; + $a_backends = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $result = false; + $activefrontends = array(); + $issues = array(); + + foreach($a_backends as $frontend) { + if (($frontend['status'] != 'active') || ($frontend['secondary'] == 'yes')) + continue; + $ipport = get_frontend_ipport($frontend); + if (isset($activefrontends[$ipport])) + $issues['P_'.$ipport] = "Multiple primary frontends with IP:Port \"$ipport\""; + else + $activefrontends[$ipport] = true; + } + foreach($a_backends as $frontend) { + if (($frontend['status'] != 'active') || ($frontend['secondary'] != 'yes')) + continue; + $ipport = get_frontend_ipport($frontend); + if (!isset($activefrontends[$ipport])) + $issues['S_'.$frontend['name']] = "Secondary frontend \"{$frontend['name']}\" without active primary frontend."; + } + foreach ($issues as $item) + $result .= ($result == false ? "" : "<br/>") . $item; + return $result; +} + +function get_haproxy_frontends($excludeitem="") { + global $config; + $a_frontend = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $result = array(); + foreach($a_frontend as &$frontend) + { + if ($frontend['secondary']) + continue; + if ($frontend['name'] == $excludeitem) + continue; + + $serveradress = "{$frontend['extaddr']}:{$frontend['port']}"; + $result[$frontend['name']]['name'] = "{$frontend['name']} - {$frontend['type']} ({$serveradress})"; + $result[$frontend['name']]['ref'] = &$frontend; + } + asort($result, SORT_STRING); + return $result; +} + +function get_frontent_acls($frontend) { + $result = array(); + $a_acl = &$frontend['ha_acls']['item']; + if (is_array($a_acl)) + { + foreach ($a_acl as $entry) { + $acl = haproxy_find_acl($entry['expression']); + if (!$acl) + continue; + + // Filter out acls for different modes + if ($acl['mode'] != '' && $acl['mode'] != strtolower($frontend['type'])) + continue; + + $acl_item = array(); + $acl_item['descr'] = $acl['descr'] . ": " . $entry['value']; + $acl_item['ref'] = $entry; + + $result[] = $acl_item; + } + } + return $result; +} + +function phparray_to_javascriptarray_recursive($nestID, $path, $items, $nodeName, $includeitems) { + $offset = str_repeat(' ',$nestID); + $itemName = "item$nestID"; + echo "{$offset}$nodeName = {};\n"; + if (is_array($items)) + foreach ($items as $key => $item) + { + if (in_array($path.'/'.$key, $includeitems)) + $subpath = $path.'/'.$key; + else + $subpath = $path.'/*'; + if (in_array($subpath, $includeitems) || in_array($path.'/*', $includeitems)) { + if (is_array($item)) { + $subNodeName = "item$nestID"; + phparray_to_javascriptarray_recursive($nestID+1, $subpath, $items[$key], $subNodeName, $includeitems); + echo "{$offset}{$nodeName}['{$key}'] = $itemName;\n"; + } else + echo "{$offset}{$nodeName}['$key'] = '$item';\n"; + } + } +} + +function phparray_to_javascriptarray($items, $javaMapName, $includeitems) { + phparray_to_javascriptarray_recursive(1,'',$items, $javaMapName, $includeitems); +} + +function haproxy_escapestring($configurationsting) { + $result = str_replace('\\', '\\\\', $configurationsting); + $result = str_replace(' ', '\\ ', $result); + return str_replace('#', '\\#', $result); +} + +function echo_html_select($name, $keyvaluelist, $selected, $listEmptyMessage="", $onchangeEvent="") { + if (count($keyvaluelist)>0){ + if ($onchangeEvent != "") + $onchangeEvent .= " onchange=$onchangeEvent"; + echo "<select name=\"$name\" id=\"$name\" class=\"formselect\"$onchangeEvent>"; + foreach($keyvaluelist as $key => $desc){ + $selectedhtml = $key == $selected ? "selected" : ""; + echo "<option value=\"{$key}\" {$selectedhtml}>{$desc['name']}</option>"; + } + echo "</select>"; + } else { + echo $listEmptyMessage; + } +} + +?> |