Project

General

Profile

Download (17.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * system_camanager.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
7
 * Copyright (c) 2008 Shrew Soft Inc
8
 * All rights reserved.
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 * http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22

    
23
##|+PRIV
24
##|*IDENT=page-system-camanager
25
##|*NAME=System: CA Manager
26
##|*DESCR=Allow access to the 'System: CA Manager' page.
27
##|*MATCH=system_camanager.php*
28
##|-PRIV
29

    
30
require_once("guiconfig.inc");
31
require_once("certs.inc");
32

    
33
$ca_methods = array(
34
	"existing" => gettext("Import an existing Certificate Authority"),
35
	"internal" => gettext("Create an internal Certificate Authority"),
36
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
37

    
38
$ca_keylens = array("512", "1024", "2048", "3072", "4096", "7680", "8192", "15360", "16384");
39
global $openssl_digest_algs;
40

    
41
if (isset($_REQUEST['id']) && is_numericint($_REQUEST['id'])) {
42
	$id = $_REQUEST['id'];
43
}
44

    
45
if (!is_array($config['ca'])) {
46
	$config['ca'] = array();
47
}
48

    
49
$a_ca =& $config['ca'];
50

    
51
if (!is_array($config['cert'])) {
52
	$config['cert'] = array();
53
}
54

    
55
$a_cert =& $config['cert'];
56

    
57
if (!is_array($config['crl'])) {
58
	$config['crl'] = array();
59
}
60

    
61
$a_crl =& $config['crl'];
62

    
63
if ($_REQUEST['act']) {
64
	$act = $_REQUEST['act'];
65
}
66

    
67
if ($_POST['act'] == "del") {
68

    
69
	if (!isset($a_ca[$id])) {
70
		pfSenseHeader("system_camanager.php");
71
		exit;
72
	}
73

    
74
	/* Only remove CA reference when deleting. It can be reconnected if a new matching CA is imported */
75
	$index = count($a_cert) - 1;
76
	for (;$index >= 0; $index--) {
77
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
78
			unset($a_cert[$index]['caref']);
79
		}
80
	}
81

    
82
	/* Remove any CRLs for this CA, there is no way to recover the connection once the CA has been removed. */
83
	$index = count($a_crl) - 1;
84
	for (;$index >= 0; $index--) {
85
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
86
			unset($a_crl[$index]);
87
		}
88
	}
89

    
90
	$name = $a_ca[$id]['descr'];
91
	unset($a_ca[$id]);
92
	write_config();
93
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted."), htmlspecialchars($name));
94
	pfSenseHeader("system_camanager.php");
95
	exit;
96
}
97

    
98
if ($act == "edit") {
99
	if (!$a_ca[$id]) {
100
		pfSenseHeader("system_camanager.php");
101
		exit;
102
	}
103
	$pconfig['descr']  = $a_ca[$id]['descr'];
104
	$pconfig['refid']  = $a_ca[$id]['refid'];
105
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
106
	$pconfig['serial'] = $a_ca[$id]['serial'];
107
	if (!empty($a_ca[$id]['prv'])) {
108
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
109
	}
110
}
111

    
112
if ($act == "new") {
113
	$pconfig['method'] = $_POST['method'];
114
	$pconfig['keylen'] = "2048";
115
	$pconfig['digest_alg'] = "sha256";
116
	$pconfig['lifetime'] = "3650";
117
	$pconfig['dn_commonname'] = "internal-ca";
118
}
119

    
120
if ($act == "exp") {
121

    
122
	if (!$a_ca[$id]) {
123
		pfSenseHeader("system_camanager.php");
124
		exit;
125
	}
126

    
127
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
128
	$exp_data = base64_decode($a_ca[$id]['crt']);
129
	$exp_size = strlen($exp_data);
130

    
131
	header("Content-Type: application/octet-stream");
132
	header("Content-Disposition: attachment; filename={$exp_name}");
133
	header("Content-Length: $exp_size");
134
	echo $exp_data;
135
	exit;
136
}
137

    
138
if ($act == "expkey") {
139

    
140
	if (!$a_ca[$id]) {
141
		pfSenseHeader("system_camanager.php");
142
		exit;
143
	}
144

    
145
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
146
	$exp_data = base64_decode($a_ca[$id]['prv']);
147
	$exp_size = strlen($exp_data);
148

    
149
	header("Content-Type: application/octet-stream");
150
	header("Content-Disposition: attachment; filename={$exp_name}");
151
	header("Content-Length: $exp_size");
152
	echo $exp_data;
153
	exit;
154
}
155

    
156
if ($_POST['save']) {
157

    
158
	unset($input_errors);
159
	$input_errors = array();
160
	$pconfig = $_POST;
161

    
162
	/* input validation */
163
	if ($pconfig['method'] == "existing") {
164
		$reqdfields = explode(" ", "descr cert");
165
		$reqdfieldsn = array(
166
			gettext("Descriptive name"),
167
			gettext("Certificate data"));
168
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
169
			$input_errors[] = gettext("This certificate does not appear to be valid.");
170
		}
171
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
172
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
173
		}
