From 88c3a9907302977e217a1181c53534aeca479e75 Mon Sep 17 00:00:00 2001 From: Micke2k Date: Thu, 24 Mar 2016 04:09:49 +0400 Subject: [PATCH] First attempt at new install 1. Added check for RRD 1.5 2. Backported dependencies check 3. Backported template installer 4. Added theme selection Tested and working with new installation on Linux. Backported a template for testing as well -> Disk IO. I will test the Upgrade and Windows installation as well as clean it up and add some extras. --- install/index.php | 435 ++++++++++++++++++++++--- install/templates/Disk IO Usage.xml.gz | Bin 0 -> 11290 bytes 2 files changed, 396 insertions(+), 39 deletions(-) create mode 100644 install/templates/Disk IO Usage.xml.gz diff --git a/install/index.php b/install/index.php index 82345f5ab0..7185b72d96 100644 --- a/install/index.php +++ b/install/index.php @@ -24,13 +24,15 @@ define('IN_CACTI_INSTALL', 1); -include('../include/global.php'); +include_once('../include/global.php'); + +top_header(); + +api_plugin_hook('console_before'); /* allow the upgrade script to run for as long as it needs to */ ini_set('max_execution_time', '0'); -/* verify all required php extensions */ -if (!verify_php_extensions()) {exit;} $cacti_versions = array('0.8', '0.8.1', '0.8.2', '0.8.2a', '0.8.3', '0.8.3a', '0.8.4', '0.8.5', '0.8.5a', '0.8.6', '0.8.6a', '0.8.6b', '0.8.6c', '0.8.6d', '0.8.6e', '0.8.6f', '0.8.6g', '0.8.6h', '0.8.6i', '0.8.6j', '0.8.6k', @@ -64,24 +66,16 @@ exit; } -function verify_php_extensions() { - global $database_type; - $extensions = array('session', 'sockets', 'xml', 'PDO', 'pdo_' . $database_type); - $ok = true; - $missing_extension = "

Error

-

The following PHP extensions are missing:

Please install those PHP extensions and retry

"; - } - return $ok; + return $extensions; } + function db_install_execute($cacti_version, $sql) { $sql_install_cache = (isset($_SESSION['sess_sql_install_cache']) ? $_SESSION['sess_sql_install_cache'] : array()); @@ -144,12 +138,153 @@ function find_best_path($binary_name) { } } + +function plugin_setup_get_templates() { + global $config; + $templates = Array( + 'Disk IO Usage.xml.gz' + ); + + $path = $config['base_path'] . '/install/templates'; + $info = Array(); + foreach ($templates as $xmlfile) { + $filename = "compress.zlib:///$path/$xmlfile"; + $xml = file_get_contents($filename);; + //Loading Template Information from package + $xmlget = simplexml_load_string($xml); + $data = to_array($xmlget); + if (is_array($data['info']['author'])) $data['info']['author'] = '1'; + if (is_array($data['info']['email'])) $data['info']['email'] = '2'; + if (is_array($data['info']['description'])) $data['info']['description'] = '3'; + if (is_array($data['info']['homepage'])) $data['info']['homepage'] = '4'; + + $data['info']['filename'] = $xmlfile; + $info[] = $data['info']; + } + return $info; +} + +function plugin_setup_install_template($xmlfile, $opt = 0, $interval = 5) { + global $config; + if ($opt) { + $path = $config['base_path'] . '/install/templates/'; + } else { + $path = $config['base_path'] . '/install/templates/'; + } + + if ($interval == 1) { + $interval = array(1, 2, 3, 4, 5); + } else { + $interval = array(1, 2, 3, 4); + } + + /* set new timeout and memory settings */ + ini_set("max_execution_time", "5"); + ini_set("memory_limit", "64M"); + + $public_key = <<") !== FALSE) { + $binary_signature = base64_decode(trim(str_replace(array('', ''), '', $x))); + $x = " \n"; + } + $xml .= "$x"; + } + fclose($f); + + // Verify Signature + $ok = openssl_verify($xml, $binary_signature, $public_key); + if ($ok == 1) { + //print " File is signed correctly\n"; + } elseif ($ok == 0) { + //print " ERROR: File has been tampered with\n"; + //exit; + return; + } else { + //print " ERROR: Could not verify signature!\n"; + //exit; + return; + } + //print "Loading Plugin Information from package\n"; + $xmlget = simplexml_load_string($xml); + $data = to_array($xmlget); + + $plugin = $data['info']['name']; + //print "Verifying each files signature\n"; + if (isset($data['files']['file']['data'])) { + $data['files']['file'] = array($data['files']['file']); + } + + foreach ($data['files']['file'] as $f) { + + $binary_signature = base64_decode($f['filesignature']); + $fdata = base64_decode($f['data']); + $ok = openssl_verify($fdata, $binary_signature, $public_key); + if ($ok == 1) { + //print " File OK : " . $f['name'] . "\n"; + } else { + //print " ERROR: Could not verify signature for file: " . $f['name'] . "\n"; + //exit; + return; + } + } + include_once($config['base_path'] . "/lib/import.php"); + + $p = $config['base_path']; + $error = false; + //print "Writing Files\n"; + foreach ($data['files']['file'] as $f) { + $fdata = base64_decode($f['data']); + $name = $f['name']; + if (substr($name, 0, 8) == 'scripts/' || substr($name, 0, 9) == 'resource/') { + $filename = "$p/$name"; + //print " Writing $filename\n"; + $file = fopen($filename,'wb'); + fwrite($file ,$fdata, strlen($fdata)); + fclose($file); + clearstatcache(); + if (!file_exists($filename)) { + //print " Unable to create directory: $filename\n"; + } + } else { + $debug_data = import_xml_data($fdata, false, $interval); + } + } + //print "File creation complete\n"; +} + + +function to_array ($data) { + if (is_object($data)) { + $data = get_object_vars($data); + } + return (is_array($data)) ? array_map(__FUNCTION__,$data) : $data; +} + + /* Here, we define each name, default value, type, and path check for each value we want the user to input. The "name" field must exist in the 'settings' table for this to work. Cacti also uses different default values depending on what OS it is running on. */ +function install_file_paths () { +global $config, $settings; + + /* RRDTool Binary Path */ +$input = array(); $input['path_rrdtool'] = $settings['path']['path_rrdtool']; if ($config['cacti_server_os'] == 'unix') { @@ -324,6 +459,7 @@ function find_best_path($binary_name) { } } + /* log file path */ $input['path_cactilog'] = $settings['path']['path_cactilog']; $input['path_cactilog']['description'] = 'The path to your Cacti log file.'; @@ -333,6 +469,16 @@ function find_best_path($binary_name) { $input['path_cactilog']['default'] = $config['base_path'] . '/log/cacti.log'; } +/* Theme */ +$input['selected_theme'] = $settings['visual']['selected_theme']; +$input['selected_theme']['description'] = 'Please select one of the available Themes to skin your Cacti with.'; +if (config_value_exists('selected_theme')) { + $input['selected_theme']['default'] = read_config_option('selected_theme'); +} else { + $input['selected_theme']['default'] = 'modern'; +} + + /* RRDTool Version */ if ((file_exists($input['path_rrdtool']['default'])) && (($config['cacti_server_os'] == 'win32') || (is_executable($input['path_rrdtool']['default']))) ) { $input['rrdtool_version'] = $settings['general']['rrdtool_version']; @@ -342,7 +488,9 @@ function find_best_path($binary_name) { exec("\"" . $input['path_rrdtool']['default'] . "\"", $out_array); if (sizeof($out_array) > 0) { - if (preg_match('/^RRDtool 1\.4/', $out_array[0])) { + if (preg_match('/^RRDtool 1\.5/', $out_array[0])) { + $input['rrdtool_version']['default'] = 'rrd-1.5.x'; + }else if (preg_match('/^RRDtool 1\.4\./', $out_array[0])) { $input['rrdtool_version']['default'] = 'rrd-1.4.x'; }else if (preg_match('/^RRDtool 1\.3\./', $out_array[0])) { $input['rrdtool_version']['default'] = 'rrd-1.3.x'; @@ -353,7 +501,8 @@ function find_best_path($binary_name) { } } } - + return $input; +} /* default value for this variable */ if (!isset($_REQUEST['install_type'])) { $_REQUEST['install_type'] = 0; @@ -369,38 +518,69 @@ function find_best_path($binary_name) { /* pre-processing that needs to be done for each step */ if (isset($_REQUEST['step']) && $_REQUEST['step'] > 0) { $step = intval($_REQUEST['step']); + + /* license and welcome screen - send to dependencies */ if ($step == '1') { $step = '2'; - } elseif (($step == '2') && ($_REQUEST['install_type'] == '1')) { + /* check for dependencies - send to install/upgrade */ + } elseif ($step == '2') { $step = '3'; - } elseif (($step == '2') && ($_REQUEST['install_type'] == '3')) { + /* install/upgrade - if user chooses "New Install" send to pathcheck */ + } elseif (($step == '3') && ($_REQUEST['install_type'] == '1')) { + $step = '4'; + /* install/upgrade - if user chooses "Upgrade" send to upgrade */ + } elseif (($step == '3') && ($_REQUEST['install_type'] == '3')) { $step = '8'; + /* upgrade-oldversion - if user runs old version send to oldversionwarning*/ } elseif (($step == '8') && ($old_version_index <= array_search('0.8.5a', $cacti_versions))) { $step = '9'; + /* upgrade - if user upgrades send to pathcheck */ } elseif ($step == '8') { - $step = '3'; + $step = '4'; + /* oldversionwarning - if user upgrades from old version send to dependencies */ } elseif ($step == '9') { $step = '3'; - } elseif ($step == '3') { - $step = '4'; + /* pathcheck - send to installpaths */ + } elseif ($step == '4') { + $step = '5'; + /* installpaths - send to templates */ + } elseif ($step == '5') { + $step = '6'; + /* templates - send to install and finalize */ + } elseif ($step == '6') { + $step = '7'; } } else { $step = 1; } -if ($step == '4') { + + + + +/* install and finalize - Install templates, change cacti version and send to login page */ +if ($step == '7') { include_once('../lib/data_query.php'); include_once('../lib/utility.php'); - $i = 0; - - /* get all items on the form and write values for them */ - while (list($name, $array) = each($input)) { - if (isset($_POST[$name])) { - db_execute("replace into settings (name,value) values ('$name','" . $_POST[$name] . "')"); + + /* look for templates that have been checked for install */ + $install = Array(); + foreach ($_POST as $post => $v) { + if (substr($post, 0, 4) == 'chk_' && is_numeric(substr($post, 4))) { + $install[] = substr($post, 4); + } } - } - + /* install templates */ + $templates = plugin_setup_get_templates(1); + if (!empty($install)) { + foreach ($install as $i) { + plugin_setup_install_template($templates[$i]['filename'], 1, $templates[$i]['interval']); + } + } + + /* clear session */ + setcookie(session_name(),'',time() - 3600,'/'); kill_session_var('sess_config_array'); @@ -425,11 +605,17 @@ function find_best_path($binary_name) { rsa_check_keypair(); } + /* change cacti version */ db_execute('DELETE FROM version'); db_execute("INSERT INTO version (cacti) VALUES ('" . $config["cacti_version"] . "')"); + /* send to login page */ header ('Location: ../index.php'); exit; + + + + /* upgrade */ }elseif (($step == '8') && ($_REQUEST['install_type'] == '3')) { /* if the version is not found, die */ if (!is_int($old_version_index)) { @@ -622,8 +808,65 @@ function find_best_path($binary_name) { but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

+ + + Pre-installation Check
'; + print 'Cacti requies several PHP Modules to be installed to work properly. If any of these are not installed, you will be unable to continue the installation until corrected.

'; + + html_start_box(" " . __("Required PHP Modules") . "", "30", 0, "", "", false); + html_header(array(array('name' => 'Name'), array('name' => 'Required'), array('name' => 'Installed'))); + + + form_selectable_cell('PHP Version', ''); + form_selectable_cell('5.2.0+', ''); + form_selectable_cell((version_compare(PHP_VERSION, '5.2.0', '<') ? "" . PHP_VERSION . "" : "" . PHP_VERSION . ""), ''); + form_end_row(); + + $extensions = array( array('name' => 'session', 'installed' => false), + array('name' => 'sockets', 'installed' => false), + array('name' => 'mysql', 'installed' => false), + array('name' => 'xml', 'installed' => false), + array('name' => 'pcre', 'installed' => false), + array('name' => 'json', 'installed' => false), + ); + + $ext = verify_php_extensions($extensions); + $i = 0; + $enabled = true; + foreach ($ext as $id =>$e) { + form_alternate_row_color($colors["alternate"], $colors["light"], $i, 'line' . $id); $i++; + form_selectable_cell($e['name'], ''); + form_selectable_cell('Yes', ''); + form_selectable_cell(($e['installed'] ? 'Yes' : 'NO'), ''); + form_end_row(); + if (!$e['installed']) $enabled = false; + } + html_end_box(false); + + print '
' . __('
These extensions may increase the performance of your Cacti install but are not necessary.

'); + $extensions = array( array('name' => 'snmp', 'installed' => false), + array('name' => 'mysqli', 'installed' => false), + array('name' => 'gd', 'installed' => false), + + ); + + $ext = verify_php_extensions($extensions); + $i = 0; + html_start_box(" " . __("Other Modules") . "", "30", 0, "", "", false); + html_header(array(array('name' => 'Name'), array('name' => 'Required'), array('name' => 'Installed'))); + foreach ($ext as $id => $e) { + form_alternate_row_color($colors["alternate"], $colors["light"], $i, 'line' . $id); $i++; + //print '' . $e['name'] . 'Yes' . ($e['installed'] ? 'Yes' : 'NO') . ''; + form_selectable_cell($e['name'], ''); + form_selectable_cell('Yes', ''); + form_selectable_cell(($e['installed'] ? 'Yes' : 'NO'), ''); + form_end_row(); + } + html_end_box(false); - +?> +

Please select the type of installation

@@ -644,11 +887,20 @@ function find_best_path($binary_name) { print "Server Operating System Type: " . $config['cacti_server_os'] . '
'; ?>

- + + + + + +

Make sure all of these values are correct before continuing.

-

NOTE: Once you click 'Finish', +

NOTE: Once you click 'Finish', all of your settings will be saved and your database will be upgraded if this is an upgrade. You can change any of the settings on this screen at a later time by going to 'Cacti Settings' from within Cacti.

+ + + Settings installed

'; + + /* Check if /resource is writable */ + print "

Next step is template installation. For installation to work the '". $config['base_path'] . "/resource' folder needs to be writable by the webserver.

"; + + + if (is_writable('../resource/snmp_queries')) { + $writeaccess_snmp_q = "Writable"; + } else { + $writeaccess_snmp_q = "is not writable"; + $writable=FALSE; + } + + if (is_writable('../resource/script_server')) { + $writeaccess_script_s = "Writable"; + } else { + $writeaccess_script_s = "is not writable"; + $writable=FALSE; + } + if (is_writable('../resource/script_queries')) { + $writeaccess_script_q = "Writable"; + } else { + $writeaccess_script_q = "is not writable"; + $writable=FALSE; + } + + + print "

". $config['base_path'] . "/resource/snmp_queries is $writeaccess_snmp_q

"; + print "

". $config['base_path'] . "/resource/script_server is $writeaccess_script_s

"; + print "

". $config['base_path'] . "/resource/script_queries is $writeaccess_script_q


"; + + /* Print help message for unix and windows if directory is not writable */ + if (($config['cacti_server_os'] == "unix") && isset($writable)) { + print 'Make sure your webserver has read and write access to the entire folder structure.
Example: chown -R apache.apache /resource
'; + } elseif (($config['cacti_server_os'] == "win32") && isset($writable)){ + print 'Check Permissions'; + } + + + + ?> + + + +

Make sure all of these values are correct before continuing.

+ Template Setup"; + print "Templates allow you to monitor and graph a vast assortment of data within Cacti. While the base Cacti install provides basic templates for most devices, you can select a few extra templates below to include in your install.

"; + print "
"; + + $templates = plugin_setup_get_templates(); + + html_start_box('Templates', '100%', '3', 'center', "", ""); + html_header_checkbox(array(array('name' => 'Name'), array('name' => 'Description'), array('name' => 'Author'), array('name' => 'Homepage'))); + $i = 0; + foreach ($templates as $id => $p) { + form_alternate_row_color($colors["alternate"], $colors["light"], $i, 'line' . $id); $i++; + form_selectable_cell($p['name'], $id); + form_selectable_cell($p['description'], $id); + form_selectable_cell($p['author'], $id); + if ($p['homepage'] != '') { + form_selectable_cell("" . $p['homepage'] . "", $id); + } else { + form_selectable_cell('', $id); + } + form_checkbox_cell($p['name'], $id); + form_end_row(); + html_end_box(false); + + } + + ?> + +

Upgrade results:

- +

Important Upgrade Notice

@@ -782,5 +1135,9 @@ function find_best_path($binary_name) {
- - +-p5h++kgG*{p#-a9{>OK z{%#iC{HCoQgMXXDe@`A~%bWLa(`$5db@z1h>17uFroMk$ex@6<=O^#}({Ib!-C`Er zzJHs4r(52Abn}2_e)FE59{%6kx9-e+TK(_c&C~n0B?{?nDk&7=Ei z_3*U1zpMA?-~Xq#dAfQ0pMP7QW_OR&-u>ObHzf8y=qUG(t#AI~?`O8xQex9kP)Z&zXdCi+Al@_F6c7=Ha2 z{Jy&XUyx!u*XYCSY4+Qd5k4;rdm|&=msav4Ke*c+mx(fa07v1=yZ>||#vl#v+&+MA6 z_1}F|&K{(mx36_ST&&7|Ew2puAe}gHlUjeP@4R<|@q?>84Xjr9EDgI`>(ls9o2{VU9Y`B-=z_5q{CY?=j{2Yu`6M4wW7Ia&piDqpFaNuPzHKh6@++ObmdzFsI%mL&>>k>Uc8$thDDSiC&4bccP7V}J&a1G-l`W_e? z`d^V3$g#pV2cI>zw6#Kv<1imo$NVgyGp+))J$DA7yXw8jRqx_vig#^#*V54g^dWoZ z7+x)Cb+_>5#v|sgkiR45?Xr+9Re`LwrL}%3>3PJvYjPExwHKG)T;7sXgn^qayKv|xXp|DoYQ8n#ZO;bJqx+#Vx?lgReuC3`ImQ`s{O-4}|TxcK{r zh6icbK81!~pi5N_Q~8n8SJN>2{dLb13J={5{*oUOyWn@@Z*B|uDYmek$zcO$qJLZb znPT8OJQGPTwg1ci{0CT{V0jAAQ<%S6-p!t#KLOj%&RKMBJP$q?PcQAqvsQmHran=%qHxYY1HlYdBi_`>YAfOe>fZ8 zeEJw%tm%#Q_mErhw(u*O4o2!+wch=@_?^0$dKjDEQ)lD<{=U3<`u+2+2KNZMTrGnS z7gl^Pru=@IoVS>E9uKuqR~i$ACj*c9p)Y+qt5YWsj%_ID1gY<;NF{xJCiI7y#;ZC*mF{0K?)_Ka9ywv{r<*iKTbP6 zxID`@gD7qd)6d?mP;{_b5XsKy*N+{DRj!7>%Fpv@@7A5#C>eUMzzFSQ?-8+YHu`+cSDIGxTJpzgx59e9bIxC4J0M#Jb!MJ&}kO*Z7JVtizpA zEE2X6zU{GOvL=-%F)m@BWw2gP**u$z&cJgGFG=SS-}86G%*x-Po#P#&)=)DyJn3(w z7OaIe2r>6Dw|IzqxQF`w4QN9_k< zI`*cWaftRF@j0B5N{KwrRlHlK_3_?*SNfjMj7bOluP|o--p82zQCs@l8(QZHCptSq zJZYqa{9)PyKa6os92Q=SOv;86Zz@5oPL`>n@A`t#~ zMY3|Z-E!{GKbRP&eM@caooDN$dCp8$YVy@5S3iqpj{S&t7SLl*yf^26diQ#c&(f}q zH_pZ@x+3X?Jb_&K`bszx$QI!u&-feK=4)1B95wQtPcicgSHW%X`}`=uGBhXHIFJD*TQ4Ri?^fBc{Fk z3N7+E>2ES!Y<6Wt`qxZ^4_P+`0ZVM?o`ol&6Ij%T9#{m-;j7x%>okK-9Y3aesCKyY&0oM0P;Z90|jm3A?v?m|n%m`uNJ?4H zLtWP-Hy1sAJu&ODg1NLW2j2WH!)2J?stcF5k?D%VymFq7k4NAU^b2-5LSJL%*r8BH zu+wtI6B=7c{F8{p#oQcdv(!WbWA+M zROXDjLv0XnrLPX1eh2c5c?UKnTM_l!U)fHycr;O1vIt=>TENH~V8NEK#y;1^E8zTUndiaJ<+!m~R!($}*pE$X(Y&GA0)T*NJ%T=fXJK^RvwCm7qaoBBy)uNmo}cd1`=7vAoK@bA``pRD}~zc%m* z?5@%8vPgh76n+wrhXDF?;A0M8lTBc&Q5*U^E_DjVvNO-w+~=~o4L$78AjMP5geT{1 z?n8eA8{?YF{2p*ozYUpOgGYSnn6dEDFaIzNGMV@cCi*L6y;`SXR&;K@Z?h59H+_ z#8W%5p2f5m6fpx~bwPJ~ElbR@wU=&W-@#V{2BDM#et+cy(>1S^cvXL#8)qHJWP5JJ z2|X*E_;KD@+|KXp$9YEgL1iBOxiW@Kr}B`=cR8mVx^vs#A}$9XuH|0Zt*a<}uFP!_ zg5JimGQFGYjJAilxq}W4=Xbzk)ALfMt2SV3>%i9;?kBkkMB=474>W1RCsn0?u7IbZ zHy}F^bVy=$8}U5&3n@omjtwwv0p6c2e7Odm?c?gEEDf#yDJFyWx)wc`CAX!o*cpFJ-? zJMbswszKMX-LdN8T|$99veX=_@|U^OZqI>BZy-B0I+uJBJ`)Y6O0oQ-K2R#l4vov_ ztF+qxo*KQI`YM+x(tXf%s?_=|V7ZWFOHhp4fo@0wmFyYlgwJln56t<&fDaG>^CWi2 zA`W|RucQP206r?yLg-aVIuEoAHcdY;*DtoK*qd}5*XQ>OpFR9FG*#2op`&E;F7qoE zQ{a-q_I9dtFW<2vi!+CKr%CJb`OHq|dMWo4+vWTmNmJ>MBhb}P%O-E=iStw^4 zdGTY>)P=5cI}7OZ(YK`Ox||>FIGSRveaNB8l)eJ|5DP~@CP$s2Aw6!4L)ibATP%j} zaX?N@=vR~CIW5w)LuU+|N%(HyGkgcnc)&if)a2>_O&vKMB!r#Ki)H-r;(YV*qW9vi z&XWcnpm*142^utPYw_GhIt$>x-tFAM94JfTgPm&yy_$5yvrWBRv|F>6`J5|7|G$Yn zEp$R;Z(JoyLp)0V`ZngD)~-ehCp&701U6QIyluDzTWXMRp$9xVB&%Z0eDbyWsz?^l zvo7RPuB3IYa>s;Cp1|iZfo&H%bj{P*mUfy7`S+$_k-oXe`}z%RXp#PzrF|ALvnod1 z#^iG>vs9R7cHry7zh@oTNML_G643;{puOT04^Z?Kv+#_+AJYnD09t_u9O%`^+;GSt z@KalAXv5(lL%TzRftI3)X)`JQ zePl8DFDZ64SRx^FLoEO%hQA$+;B$)&Y3bCv;%9@4hX*$>-t- z9qK{%iG7WnmCsua_S##gBqiTzN2DBC)2B8W0#4rpp!ND zwX~+`FB4k775)HijuhNeV1J>X&CX5^SRONiJU;*z_&3)?`!C6+2x4j2lu>i<}B_DzJm=f zzSEjA*@*gm^ml>wC>J&TeP{6?FUQ+C&F^vle2x2^nKN$9D!+A=_=RL+?xeDj&U@op z{w#+MCf(Xjb!)oys#CvK&}pg=PB=q5+#~vByXd&BC`dJw$blSueDtp*?TW;zNct!{}}K)b{-A#8^dRrk1cO+E#S`sW8F30v%Oy9iJm8oak&nbwGSI&oNX3b z)-J6rrIL)eP#abHF$l4`G@?5t$Z%pF6piC>=Cm2u$8suv$U3(Vw^Q? zm7N>m(`;HDJM5UU(>7t5-L{S9vn1<{Yg;<5pua(*d@EwsZv9#@pOKz`Uio&LZk*=w zUhIFC#!dX!Xt#c?c-B!LaIa{W)&PmuikL!me$(qI9TYWPLo1%i^wr_}c^ldLGde#@ zN3HiS>qko0d1OCV`n1)Lk-dS9Aif5T^p$kh`MYK4X1>fL=GX(Cek{kg=QpFfG+*jv zqS(L{D&-YZtd#a2cyrKIID>Gm752Jf+wz?0o%LGlJjeMad~c3-^nnX_D3U(q$**KI zNrsM@a4o=dfhv+<8K1&=BHL^>FUKMqACL&~FLNt&lYaXWQq zABtAwTT#xBjD$ae9>?5R0v%xvOOMy zOh~$BjM@VI5f-hd4t(y9I?^y(f^6Cv}W1Ow;u4l$JKd0PZ zwarUX%RDQ+k)Kq^&mGB3NIov*o!!bi%zu`HUiv#n@O(K2Y1mOk9!uUX=W>e`YIZ%9 z#R?CW{efsAJ5RJP-3>ftP=SLN^Rex6G1Cfk?0S;?7uB`8!z^I`*M&>Gbkw%=4zyA<_Xzy=#yzhbn0mFo4NEh00P{$GkS)+39hdjY=&Pjl@b zo<6|SBkAa$j@Qju&eJhigImX7jnnyY#}6bM6%By9LXT@Ql+d3%5o_Ut-xSjC1M(3! zqK3IXk2RPTMwnlZ_BpT!SR3~&+TkZa2$u?ArY`={^&OU#h%%}8jU zD2wcLPoA*62zm(fA5h%2EqsR0tOHv5Jo$U$?!3g$SI6A^ILsYW|AA$@L#N-O^=jyJ z@LP}M12};x(4!&cGD~g09Pq?dNCt%O#-Iu9vw$r16XD2^u!b~Pv*s4%$>#d$Tnybb!d@&n*a~EYxt%K!Ht%#@wgX)A+$+d&$ol+p`?K zJEJ``*4K~i4UfXxr}JVHXJ9@$7&rV(eKn-qM~mM=0 zUx}wje;H5ztay83*dCRWJyxe@WowVYDE;+udVQI2g(<>vW8r%t#sL`^_`>hUETO%g zedto)u1K`A^~k)w!8G4no97|#?k5ehcy-eAndPe7fJ|sXmvl6+y$b&BQe2%YHsv6K zueUfi&l}D5#OnDT{p;RbQvOZ*Y?ao3X;aRh9!z)h2%ELsD1R>9yF8oriZYK|y?<#x zfj9G<%ce|axMtzBpBwwV-xYJvs8O~7@2!PGJ_`33#s)pml4)#;skqll<5j>1!)Y^* z3j9iI4v}1Otq0x1)SH5JHZz%X^gr%{WTzO#suS)gk``#qm2aLz!tA@Z%uur{Tag7qoVdm$s^m` zMK#Y0Ujql@`Ef*hYK8{szR(+5g8*%{n6?N>wkWRG%v~I2`T+lRjMt-DJ1y=nRf&0` zz0A^?fRAYv2Q+X-)fyam9vbPY{&JGe0ex!QM z!75&^^3jeh$o#?f+)-~E{G897+Ta($loIq=s+fC4I(Eo_dss>r$^#XfbR9O*gC@Tm z@0Zv+{F1Hjf~T$X_K<1=D*ZO*9Jxz%QHp-ET?FrTP@BA@C#V7wHJl z9eOQB^ZSnzJQEE z#vDO=?=dG-=UZML$dSzH zux)2?o92ma^E>z6BdqnA-|Y0g$aUN|u0|f@!Mw`@d6mt1+~z~pay57IA$P9Vcw(D_ zS+-y1M`rCd^CQdeSvzH}bQM5j@i5Sb7-4Mk_Y?RPXQ4qD?UEI?6g{a$FBPVm8`!MR|Wn$ZrwNeC$4%Ru3FS@{>tkEd+Vv~Ss&1W-v{tp zZ1`<#BNUtJ{m|O)n*P$dOF+2HDeXwZcMqvv1+9ri!2TQV>z<6r$2nJ}27l3V;mf0C zjcx0~d~tmMzJvw5s!+WlszYKXOdAufZ*jxy2uEKr_oeTd_81|+m-M~;SWaeNh3%=H=Q?au!2tYtMl*|=;pu6`oK?{ z!#GZIZ?HbVlB3oKPUgj(++S_f621xk2ah>pl8rQ1m??d#Gf454jj+x;yiz0f zOX~xa*Ms+WB&`z|<1noaXVAJEy=#H@;C;S~r1j(FA+Y3U%|mE=H}D}APU{=wA&id8 z{XLZzcW{Q=r5eOk(~2c#OOnlsCcw+m=hle!c_iG>p;IS+G0y1qfaSYiiKkot@bm$m z4tt?+9*@_}+5X96utxhhtZ_0w?(8#ykGCEgc0l<7X{`jMuSoFT&=@7OSBcKS7+SqR z9H0LiG7UPI);y9)A`;BIhW=3wg46FvZAts_RIZ@E$6^g&2|oE5{J>K7+Zp|Mz$jkt zq_ujzp&>fZPo1GfaaMap>)urN8GPCKo8#{CT$-yV4m4%pf_SamWC8 z06L!$zqJ|F$}+~Q19Gt8iX9NPp?deyU$$9VlazG@Qv5{aw0AEu+q5nW-w(1RBjQb_9G?S!()kcS8s`3I zzuhY^_wqQ*eJU?DF}KkNhM`)r+ylS0nD468=Fp;Z_{0YHAvdt0M!$PB<~Am4ig|FV zPi%1?-+`kj&ZvjNS_IH1@OcB+;`H3JA1t=_)9y|Bmiz70qj1LQytsoi@S9e8=7Ta) z1m#*N+)MF_?{?vPwlF8Ew1PJOi}7}r*YTt8xBF@F_BY$72fcliUT2s$u3?`2MZoc# zv}j$P{71~E(O+|_v*$qHTcXS#s^q>EH6feplX)JS7jO&ysNZjK@?j&H)is81n${K9 z#w!uQKegPB+&PE!KptC%j(4Xzuj!d@f3qAmHx^NUBViXMt-GS%!lIfr1IYFu-DeK(~u@-=O_B<+J1c?vizP+NkmX;lYa%;-B-sQ97 z?d@)VkJe9LDkW-e_d~!gILs*&hq*8-`nc*+F5OdZ0y^kQP#CR)YfIrYfAfJ z9;It#dH!3@81)J%^ zp5q=cyJafEcQ@Nw>3im(GT-%l>c$$&p}0ov4CfO2Y|qcOj-W+}N(&g9?N2E80)5~qxSWo zQI8&SDLTwDRjQE)y-E7igJ07DUZuQ1I;*fndl@Tdm4A-En>B=+YYB(e%nH7&j-$el z>~wxS<-D5j#*b`=ANeu5p}~(QoaJ*OKW3Qzm2_+E(rxkzx}9*|)QNPo;cKI?noi9%~tM9w%NuKP`t@{eP#TZ4*wIC6e=nOU8lRmJi zmc}3{H(BN6Zzejt{qLqHySwrhyh7eiIG^^r>B*qUk1>0No;=~~-tWba|p*9GBRn(zF=bppIgY9Es@^5yqcZ;*54(bXlHSDFJA{t8u9j~ z>{ewq$z*l_pBKn2St`KT=p+VRopgft^3@o{DHYb?3C0;wo>%}rl)&!LGiff=sj;Cy6|+=!`OMP_8xKMiV&nU;jhfWDoo$u2*4Uo48R)Ol1Ca(G&?Y6qZkJc;J z;^4;$f7BW}qnOhgpyg3+qrxp(BWEhE!)DXo;WTIX`|zW+vlbV>YAw#ESPyKPLA8I* z6=s2ERBHuTge8LN5<+LXozvGo{zOdAD)W6PkGGLO*U$45O!y1q5c(T>U7W|aZ?ex5S zzr#CFvW$DM={l`HOgZu;j<4)Pd(}Ky&0Mh_)v#PsuBh-mkN6Ba5Bef#0cRwzO_Wmt zU&B;u@x^cvO6Mt^XYl0Gf`0~n3(bDHm#Kb+x)pbc$LCa|RFM?-dU5^k`XklA?ChaI z_M0^3hH5k%cR@+{5%Wblx22qtr0$ zVU>U5TP@uqvYmT8Q`x_Ba{gla``70%=Dd*2p94SIjna9J&HmWCx$c$c7<{PLZGN|J zF4;v|4`3gkXLCuh;Y-N1zw92E=ag3{j*;q-Z2qb`0=+tl6b5-UzpI+Cz z&eOtq3Qe82Fn6EKz8XqJ&*J2DopInS;ZdGjyQExhRYns<_=M5uJgRY44;sG1Pz!*) z-Go)+IJG@Ad-nN~{iC>JcD7p@C)%<30kr(cZzn0Hu>$Cw0PQU(KX^0LXn%&=O>KdZ#$PIVT-E)c=DvMHB+sgAWal2`<8NZP zCz`JvtW)T?ynYo+Xf2|zf(KOEpGCS7{N5JP1iF%Pp&ZkfB9^On{b+i0~eHyzDlW;7SgI`9fEys!a z>x0`K%x{c9iTm>6cGx=`+$M{wUsQAcc+6HOZb%Eqfgrh{BGfE=lYLWd^nFU<-3dPxAEPLd}}@~9`w1*FFe`pnX~C1 zzkamsD{bYyd3LYl^F99%F!kn_cbCRWzcL>^e>T2#I=^0@|1zSER66ngZ5)Na{eJ)e Q0RR630FyzBo{hW!02?=hiU0rr literal 0 HcmV?d00001