Project

General

Profile

Download (37.2 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * pkg-utils.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2005-2006 Colin Smith (ethethlay@gmail.com)
7
 * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
8
 * All rights reserved.
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions are met:
12
 *
13
 * 1. Redistributions of source code must retain the above copyright notice,
14
 *    this list of conditions and the following disclaimer.
15
 *
16
 * 2. Redistributions in binary form must reproduce the above copyright
17
 *    notice, this list of conditions and the following disclaimer in
18
 *    the documentation and/or other materials provided with the
19
 *    distribution.
20
 *
21
 * 3. All advertising materials mentioning features or use of this software
22
 *    must display the following acknowledgment:
23
 *    "This product includes software developed by the pfSense Project
24
 *    for use in the pfSense® software distribution. (http://www.pfsense.org/).
25
 *
26
 * 4. The names "pfSense" and "pfSense Project" must not be used to
27
 *    endorse or promote products derived from this software without
28
 *    prior written permission. For written permission, please contact
29
 *    coreteam@pfsense.org.
30
 *
31
 * 5. Products derived from this software may not be called "pfSense"
32
 *    nor may "pfSense" appear in their names without prior written
33
 *    permission of the Electric Sheep Fencing, LLC.
34
 *
35
 * 6. Redistributions of any form whatsoever must retain the following
36
 *    acknowledgment:
37
 *
38
 * "This product includes software developed by the pfSense Project
39
 * for use in the pfSense software distribution (http://www.pfsense.org/).
40
 *
41
 * THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
42
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
44
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
45
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
47
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
48
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
50
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
51
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
52
 * OF THE POSSIBILITY OF SUCH DAMAGE.
53
 */
54

    
55
require_once("globals.inc");
56
require_once("service-utils.inc");
57

    
58
if (file_exists("/cf/conf/use_xmlreader")) {
59
	require_once("xmlreader.inc");
60
} else {
61
	require_once("xmlparse.inc");
62
}
63

    
64
require_once("pfsense-utils.inc");
65

    
66
if (!function_exists("pkg_debug")) {
67
	/* set up logging if needed */
68
	function pkg_debug($msg) {
69
		global $g, $debug, $fd_log;
70

    
71
		if (!$debug) {
72
			return;
73
		}
74

    
75
		if (!$fd_log) {
76
			if (!$fd_log = fopen("{$g['tmp_path']}/pkg_mgr_debug.log", "w")) {
77
				update_status(gettext("Warning, could not open log for writing.") . "\n");
78
			}
79
		}
80
		@fwrite($fd_log, $msg);
81
	}
82
}
83

    
84
/* Validate if pkg name is valid */
85
function pkg_valid_name($pkgname) {
86
	global $g;
87

    
88
	$pattern = "/^{$g['pkg_prefix']}[a-zA-Z0-9\.\-_]+$/";
89
	return preg_match($pattern, $pkgname);
90
}
91

    
92
/* Remove pkg_prefix from package name if it's present */
93
function pkg_remove_prefix(&$pkg_name) {
94
	global $g;
95

    
96
	if (substr($pkg_name, 0, strlen($g['pkg_prefix'])) == $g['pkg_prefix']) {
97
		$pkg_name = substr($pkg_name, strlen($g['pkg_prefix']));
98
	}
99
}
100

    
101
/* Execute pkg update when it's necessary */
102
function pkg_update($force = false) {
103
	global $g;
104

    
105
	return pkg_call("update" . ($force ? " -f" : ""));
106
}
107

    
108
/* return an array with necessary environment vars for pkg */
109
function pkg_env($extra_env = array()) {
110
	global $config, $g;
111

    
112
	$user_agent = $g['product_name'] . '/' . $g['product_version'];
113
	if (!isset($config['system']['do_not_send_uniqueid'])) {
114
		$user_agent .= ':' . system_get_uniqueid();
115
	}
116

    
117
	$pkg_env_vars = array(
118
		"LANG" => "C",
119
		"HTTP_USER_AGENT" => $user_agent,
120
		"ASSUME_ALWAYS_YES" => "true",
121
		"FETCH_TIMEOUT" => 5,
122
		"FETCH_RETRY" => 2
123
	);
124

    
125
	if (!empty($config['system']['proxyurl'])) {
126
		$http_proxy = $config['system']['proxyurl'];
127
		if (!empty($config['system']['proxyport'])) {
128
			$http_proxy .= ':' . $config['system']['proxyport'];
129
		}
130
		$pkg_env_vars['HTTP_PROXY'] = $http_proxy;
131
	}
132

    
133
	if ($g['platform'] == "nanobsd" ||
134
	    isset($config['system']['use_mfs_tmpvar'])) {
135
		$pkg_env_vars['PKG_DBDIR'] = '/root/var/db/pkg';
136
		$pkg_env_vars['PKG_CACHEDIR'] = '/root/var/cache/pkg';
137
	}
138

    
139
	foreach ($extra_env as $key => $value) {
140
		$pkg_env_vars[$key] = $value;
141
	}
142

    
143
	return $pkg_env_vars;
144
}
145

    
146
/* Execute a pkg call */
147
function pkg_call($params, $mute = false, $extra_env = array()) {
148
	global $g, $config;
149

    
150
	if (empty($params)) {
151
		return false;
152
	}
153

    
154
	$descriptorspec = array(
155
		1 => array("pipe", "w"), /* stdout */
156
		2 => array("pipe", "w")	 /* stderr */
157
	);
158

    
159
	conf_mount_rw();
160

    
161
	pkg_debug("pkg_call(): {$params}\n");
162
	$process = proc_open("/usr/sbin/pkg {$params}", $descriptorspec, $pipes,
163
	    '/', pkg_env($extra_env));
164

    
165
	if (!is_resource($process)) {
166
		conf_mount_ro();
167
		return false;
168
	}
169

    
170
	stream_set_blocking($pipes[1], 0);
171
	stream_set_blocking($pipes[2], 0);
172

    
173
	/* XXX: should be a tunnable? */
174
	$timeout = 60; // seconds
175
	$error_log = '';
176

    
177
	do {
178
		$write = array();
179
		$read = array($pipes[1], $pipes[2]);
180
		$except = array();
181

    
182
		$stream = stream_select($read, $write, $except, $timeout);
183
		if ($stream !== FALSE && $stream > 0) {
184
			foreach ($read as $pipe) {
185
				$content = stream_get_contents($pipe);
186
				if ($content == '') {
187
					continue;
188
				}
189
				if ($pipe === $pipes[1]) {
190
					if (!$mute) {
191
						update_status($content);
192
					}
193
					flush();
194
				} else if ($pipe === $pipes[2]) {
195
					$error_log .= $content;
196
				}
197
			}
198
		}
199

    
200
		$status = proc_get_status($process);
201
	} while ($status['running']);
202

    
203
	fclose($pipes[1]);
204
	fclose($pipes[2]);
205
	proc_close($process);
206

    
207
	conf_mount_ro();
208

    
209
	$rc = $status['exitcode'];
210

    
211
	pkg_debug("pkg_call(): rc = {$rc}\n");
212
	if ($rc == 0) {
213
		return true;
214
	}
215

    
216
	pkg_debug("pkg_call(): error_log\n{$error_log}\n");
217
	if (!$mute) {
218
		update_status("\n\n" .  sprintf(gettext(
219
		    "ERROR!!! An error occurred on pkg execution (rc = %d) with parameters '%s':"),
220
		    $rc, $params) . "\n" . $error_log . "\n");
221
	}
222

    
223
	return false;
224
}
225

    
226
/* Execute pkg with $params, fill stdout and stderr and return pkg rc */
227
function pkg_exec($params, &$stdout, &$stderr, $extra_env = array()) {
228
	global $g, $config;
229

    
230
	if (empty($params)) {
231
		return -1;
232
	}
233

    
234
	$descriptorspec = array(
235
		1 => array("pipe", "w"), /* stdout */
236
		2 => array("pipe", "w")	 /* stderr */
237
	);
238

    
239
	conf_mount_rw();
240

    
241
	pkg_debug("pkg_exec(): {$params}\n");
242
	$process = proc_open("/usr/sbin/pkg {$params}", $descriptorspec, $pipes,
243
	    '/', pkg_env($extra_env));
244

    
245
	if (!is_resource($process)) {
246
		conf_mount_ro();
247
		return -1;
248
	}
249

    
250
	$stdout = '';
251
	while (($l = fgets($pipes[1])) !== FALSE) {
252
		$stdout .= $l;
253
	}
254
	fclose($pipes[1]);
255

    
256
	$stderr = '';
257
	while (($l = fgets($pipes[2])) !== FALSE) {
258
		$stderr .= $l;
259
	}
260
	fclose($pipes[2]);
261

    
262
	conf_mount_ro();
263

    
264
	return proc_close($process);
265
}
266

    
267
/* Compare 2 pkg versions and return:
268
 * '=' - versions are the same
269
 * '>' - $v1 > $v2
270
 * '<' - $v1 < $v2
271
 * '?' - Error
272
 */
273
function pkg_version_compare($v1, $v2) {
274
	if (empty($v1) || empty($v2)) {
275
		return '?';
276
	}
277

    
278
	$rc = pkg_exec("version -t '{$v1}' '{$v2}'", $stdout, $stderr);
279

    
280
	if ($rc != 0) {
281
		return '?';
282
	}
283

    
284
	return str_replace("\n", "", $stdout);
285
}
286

    
287
/* Check if package is installed */
288
function is_pkg_installed($pkg_name) {
289
	global $g;
290

    
291
	if (empty($pkg_name)) {
292
		return false;
293
	}
294

    
295
	return pkg_call("info -e " . $pkg_name, true);
296
}
297

    
298
/* Install package, $pkg_name should not contain prefix */
299
function pkg_install($pkg_name, $force = false) {
300
	global $g;
301
	$result = false;
302

    
303
	$shortname = $pkg_name;
304
	pkg_remove_prefix($shortname);
305

    
306
	$pkg_force = "";
307
	if ($force) {
308
		$pkg_force = "-f ";
309
	}
310

    
311
	pkg_debug("Installing package {$shortname}\n");
312
	if ($force || !is_pkg_installed($pkg_name)) {
313
		$result = pkg_call("install -y " . $pkg_force . $pkg_name);
314
		/* Cleanup cacke to free disk space */
315
		pkg_call("clean -y");
316
	}
317

    
318
	return $result;
319
}
320

    
321
/* Delete package from FreeBSD, $pkg_name should not contain prefix */
322
function pkg_delete($pkg_name) {
323
	global $g;
324

    
325
	$shortname = $pkg_name;
326
	pkg_remove_prefix($shortname);
327

    
328
	pkg_debug("Removing package {$shortname}\n");
329
	if (is_pkg_installed($pkg_name)) {
330
		pkg_call("delete -y " . $pkg_name);
331
		/* Cleanup unecessary dependencies */
332
		pkg_call("autoremove -y");
333
	}
334
}
335

    
336
/* Check if package is present in config.xml */
337
function is_package_installed($package_name) {
338
	return (get_package_id($package_name) != -1);
339
}
340

    
341
/* Find package array index */
342
function get_package_id($package_name) {
343
	global $config;
344

    
345
	if (!is_array($config['installedpackages']['package'])) {
346
		return -1;
347
	}
348

    
349
	foreach ($config['installedpackages']['package'] as $idx => $pkg) {
350
		if ($pkg['name'] == $package_name ||
351
		    get_package_internal_name($pkg) == $package_name) {
352
			return $idx;
353
		}
354
	}
355

    
356
	return -1;
357
}
358

    
359
/* Return internal_name when it's defined, otherwise, returns name */
360
function get_package_internal_name($package_data) {
361
	if (isset($package_data['internal_name']) && ($package_data['internal_name'] != "")) {
362
		/* e.g. name is Ipguard-dev, internal name is ipguard */
363
		return $package_data['internal_name'];
364
	} else {
365
		return $package_data['name'];
366
	}
367
}
368

    
369
// Get information about packages.
370
function get_pkg_info($pkgs = 'all', $remote_repo_usage_disabled = false,
371
    $installed_pkgs_only = false) {
372
	global $g, $input_errors;
373

    
374
	$out = $err = $extra_param = '';
375
	$rc = 0;
376

    
377
	unset($pkg_filter);
378

    
379
	if (is_array($pkgs)) {
380
		$pkg_filter = $pkgs;
381
		$pkgs = $g['pkg_prefix'] . '*';
382
	} elseif ($pkgs == 'all') {
383
		$pkgs = $g['pkg_prefix'] . '*';
384
	}
385

    
386
	
387
	if (!function_exists('is_subsystem_dirty')) {
388
		require_once("util.inc");
389
	}
390

    
391
	/* Do not run remote operations if pkg has a lock */
392
	if (is_subsystem_dirty('pkg')) {
393
		$remote_repo_usage_disabled = true;
394
		$lock = false;
395
	} else {
396
		$lock = true;
397
	}
398

    
399
	if ($lock) {
400
		mark_subsystem_dirty('pkg');
401
	}
402

    
403
	if ($remote_repo_usage_disabled) {
404
		$extra_param = "-U ";
405
	}
406

    
407
	if (!$installed_pkgs_only) {
408
		$rc = pkg_exec(
409
		    "search {$extra_param}-R --raw-format json-compact " .
410
		    $pkgs, $out, $err);
411
	}
412
	if (($installed_pkgs_only || ($rc != 0 && $remote_repo_usage_disabled))
413
	    && is_pkg_installed($pkgs)) {
414
		/*
415
		 * Fall back on pkg info to return locally installed matching
416
		 * pkgs instead, if:
417
		 *
418
		 *   (1) only installed pkgs needed, or
419
		 *       we tried to check the local catalog copy (implying that
420
		 *       we would have accepted incomplete/outdated pkg info)
421
		 *       but it didn't have any contents, or for other reasons
422
		 *       returned an error.
423
		 *   AND
424
		 *   (2) at least some pkgs matching <pattern> are installed
425
		 *
426
		 * Following an unsuccessful attempt to access a remote repo
427
		 * catalog, the local copy is wiped clear. Thereafter any
428
		 * "pkg search" will return an error until online+updated again.
429
		 * If the calling code would have accepted local copy info
430
		 * (which could be incomplete/out of date), then it makes sense
431
		 * to fall back on pkg info to at least return the known
432
		 * info about installed pkgs (pkg info should still work),
433
		 * instead of failing and returning no info at all. 
434
		 * For example, this at least enables offline view + management
435
		 * of installed pkgs in GUI/console.
436
		 *
437
		 * We skip this step if no matching pkgs are installed, because
438
		 * then pkg info would return a "no matching pkgs" RC code,
439
		 * even though this wouldn't be considered an "error" (and
440
		 * $out+$err would be correct empty strings if none match).
441
		 *
442
		 * Note that is_pkg_installed() is a wrapper for pkg info -e
443
		 * <pattern> which is what we need here.
444
		*/
445
		
446
		// ok, 1 or more packages match, so pkg info can be safely called to get the pkg list  
447
		$rc = pkg_exec("info -R --raw-format json-compact " . $pkgs,
448
		    $out, $err);
449
	}
450

    
451
	if ($lock) {
452
		clear_subsystem_dirty('pkg');
453
	}
454

    
455
	if ($rc != 0) {
456
		update_status("\n" . gettext(
457
		    "ERROR: Error trying to get packages list. Aborting...")
458
		    . "\n");
459
		update_status($err);
460
		$input_errors[] = gettext(
461
		    "ERROR: Error trying to get packages list. Aborting...") .
462
		    "\n";
463
		$input_errors[] = $err;
464
		return array();
465
	}
466

    
467
	$result = array();
468
	$pkgs_info = explode("\n", $out);
469
	foreach ($pkgs_info as $pkg_info_json) {
470
		$pkg_info = json_decode($pkg_info_json, true);
471
		if (!isset($pkg_info['name'])) {
472
			continue;
473
		}
474

    
475
		if (isset($pkg_filter) && !in_array($pkg_info['name'],
476
		    $pkg_filter)) {
477
			continue;
478
		}
479

    
480
		$pkg_info['shortname'] = $pkg_info['name'];
481
		pkg_remove_prefix($pkg_info['shortname']);
482

    
483
		/* XXX: Add it to globals.inc? */
484
		$pkg_info['changeloglink'] =
485
		    "https://github.com/pfsense/FreeBSD-ports/commits/devel/" .
486
		    $pkg_info['categories'][0] . '/' . $pkg_info['name'];
487

    
488
		if (is_pkg_installed($pkg_info['name'])) {
489
			$pkg_info['installed'] = true;
490

    
491
			$rc = pkg_exec("query %v {$pkg_info['name']}", $out,
492
			    $err);
493

    
494
			if ($rc != 0) {
495
				update_status("\n" . gettext(
496
				    "ERROR: Error trying to get package version. Aborting...")
497
				    . "\n");
498
				update_status($err);
499
				$input_errors[] = gettext(
500
				    "ERROR: Error trying to get package version. Aborting...") .
501
				    "\n";
502
				$input_errors[] = $err;
503
				return array();
504
			}
505

    
506
			$pkg_info['installed_version'] = str_replace("\n", "",
507
			    $out);
508
		} else if (is_package_installed($pkg_info['shortname'])) {
509
			$pkg_info['broken'] = true;
510
		}
511

    
512
		$pkg_info['desc'] = preg_replace('/\n+WWW:.*$/', '',
513
		    $pkg_info['desc']);
514

    
515
		$result[] = $pkg_info;
516
		unset($pkg_info);
517
	}
518

    
519
	/* Sort result alphabetically */
520
	usort($result, function($a, $b) {
521
		return(strcasecmp ($a['name'], $b['name']));
522
	});
523

    
524
	return $result;
525
}
526

    
527
/*
528
 * If binary pkg is installed but post-install tasks were not
529
 * executed yet, do it now.
530
 * This scenario can happen when a pkg is pre-installed during
531
 * build phase, and at this point, cannot find a running system
532
 * to register itself in config.xml and also execute custom
533
 * install functions
534
 */
535
function register_all_installed_packages() {
536
	global $g, $config, $pkg_interface;
537

    
538
	$pkg_info = get_pkg_info('all', true, true);
539

    
540
	foreach ($pkg_info as $pkg) {
541
		pkg_remove_prefix($pkg['name']);
542

    
543
		if (is_package_installed($pkg['name'])) {
544
			continue;
545
		}
546

    
547
		update_status(sprintf(gettext(
548
		    "Running last steps of %s installation.") . "\n",
549
		    $pkg['name']));
550
		install_package_xml($pkg['name']);
551
	}
552
}
553

    
554
/*
555
 * resync_all_package_configs() Force packages to setup their configuration and rc.d files.
556
 * This function may also print output to the terminal indicating progress.
557
 */
558
function resync_all_package_configs($show_message = false) {
559
	global $config, $pkg_interface, $g;
560

    
561
	log_error(gettext("Resyncing configuration for all packages."));
562

    
563
	if (!is_array($config['installedpackages']['package'])) {
564
		return;
565
	}
566

    
567
	if ($show_message == true) {
568
		echo "Syncing packages:";
569
	}
570

    
571
	conf_mount_rw();
572

    
573
	foreach ($config['installedpackages']['package'] as $idx => $package) {
574
		if (empty($package['name'])) {
575
			continue;
576
		}
577
		if ($show_message == true) {
578
			echo " " . $package['name'];
579
		}
580
		if (platform_booting() != true) {
581
			stop_service(get_package_internal_name($package));
582
		}
583
		sync_package($package['name']);
584
		update_status(gettext("Syncing packages...") . "\n");
585
	}
586

    
587
	if ($show_message == true) {
588
		echo " done.\n";
589
	}
590

    
591
	@unlink("/conf/needs_package_sync");
592
	conf_mount_ro();
593
}
594

    
595
function uninstall_package($package_name) {
596
	global $config;
597

    
598
	$internal_name = $package_name;
599
	$id = get_package_id($package_name);
600
	if ($id >= 0) {
601
		$internal_name = get_package_internal_name($config['installedpackages']['package'][$id]);
602
		stop_service($internal_name);
603
	}
604
	$pkg_name = $g['pkg_prefix'] . $internal_name;
605

    
606
	if (is_pkg_installed($pkg_name)) {
607
		update_status(gettext("Removing package...") . "\n");
608
		pkg_delete($pkg_name);
609
	} else {
610
		delete_package_xml($package_name);
611
	}
612

    
613
	update_status(gettext("done.") . "\n");
614
}
615

    
616
/* Run <custom_php_resync_config_command> */
617
function sync_package($package_name) {
618
	global $config, $builder_package_install;
619

    
620
	// If this code is being called by pfspkg_installer
621
	// which the builder system uses then return (ignore).
622
	if ($builder_package_install) {
623
		return;
624
	}
625

    
626
	if (empty($config['installedpackages']['package'])) {
627
		return;
628
	}
629

    
630
	if (($pkg_id = get_package_id($package_name)) == -1) {
631
		return; // This package doesn't really exist - exit the function.
632
	}
633

    
634
	if (!is_array($config['installedpackages']['package'][$pkg_id])) {
635
		return;	 // No package belongs to the pkg_id passed to this function.
636
	}
637

    
638
	$package =& $config['installedpackages']['package'][$pkg_id];
639
	if (!file_exists("/usr/local/pkg/" . $package['configurationfile'])) {
640
		log_error(sprintf(gettext("The %s package is missing its configuration file and must be reinstalled."), $package['name']));
641
		delete_package_xml($package['name']);
642
		return;
643
	}
644

    
645
	$pkg_config = parse_xml_config_pkg("/usr/local/pkg/" . $package['configurationfile'], "packagegui");
646
	if (isset($pkg_config['nosync'])) {
647
		return;
648
	}
649

    
650
	/* Bring in package include files */
651
	if (!empty($pkg_config['include_file'])) {
652
		$include_file = $pkg_config['include_file'];
653
		if (file_exists($include_file)) {
654
			require_once($include_file);
655
		} else {
656
			log_error(sprintf(gettext('Reinstalling package %1$s because its include file(%2$s) is missing!'), $package['name'], $include_file));
657
			uninstall_package($package['name']);
658
			if (install_package($package['name']) != 0) {
659
				log_error(sprintf(gettext("Reinstalling package %s failed. Take appropriate measures!!!"), $package['name']));
660
				return;
661
			}
662
			if (file_exists($include_file)) {
663
				require_once($include_file);
664
			} else {
665
				return;
666
			}
667
		}
668
	}
669

    
670
	if (!empty($pkg_config['custom_php_global_functions'])) {
671
		eval($pkg_config['custom_php_global_functions']);
672
	}
673
	if (!empty($pkg_config['custom_php_resync_config_command'])) {
674
		eval($pkg_config['custom_php_resync_config_command']);
675
	}
676
}
677

    
678
/* Read info.xml installed by package and return an array */
679
function read_package_config($package_name) {
680
	global $g;
681

    
682
	$pkg_info_xml = '/usr/local/share/' . $g['pkg_prefix'] . $package_name . '/info.xml';
683

    
684
	if (!file_exists($pkg_info_xml)) {
685
		return false;
686
	}
687

    
688
	$pkg_info = parse_xml_config_pkg($pkg_info_xml, 'pfsensepkgs');
689

    
690
	if (empty($pkg_info)) {
691
		return false;
692
	}
693

    
694
	/* it always returns an array with 1 item */
695
	return $pkg_info['package'][0];
696
}
697

    
698
/* Read package configurationfile and return an array */
699
function read_package_configurationfile($package_name) {
700
	global $config, $g;
701

    
702
	$pkg_config = array();
703
	$id = get_package_id($package_name);
704

    
705
	if ($id < 0 || !isset($config['installedpackages']['package'][$id]['configurationfile'])) {
706
		return $pkg_config;
707
	}
708

    
709
	$pkg_configurationfile = $config['installedpackages']['package'][$id]['configurationfile'];
710

    
711
	if (empty($pkg_configurationfile) || !file_exists('/usr/local/pkg/' . $pkg_configurationfile)) {
712
		return $pkg_config;
713
	}
714

    
715
	$pkg_config = parse_xml_config_pkg('/usr/local/pkg/' . $pkg_configurationfile, "packagegui");
716

    
717
	return $pkg_config;
718
}
719

    
720
function get_after_install_info($package_name) {
721
	$pkg_config = read_package_config($package_name);
722

    
723
	if (isset($pkg_config['after_install_info'])) {
724
		return $pkg_config['after_install_info'];
725
	}
726

    
727
	return '';
728
}
729

    
730
function eval_once($toeval) {
731
	global $evaled;
732
	if (!$evaled) {
733
		$evaled = array();
734
	}
735
	$evalmd5 = md5($toeval);
736
	if (!in_array($evalmd5, $evaled)) {
737
		@eval($toeval);
738
		$evaled[] = $evalmd5;
739
	}
740
	return;
741
}
742

    
743
function install_package_xml($package_name) {
744
	global $g, $config, $pkg_interface;
745

    
746
	if (($pkg_info = read_package_config($package_name)) == false) {
747
		return false;
748
	}
749

    
750
	/* safe side. Write config below will send to ro again. */
751
	conf_mount_rw();
752

    
753
	pkg_debug(gettext("Beginning package installation.") . "\n");
754
	log_error(sprintf(gettext('Beginning package installation for %s .'), $pkg_info['name']));
755

    
756
	/* add package information to config.xml */
757
	$pkgid = get_package_id($pkg_info['name']);
758
	update_status(gettext("Saving updated package information...") . "\n");
759
	if ($pkgid == -1) {
760
		$config['installedpackages']['package'][] = $pkg_info;
761
		$changedesc = sprintf(gettext("Installed %s package."), $pkg_info['name']);
762
		$to_output = gettext("done.") . "\n";
763
	} else {
764
		$config['installedpackages']['package'][$pkgid] = $pkg_info;
765
		$changedesc = sprintf(gettext("Overwrote previous installation of %s."), $pkg_info['name']);
766
		$to_output = gettext("overwrite!") . "\n";
767
	}
768
	unlink_if_exists('/conf/needs_package_sync');
769
	write_config(sprintf(gettext("Intermediate config write during package install for %s."), $pkg_info['name']));
770
	conf_mount_ro();
771
	update_status($to_output);
772

    
773
	if (($pkgid = get_package_id($package_name)) == -1) {
774
		update_status(sprintf(gettext("The %s package is not installed.%sInstallation aborted."), $package_name, "\n\n"));
775

    
776
		uninstall_package($package_name);
777
		write_config($changedesc);
778
		log_error(sprintf(gettext("Failed to install package: %s."), $pkg_info['name']));
779
		update_status(gettext("Failed to install package.") . "\n");
780
		return false;
781
	}
782

    
783
	if (file_exists("/usr/local/pkg/" . $pkg_info['configurationfile'])) {
784
		update_status(gettext("Loading package configuration... "));
785
		$pkg_config = parse_xml_config_pkg("/usr/local/pkg/" . $pkg_info['configurationfile'], "packagegui");
786
		update_status(gettext("done.") . "\n");
787
		update_status(gettext("Configuring package components...") . "\n");
788
		if (!empty($pkg_config['filter_rules_needed'])) {
789
			$config['installedpackages']['package'][$pkgid]['filter_rule_function'] = $pkg_config['filter_rules_needed'];
790
		}
791
		/* modify system files */
792

    
793
		/* if a require exists, include it.  this will
794
		 * show us where an error exists in a package
795
		 * instead of making us blindly guess
796
		 */
797
		$missing_include = false;
798
		if ($pkg_config['include_file'] <> "") {
799
			update_status(gettext("Loading package instructions...") . "\n");
800
			if (file_exists($pkg_config['include_file'])) {
801
				pkg_debug("require_once('{$pkg_config['include_file']}')\n");
802
				require_once($pkg_config['include_file']);
803
			} else {
804
				pkg_debug("Missing include {$pkg_config['include_file']}\n");
805
				$missing_include = true;
806
				update_status(sprintf(gettext("Include %s is missing!"), basename($pkg_config['include_file'])) . "\n");
807

    
808
				uninstall_package($package_name);
809
				write_config($changedesc);
810
				log_error(sprintf(gettext("Failed to install package: %s."), $pkg_info['name']));
811
				update_status(gettext("Failed to install package.") . "\n");
812
				return false;
813
			}
814
		}
815

    
816
		/* custom commands */
817
		update_status(gettext("Custom commands...") . "\n");
818
		if ($missing_include == false) {
819
			if ($pkg_config['custom_php_global_functions'] <> "") {
820
				update_status(gettext("Executing custom_php_global_functions()..."));
821
				eval_once($pkg_config['custom_php_global_functions']);
822
				update_status(gettext("done.") . "\n");
823
			}
824
			if ($pkg_config['custom_php_install_command']) {
825
				update_status(gettext("Executing custom_php_install_command()..."));
826
				eval_once($pkg_config['custom_php_install_command']);
827
				update_status(gettext("done.") . "\n");
828
			}
829
			if ($pkg_config['custom_php_resync_config_command'] <> "") {
830
				update_status(gettext("Executing custom_php_resync_config_command()..."));
831
				eval_once($pkg_config['custom_php_resync_config_command']);
832
				update_status(gettext("done.") . "\n");
833
			}
834
		}
835
		/* sidebar items */
836
		if (is_array($pkg_config['menu'])) {
837
			update_status(gettext("Menu items... "));
838
			foreach ($pkg_config['menu'] as $menu) {
839
				if (is_array($config['installedpackages']['menu'])) {
840
					foreach ($config['installedpackages']['menu'] as $amenu) {
841
						if ($amenu['name'] == $menu['name']) {
842
							continue 2;
843
						}
844
					}
845
				} else {
846
					$config['installedpackages']['menu'] = array();
847
				}
848
				$config['installedpackages']['menu'][] = $menu;
849
			}
850
			update_status(gettext("done.") . "\n");
851
		}
852
		/* services */
853
		if (is_array($pkg_config['service'])) {
854
			update_status(gettext("Services... "));
855
			foreach ($pkg_config['service'] as $service) {
856
				if (is_array($config['installedpackages']['service'])) {
857
					foreach ($config['installedpackages']['service'] as $aservice) {
858
						if ($aservice['name'] == $service['name']) {
859
							continue 2;
860
						}
861
					}
862
				} else {
863
					$config['installedpackages']['service'] = array();
864
				}
865
				$config['installedpackages']['service'][] = $service;
866
			}
867
			update_status(gettext("done.") . "\n");
868
		}
869
 		if (is_array($pkg_config['tabs'])) {
870
 			$config['installedpackages']['package'][$pkgid]['tabs'] = $pkg_config['tabs'];			
871
 		}
872
	} else {
873
		pkg_debug("Unable to find config file\n");
874
		update_status(gettext("Loading package configuration... failed!") . "\n\n" . gettext("Installation aborted."));
875
		pkg_debug(gettext("Unable to load package configuration. Installation aborted.") ."\n");
876

    
877
		uninstall_package($package_name);
878
		write_config($changedesc);
879
		log_error(sprintf(gettext("Failed to install package: %s."), $pkg_info['name']));
880
		update_status(gettext("Failed to install package.") . "\n");
881
		return false;
882
	}
883

    
884
	update_status(gettext("Writing configuration... "));
885
	write_config($changedesc);
886
	log_error(sprintf(gettext("Successfully installed package: %s."), $pkg_info['name']));
887
	update_status(gettext("done.") . "\n");
888
	if ($pkg_info['after_install_info']) {
889
		update_status($pkg_info['after_install_info']);
890
	}
891

    
892
	/* set up package logging streams */
893
	if ($pkg_info['logging']) {
894
		system_syslogd_start(true);
895
	}
896

    
897
	return true;
898
}
899

    
900
function delete_package_xml($package_name, $when = "post-deinstall") {
901
	global $g, $config, $pkg_interface;
902

    
903
	conf_mount_rw();
904

    
905
	$pkgid = get_package_id($package_name);
906
	if ($pkgid == -1) {
907
		update_status(sprintf(gettext("The %s package is not installed.%sDeletion aborted."), $package_name, "\n\n"));
908
		ob_flush();
909
		sleep(1);
910
		conf_mount_ro();
911
		return;
912
	}
913
	pkg_debug(sprintf(gettext("Removing %s package... "), $package_name));
914
	update_status(sprintf(gettext("Removing %s components..."), $package_name) . "\n");
915
	/* parse package configuration */
916
	$packages = &$config['installedpackages']['package'];
917
	$menus =& $config['installedpackages']['menu'];
918
	$services = &$config['installedpackages']['service'];
919
	$pkg_info =& $packages[$pkgid];
920
	if (file_exists("/usr/local/pkg/" . $pkg_info['configurationfile'])) {
921
		$pkg_config = parse_xml_config_pkg("/usr/local/pkg/" . $packages[$pkgid]['configurationfile'], "packagegui");
922
		/* remove menu items */
923
		if (is_array($pkg_config['menu'])) {
924
			update_status(gettext("Menu items... "));
925
			if (is_array($pkg_config['menu']) && is_array($menus)) {
926
				foreach ($pkg_config['menu'] as $menu) {
927
					foreach ($menus as $key => $instmenu) {
928
						if ($instmenu['name'] == $menu['name']) {
929
							unset($menus[$key]);
930
							break;
931
						}
932
					}
933
				}
934
			}
935
			update_status(gettext("done.") . "\n");
936
		}
937
		/* remove services */
938
		if (is_array($pkg_config['service'])) {
939
			update_status(gettext("Services... "));
940
			if (is_array($pkg_config['service']) && is_array($services)) {
941
				foreach ($pkg_config['service'] as $service) {
942
					foreach ($services as $key => $instservice) {
943
						if ($instservice['name'] == $service['name']) {
944
							if (platform_booting() != true) {
945
								stop_service($service['name']);
946
							}
947
							if ($service['rcfile']) {
948
								$prefix = RCFILEPREFIX;
949
								if (!empty($service['prefix'])) {
950
									$prefix = $service['prefix'];
951
								}
952
								if (file_exists("{$prefix}{$service['rcfile']}")) {
953
									@unlink("{$prefix}{$service['rcfile']}");
954
								}
955
							}
956
							unset($services[$key]);
957
						}
958
					}
959
				}
960
			}
961
			update_status(gettext("done.") . "\n");
962
		}
963
		/*
964
		 * XXX: Otherwise inclusion of config.inc again invalidates actions taken.
965
		 *	Same is done during installation.
966
		 */
967
		write_config(sprintf(gettext("Intermediate config write during package removal for %s."), $package_name));
968

    
969
		/*
970
		 * If a require exists, include it.	 this will
971
		 * show us where an error exists in a package
972
		 * instead of making us blindly guess
973
		 */
974
		$missing_include = false;
975
		if ($pkg_config['include_file'] <> "") {
976
			update_status(gettext("Loading package instructions...") . "\n");
977
			if (file_exists($pkg_config['include_file'])) {
978
				pkg_debug("require_once(\"{$pkg_config['include_file']}\")\n");
979
				require_once($pkg_config['include_file']);
980
			} else {
981
				pkg_debug("Missing include {$pkg_config['include_file']}\n");
982
				$missing_include = true;
983
				update_status(sprintf(gettext("Include file %s could not be found for inclusion."), basename($pkg_config['include_file'])) . "\n");
984
			}
985
		}
986
		/* ermal
987
		 * NOTE: It is not possible to handle parse errors on eval.
988
		 * So we prevent it from being run at all to not interrupt all the other code.
989
		 */
990
		if ($when == "deinstall" && $missing_include == false) {
991
			/* evaluate this package's global functions and pre deinstall commands */
992
			if ($pkg_config['custom_php_global_functions'] <> "") {
993
				eval_once($pkg_config['custom_php_global_functions']);
994
			}
995
			if ($pkg_config['custom_php_pre_deinstall_command'] <> "") {
996
				eval_once($pkg_config['custom_php_pre_deinstall_command']);
997
			}
998
		}
999
		/* deinstall commands */
1000
		if ($when == "deinstall" && $pkg_config['custom_php_deinstall_command'] <> "") {
1001
			update_status(gettext("Deinstall commands... "));
1002
			if ($missing_include == false) {
1003
				eval_once($pkg_config['custom_php_deinstall_command']);
1004
				update_status(gettext("done.") . "\n");
1005
			} else {
1006
				update_status("\n". gettext("Not executing custom deinstall hook because an include is missing.") . "\n");
1007
			}
1008
		}
1009
	}
1010
	/* syslog */
1011
	$need_syslog_restart = false;
1012
	if (is_array($pkg_info['logging']) && $pkg_info['logging']['logfilename'] <> "") {
1013
		update_status(gettext("Syslog entries... "));
1014
		@unlink_if_exists("{$g['varlog_path']}/{$pkg_info['logging']['logfilename']}");
1015
		update_status("done.\n");
1016
		$need_syslog_restart = true;
1017
	}
1018

    
1019
	if ($when == "post-deinstall") {
1020
		/* remove config.xml entries */
1021
		update_status(gettext("Configuration... "));
1022
		unset($config['installedpackages']['package'][$pkgid]);
1023
		update_status(gettext("done.") . "\n");
1024
		write_config(sprintf(gettext("Removed %s package."), $package_name));
1025
		/* remove package entry from /etc/syslog.conf if needed */
1026
		/* this must be done after removing the entries from config.xml */
1027
		if ($need_syslog_restart) {
1028
			system_syslogd_start(true);
1029
		}
1030
	}
1031

    
1032
	conf_mount_ro();
1033
}
1034

    
1035
/*
1036
 * Used during upgrade process or retore backup process, verify all
1037
 * packages installed in config.xml and install pkg accordingly
1038
 */
1039
function package_reinstall_all() {
1040
	global $g, $config, $pkg_interface;
1041

    
1042
	$upgrade = (file_exists('/conf/needs_package_sync') && platform_booting());
1043

    
1044
	if ((!isset($config['installedpackages']['package']) ||
1045
	    !is_array($config['installedpackages']['package'])) && !$upgrade) {
1046
		return true;
1047
	}
1048

    
1049
	/* During boot after upgrade, wait for internet connection */
1050
	if ($upgrade) {
1051
		update_status(gettext("Waiting for Internet connection to update pkg metadata and finish package reinstallation"));
1052
		$ntries = 3;
1053
		while ($ntries > 0) {
1054
			if (pkg_update(true)) {
1055
				break;
1056
			}
1057
			update_status('.');
1058
			sleep(1);
1059
			$ntries--;
1060
		}
1061
		update_status("\n");
1062

    
1063
		if ($ntries == 0) {
1064
			file_notice(gettext("Package reinstall"),
1065
			    gettext("Package reinstall process was ABORTED due to lack of internet connectivity"));
1066
			return false;
1067
		}
1068
	}
1069

    
1070
	$pkg_info = get_pkg_info();
1071

    
1072
	if ($upgrade &&
1073
	    file_exists("{$g['cf_conf_path']}/packages_to_reinstall_after_upgrade.txt")) {
1074
		$package_list = file("{$g['cf_conf_path']}/packages_to_reinstall_after_upgrade.txt",
1075
		    FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1076
		unlink_if_exists("{$g['cf_conf_path']}/packages_to_reinstall_after_upgrade.txt");
1077
	} else {
1078
		if (!isset($config['installedpackages']['package']) || !is_array($config['installedpackages']['package'])) {
1079
			return true;
1080
		}
1081
		$package_list = array();
1082
		foreach ($config['installedpackages']['package'] as $package) {
1083
			$package_list[] = get_package_internal_name($package);
1084
		}
1085
	}
1086

    
1087
	foreach ($package_list as $package) {
1088
		$found = false;
1089
		foreach ($pkg_info as $pkg) {
1090
			pkg_remove_prefix($pkg['name']);
1091
			if ($pkg['name'] == $package) {
1092
				pkg_install($g['pkg_prefix'] . $package, true);
1093
				$found = true;
1094
				break;
1095
			}
1096
		}
1097

    
1098
		if (!$found) {
1099
			if (!function_exists("file_notice")) {
1100
				require_once("notices.inc");
1101
			}
1102

    
1103
			file_notice(gettext("Package reinstall"),
1104
			    sprintf(gettext("Package %s does not exist in current %s version and it has been removed."),
1105
			    $package, $g['product_name']));
1106
			uninstall_package($package);
1107
		}
1108
	}
1109

    
1110
	return true;
1111
}
1112

    
1113
function stop_packages() {
1114
	require_once("config.inc");
1115
	require_once("functions.inc");
1116
	require_once("filter.inc");
1117
	require_once("shaper.inc");
1118
	require_once("captiveportal.inc");
1119
	require_once("pkg-utils.inc");
1120
	require_once("pfsense-utils.inc");
1121
	require_once("service-utils.inc");
1122

    
1123
	global $config, $g;
1124

    
1125
	log_error(gettext("Stopping all packages."));
1126

    
1127
	$rcfiles = glob(RCFILEPREFIX . "*.sh");
1128
	if (!$rcfiles) {
1129
		$rcfiles = array();
1130
	} else {
1131
		$rcfiles = array_flip($rcfiles);
1132
		if (!$rcfiles) {
1133
			$rcfiles = array();
1134
		}
1135
	}
1136

    
1137
	if (is_array($config['installedpackages']['package'])) {
1138
		foreach ($config['installedpackages']['package'] as $package) {
1139
			echo " Stopping package {$package['name']}...";
1140
			$internal_name = get_package_internal_name($package);
1141
			stop_service($internal_name);
1142
			unset($rcfiles[RCFILEPREFIX . strtolower($internal_name) . ".sh"]);
1143
			echo "done.\n";
1144
		}
1145
	}
1146

    
1147
	foreach ($rcfiles as $rcfile => $number) {
1148
		$shell = @popen("/bin/sh", "w");
1149
		if ($shell) {
1150
			echo " Stopping {$rcfile}...";
1151
			if (!@fwrite($shell, "{$rcfile} stop >>/tmp/bootup_messages 2>&1")) {
1152
				if ($shell) {
1153
					pclose($shell);
1154
				}
1155
				$shell = @popen("/bin/sh", "w");
1156
			}
1157
			echo "done.\n";
1158
			pclose($shell);
1159
		}
1160
	}
1161
}
1162

    
1163
/* Identify which meta package is installed */
1164
function get_meta_pkg_name() {
1165
	global $g;
1166

    
1167
	/* XXX: Use pkg annotation */
1168
	if (is_pkg_installed($g['product_name'])) {
1169
		return $g['product_name'];
1170
	} else if (is_pkg_installed($g['product_name'] . '-vmware')) {
1171
		return $g['product_name'] . '-vmware';
1172
	}
1173
	return false;
1174
}
1175

    
1176
/* Identify which base package is installed */
1177
function get_base_pkg_name() {
1178
	global $g;
1179

    
1180
	/* XXX: Use pkg annotation */
1181
	if (is_pkg_installed($g['product_name'] . '-base-' . $g['platform'])) {
1182
		return $g['product_name'] . '-base-' . $g['platform'];
1183
	} else if (is_pkg_installed($g['product_name'] . '-base')) {
1184
		return $g['product_name'] . '-base';
1185
	}
1186
	return false;
1187
}
1188

    
1189
/* Verify if system needs upgrade (meta package or base) */
1190
function get_system_pkg_version($baseonly = false, $use_cache = true) {
1191
	global $g;
1192

    
1193
	$cache_file = $g['version_cache_file'];
1194
	$rc_file = $cache_file . '.rc';
1195

    
1196
	$rc = "";
1197
	if ($use_cache && file_exists($rc_file) &&
1198
	    (time()-filemtime($rc_file) < $g['version_cache_refresh'])) {
1199
		$rc = chop(@file_get_contents($rc_file));
1200
	}
1201

    
1202
	if ($rc == "2") {
1203
		$output = @file_get_contents($cache_file);
1204
	} else if ($rc != "0") {
1205
		$output = exec(
1206
		    "/usr/local/sbin/{$g['product_name']}-upgrade -c", $_gc,
1207
		    $rc);
1208

    
1209
		/* Update cache if it succeeded */
1210
		if ($rc == 0 || $rc == 2) {
1211
			@file_put_contents($cache_file, $output);
1212
			@file_put_contents($rc_file, $rc);
1213
		}
1214
	}
1215

    
1216
	/* pfSense-upgrade returns 2 when there is a new version */
1217
	if ($rc == "2") {
1218
		$new_version = explode(' ', $output)[0];
1219
	}
1220

    
1221
	$base_pkg = get_base_pkg_name();
1222
	$meta_pkg = get_meta_pkg_name();
1223

    
1224
	if (!$base_pkg || !$meta_pkg) {
1225
		return false;
1226
	}
1227

    
1228
	$info = get_pkg_info($base_pkg, true, true);
1229

    
1230
	$pkg_info = array();
1231
	foreach ($info as $item) {
1232
		if ($item['name'] == $base_pkg) {
1233
			$pkg_info = $item;
1234
			break;
1235
		}
1236
	}
1237

    
1238
	if (empty($pkg_info) || (!$baseonly && ($pkg_info['version'] ==
1239
	    $pkg_info['installed_version']))) {
1240
		$info = get_pkg_info($meta_pkg, true, true);
1241

    
1242
		foreach ($info as $item) {
1243
			if ($item['name'] == $meta_pkg) {
1244
				$pkg_info = $item;
1245
				break;
1246
			}
1247
		}
1248
	}
1249

    
1250
	if (empty($pkg_info)) {
1251
		return false;
1252
	}
1253

    
1254
	return array(
1255
	    'version'           => $new_version ?: $pkg_info['version'],
1256
	    'installed_version' => $pkg_info['installed_version']
1257
	);
1258
}
1259

    
1260
/* List available repos */
1261
function pkg_list_repos() {
1262
	global $g;
1263

    
1264
	$path = "/usr/local/share/{$g['product_name']}/pkg/repos";
1265

    
1266
	$default_descr = @file_get_contents($path . "/{$g['product_name']}-repo.descr");
1267

    
1268
	$default = array(
1269
	    'name' => 'Default',
1270
	    'path' => $path . "/{$g['product_name']}-repo.conf",
1271
	    'descr' => $default_descr
1272
	);
1273

    
1274
	$result = array($default);
1275

    
1276
	$conf_files = glob("{$path}/{$g['product_name']}-repo-*.conf");
1277
	foreach ($conf_files as $conf_file) {
1278
		$descr_file = preg_replace('/.conf$/', '.descr', $conf_file);
1279
		if (file_exists($descr_file)) {
1280
			$descr_content = file($descr_file);
1281
			$descr = chop($descr_content[0]);
1282
		} else {
1283
			$descr = 'Unknown';
1284
		}
1285
		if (!preg_match('/-repo-(.*).conf/', $conf_file, $matches)) {
1286
			continue;
1287
		}
1288
		$entry = array(
1289
		    'name' => ucfirst(strtolower($matches[1])),
1290
		    'path' => $conf_file,
1291
		    'descr' => $descr
1292
		);
1293
		$result[] = $entry;
1294
	}
1295

    
1296
	return $result;
1297
}
1298

    
1299
/* Switch between stable and devel repos */
1300
function pkg_switch_repo($path) {
1301
	global $g;
1302

    
1303
	safe_mkdir("/usr/local/etc/pkg/repos");
1304
	@unlink("/usr/local/etc/pkg/repos/{$g['product_name']}.conf");
1305
	@symlink($path, "/usr/local/etc/pkg/repos/{$g['product_name']}.conf");
1306

    
1307
	$abi_file = str_replace('.conf', '.abi', $path);
1308
	$altabi_file = str_replace('.conf', '.altabi', $path);
1309

    
1310
	if (file_exists($abi_file) && file_exists($altabi_file)) {
1311
		$abi = file_get_contents($abi_file);
1312
		$altabi = file_get_contents($altabi_file);
1313

    
1314
		$pkg_conf = array(
1315
			"ABI={$abi}",
1316
			"ALTABI={$altabi}"
1317
		);
1318

    
1319
		file_put_contents("/usr/local/etc/pkg.conf", $pkg_conf);
1320
	}
1321

    
1322
	/* Update pfSense_version cache */
1323
	mwexec_bg("/etc/rc.update_pkg_metadata now");
1324
	return;
1325
}
1326

    
1327
?>
(40-40/65)