174
		if (!$input_errors && !empty($_POST['key']) && cert_get_modulus($_POST['cert'], false) != prv_get_modulus($_POST['key'], false)) {
175
			$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
176
		}
177
	}
178
	if ($pconfig['method'] == "internal") {
179
		$reqdfields = explode(" ",
180
			"descr keylen lifetime dn_country dn_state dn_city ".
181
			"dn_organization dn_email dn_commonname");
182
		$reqdfieldsn = array(
183
			gettext("Descriptive name"),
184
			gettext("Key length"),
185
			gettext("Lifetime"),
186
			gettext("Distinguished name Country Code"),
187
			gettext("Distinguished name State or Province"),
188
			gettext("Distinguished name City"),
189
			gettext("Distinguished name Organization"),
190
			gettext("Distinguished name Email Address"),
191
			gettext("Distinguished name Common Name"));
192
	}
193
	if ($pconfig['method'] == "intermediate") {
194
		$reqdfields = explode(" ",
195
			"descr caref keylen lifetime dn_country dn_state dn_city ".
196
			"dn_organization dn_email dn_commonname");
197
		$reqdfieldsn = array(
198
			gettext("Descriptive name"),
199
			gettext("Signing Certificate Authority"),
200
			gettext("Key length"),
201
			gettext("Lifetime"),
202
			gettext("Distinguished name Country Code"),
203
			gettext("Distinguished name State or Province"),
204
			gettext("Distinguished name City"),
205
			gettext("Distinguished name Organization"),
206
			gettext("Distinguished name Email Address"),
207
			gettext("Distinguished name Common Name"));
208
	}
209

    
210
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
211
	if ($pconfig['method'] != "existing") {
212
		/* Make sure we do not have invalid characters in the fields for the certificate */
213
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
214
			array_push($input_errors, gettext("The field 'Descriptive Name' contains invalid characters."));
215
		}
216

    
217
		for ($i = 0; $i < count($reqdfields); $i++) {
218
			if ($reqdfields[$i] == 'dn_email') {
219
				if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_email"])) {
220
					array_push($input_errors, gettext("The field 'Distinguished name Email Address' contains invalid characters."));
221
				}
222
			} else if ($reqdfields[$i] == 'dn_commonname') {
223
				if (preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_commonname"])) {
224
					array_push($input_errors, gettext("The field 'Distinguished name Common Name' contains invalid characters."));
225
				}
226
			} else if (($reqdfields[$i] != "descr") && preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\.\"\']/", $_POST["$reqdfields[$i]"])) {
227
				array_push($input_errors, sprintf(gettext("The field '%s' contains invalid characters."), $reqdfieldsn[$i]));
228
			}
229
		}
230
		if (!in_array($_POST["keylen"], $ca_keylens)) {
231
			array_push($input_errors, gettext("Please select a valid Key Length."));
232
		}
233
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
234
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
235
		}
236
	}
237

    
238
	/* save modifications */
239
	if (!$input_errors) {
240
		$ca = array();
241
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
242
			$ca['refid'] = uniqid();
243
		} else {
244
			$ca['refid'] = $pconfig['refid'];
245
		}
246

    
247
		if (isset($id) && $a_ca[$id]) {
248
			$ca = $a_ca[$id];
249
		}
250

    
251
		$ca['descr'] = $pconfig['descr'];
252

    
253
		if ($act == "edit") {
254
			$ca['descr']  = $pconfig['descr'];
255
			$ca['refid']  = $pconfig['refid'];
256
			$ca['serial'] = $pconfig['serial'];
257
			$ca['crt']	  = base64_encode($pconfig['cert']);
258
			if (!empty($pconfig['key'])) {
259
				$ca['prv']	  = base64_encode($pconfig['key']);
260
			}
261
		} else {
262
			$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
263
			if ($pconfig['method'] == "existing") {
264
				ca_import($ca, $pconfig['cert'], $pconfig['key'], $pconfig['serial']);
265
			} else if ($pconfig['method'] == "internal") {
266
				$dn = array(
267
					'countryName' => $pconfig['dn_country'],
268
					'stateOrProvinceName' => $pconfig['dn_state'],
269
					'localityName' => $pconfig['dn_city'],
270
					'organizationName' => $pconfig['dn_organization'],
271
					'emailAddress' => $pconfig['dn_email'],
272
					'commonName' => $pconfig['dn_commonname']);
273
				if (!empty($pconfig['dn_organizationalunit'])) {
274
					$dn['organizationalUnitName'] = $pconfig['dn_organizationalunit'];
275
				}
276
				if (!ca_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['digest_alg'])) {
277
					$input_errors = array();
278
					while ($ssl_err = openssl_error_string()) {
279
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
280
							array_push($input_errors, "openssl library returns: " . $ssl_err);
281
						}
282
					}
283
				}
284
			} else if ($pconfig['method'] == "intermediate") {
285
				$dn = array(
286
					'countryName' => $pconfig['dn_country'],
287
					'stateOrProvinceName' => $pconfig['dn_state'],
288
					'localityName' => $pconfig['dn_city'],
289
					'organizationName' => $pconfig['dn_organization'],
290
					'emailAddress' => $pconfig['dn_email'],
291
					'commonName' => $pconfig['dn_commonname']);
292
				if (!empty($pconfig['dn_organizationalunit'])) {
293
					$dn['organizationalUnitName'] = $pconfig['dn_organizationalunit'];
294
				}
295
				if (!ca_inter_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['caref'], $pconfig['digest_alg'])) {
296
					$input_errors = array();
297
					while ($ssl_err = openssl_error_string()) {
298
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
299
							array_push($input_errors, "openssl library returns: " . $ssl_err);
300
						}
301
					}
302
				}
303
			}
304
			error_reporting($old_err_level);
305
		}
306

    
307
		if (isset($id) && $a_ca[$id]) {
308
			$a_ca[$id] = $ca;
309
		} else {
310
			$a_ca[] = $ca;
311
		}
312

    
313
		if (!$input_errors) {
314
			write_config();
315
			pfSenseHeader("system_camanager.php");
316
		}
317
	}
318
}
319

    
320
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
321
$pglinks = array("", "system_camanager.php", "system_camanager.php");
322

    
323
if ($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors) {
324
	$pgtitle[] = gettext('Edit');
325
	$pglinks[] = "@self";
326
}
327
include("head.inc");
328

    
329
if ($input_errors) {
330
	print_input_errors($input_errors);
331
}
332

    
333
if ($savemsg) {
334
	print_info_box($savemsg, 'success');
335
}
336

    
337
// Load valid country codes
338
$dn_cc = array();
339
if (file_exists("/etc/ca_countries")) {
340
	$dn_cc_file=file("/etc/ca_countries");
341
	foreach ($dn_cc_file as $line) {
342
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
343
			$dn_cc[$matches[1]] = $matches[1];
344
		}
345
	}
346
}
347

    
348
$tab_array = array();
349
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
350
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
351
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
352
display_top_tabs($tab_array);
353

    
354
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors)) {
355
?>
356
<div class="panel panel-default">
357
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificate Authorities')?></h2></div>
358
	<div class="panel-body">
359
		<div class="table-responsive">
360
		<table class="table table-striped table-hover table-rowdblclickedit">
361
			<thead>
362
				<tr>
363
					<th><?=gettext("Name")?></th>
364
					<th><?=gettext("Internal")?></th>
365
					<th><?=gettext("Issuer")?></th>
366
					<th><?=gettext("Certificates")?></th>
367
					<th><?=gettext("Distinguished Name")?></th>
368
					<th><?=gettext("In Use")?></th>
369
					<th><?=gettext("Actions")?></th>
370
				</tr>
371
			</thead>
372
			<tbody>
373
<?php
374
foreach ($a_ca as $i => $ca):
375
	$name = htmlspecialchars($ca['descr']);
376
	$subj = cert_get_subject($ca['crt']);
377
	$issuer = cert_get_issuer($ca['crt']);
378
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
379
	if ($subj == $issuer) {
380
		$issuer_name = gettext("self-signed");
381
	} else {
382
		$issuer_name = gettext("external");
383
	}
384
	$subj = htmlspecialchars($subj);
385
	$issuer = htmlspecialchars($issuer);
386
	$certcount = 0;
387

    
388
	$issuer_ca = lookup_ca($ca['caref']);
389
	if ($issuer_ca) {
390
		$issuer_name = $issuer_ca['descr'];
391
	}
392

    
393
	foreach ($a_cert as $cert) {
394
		if ($cert['caref'] == $ca['refid']) {
395
			$certcount++;
396
		}
397
	}
398

    
399
	foreach ($a_ca as $cert) {
400
		if ($cert['caref'] == $ca['refid']) {
401
			$certcount++;
402
		}
403
	}
404
?>
405
				<tr>
406
					<td><?=$name?></td>
407
					<td><i class="fa fa-<?= (!empty($ca['prv'])) ? "check" : "times" ; ?>"></i></td>
408
					<td><i><?=$issuer_name?></i></td>
409
					<td><?=$certcount?></td>
410
					<td>
411
						<?=$subj?>
412
						<br />
413
						<small>
414
							<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
415
						</small>
416
					</td>
417
					<td class="text-nowrap">
418
						<?php if (is_openvpn_server_ca($ca['refid'])): ?>
419
							<?=gettext("OpenVPN Server")?><br/>
420
						<?php endif?>
421
						<?php if (is_openvpn_client_ca($ca['refid'])): ?>
422
							<?=gettext("OpenVPN Client")?><br/>
423
						<?php endif?>
424
						<?php if (is_ipsec_peer_ca($ca['refid'])): ?>
425
							<?=gettext("IPsec Tunnel")?><br/>
426
						<?php endif?>
427
						<?php if (is_ldap_peer_ca($ca['refid'])): ?>
428
							<?=gettext("LDAP Server")?>
429
						<?php endif?>
430
					</td>
431
					<td class="text-nowrap">
432
						<a class="fa fa-pencil"	title="<?=gettext("Edit CA")?>"	href="system_camanager.php?act=edit&amp;id=<?=$i?>"></a>
433
						<a class="fa fa-certificate"	title="<?=gettext("Export CA")?>"	href="system_camanager.php?act=exp&amp;id=<?=$i?>"></a>
434
					<?php if ($ca['prv']): ?>
435
						<a class="fa fa-key"	title="<?=gettext("Export key")?>"	href="system_camanager.php?act=expkey&amp;id=<?=$i?>"></a>
436
					<?php endif?>
437
					<?php if (!ca_in_use($ca['refid'])): ?>
438
						<a class="fa fa-trash" 	title="<?=gettext("Delete CA and its CRLs")?>"	href="system_camanager.php?act=del&amp;id=<?=$i?>" usepost ></a>
439
					<?php endif?>
440
					</td>
441
				</tr>
442
<?php endforeach; ?>
443
			</tbody>
444
		</table>
445
		</div>
446
	</div>
447
</div>
448

    
449
<nav class="action-buttons">
450
	<a href="?act=new" class="btn btn-success btn-sm">
451
		<i class="fa fa-plus icon-embed-btn"></i>
452
		<?=gettext("Add")?>
453
	</a>
454
</nav>
455
<?php
456
	include("foot.inc");
457
	exit;
458
}
459

    
460
$form = new Form;
461
//$form->setAction('system_camanager.php?act=edit');
462
if (isset($id) && $a_ca[$id]) {
463
	$form->addGlobal(new Form_Input(
464
		'id',
465
		null,
466
		'hidden',
467
		$id
468
	));
469
}
470

    
471
if ($act == "edit") {
472
	$form->addGlobal(new Form_Input(
473
		'refid',
474
		null,
475
		'hidden',
476
		$pconfig['refid']
477
	));
478
}
479

    
480
$section = new Form_Section('Create / Edit CA');
481

    
482
$section->addInput(new Form_Input(
483
	'descr',
484
	'*Descriptive name',
485
	'text',
486
	$pconfig['descr']
487
));
488

    
489
if (!isset($id) || $act == "edit") {
490
	$section->addInput(new Form_Select(
491
		'method',
492
		'*Method',
493
		$pconfig['method'],
494
		$ca_methods
495
	))->toggles();
496
}
497

    
498
$form->add($section);
499

    
500
$section = new Form_Section('Existing Certificate Authority');
501
$section->addClass('toggle-existing collapse');
502

    
503
$section->addInput(new Form_Textarea(
504
	'cert',
505
	'*Certificate data',
506
	$pconfig['cert']
507
))->setHelp('Paste a certificate in X.509 PEM format here.');
508

    
509
$section->addInput(new Form_Textarea(
510
	'key',
511
	'Certificate Private Key (optional)',
512
	$pconfig['key']
513
))->setHelp('Paste the private key for the above certificate here. This is '.
514
	'optional in most cases, but is required when generating a '.
515
	'Certificate Revocation List (CRL).');
516

    
517
$section->addInput(new Form_Input(
518
	'serial',
519
	'Serial for next certificate',
520
	'number',
521
	$pconfig['serial']
522
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
523
	'certificate to be created using this CA.');
524

    
525
$form->add($section);
526

    
527
$section = new Form_Section('Internal Certificate Authority');
528
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
529

    
530
$allCas = array();
531
foreach ($a_ca as $ca) {
532
	if (!$ca['prv']) {
533
			continue;
534
	}
535

    
536
	$allCas[ $ca['refid'] ] = $ca['descr'];
537
}
538

    
539
$group = new Form_Group('*Signing Certificate Authority');
540
$group->addClass('toggle-intermediate', 'collapse');
541
$group->add(new Form_Select(
542
	'caref',
543
	null,
544
	$pconfig['caref'],
545
	$allCas
546
));
547
$section->add($group);
548

    
549
$section->addInput(new Form_Select(
550
	'keylen',
551
	'*Key length (bits)',
552
	$pconfig['keylen'],
553
	array_combine($ca_keylens, $ca_keylens)
554
));
555

    
556
$section->addInput(new Form_Select(
557
	'digest_alg',
558
	'*Digest Algorithm',
559
	$pconfig['digest_alg'],
560
	array_combine($openssl_digest_algs, $openssl_digest_algs)
561
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
562
	'when possible.');
563

    
564
$section->addInput(new Form_Input(
565
	'lifetime',
566
	'*Lifetime (days)',
567
	'number',
568
	$pconfig['lifetime']
569
));
570

    
571
$section->addInput(new Form_Select(
572
	'dn_country',
573
	'*Country Code',
574
	$pconfig['dn_country'],
575
	$dn_cc
576
));
577

    
578
$section->addInput(new Form_Input(
579
	'dn_state',
580
	'*State or Province',
581
	'text',
582
	$pconfig['dn_state'],
583
	['placeholder' => 'e.g. Texas']
584
));
585

    
586
$section->addInput(new Form_Input(
587
	'dn_city',
588
	'*City',
589
	'text',
590
	$pconfig['dn_city'],
591
	['placeholder' => 'e.g. Austin']
592
));
593

    
594
$section->addInput(new Form_Input(
595
	'dn_organization',
596
	'*Organization',
597
	'text',
598
	$pconfig['dn_organization'],
599
	['placeholder' => 'e.g. My Company Inc']
600
));
601

    
602
$section->addInput(new Form_Input(
603
	'dn_organizationalunit',
604
	'Organizational Unit',
605
	'text',
606
	$pconfig['dn_organizationalunit'],
607
	['placeholder' => 'e.g. My Department Name (optional)']
608
));
609

    
610
$section->addInput(new Form_Input(
611
	'dn_email',
612
	'*Email Address',
613
	'email',
614
	$pconfig['dn_email'],
615
	['placeholder' => 'e.g. admin@mycompany.com']
616
));
617

    
618
$section->addInput(new Form_Input(
619
	'dn_commonname',
620
	'*Common Name',
621
	'text',
622
	$pconfig['dn_commonname'],
623
	['placeholder' => 'e.g. internal-ca']
624
));
625

    
626
$form->add($section);
627

    
628
print $form;
629

    
630
$internal_ca_count = 0;
631
foreach ($a_ca as $ca) {
632
	if ($ca['prv']) {
633
		$internal_ca_count++;
634
	}
635
}
636

    
637
include('foot.inc');
638
?>
(190-190/223)