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
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512", "whirlpool");
40

    
41
if (is_numericint($_GET['id'])) {
42
	$id = $_GET['id'];
43
}
44
if (isset($_POST['id']) && is_numericint($_POST['id'])) {
45
	$id = $_POST['id'];
46
}
47

    
48
if (!is_array($config['ca'])) {
49
	$config['ca'] = array();
50
}
51

    
52
$a_ca =& $config['ca'];
53

    
54
if (!is_array($config['cert'])) {
55
	$config['cert'] = array();
56
}
57

    
58
$a_cert =& $config['cert'];
59

    
60
if (!is_array($config['crl'])) {
61
	$config['crl'] = array();
62
}
63

    
64
$a_crl =& $config['crl'];
65

    
66
$act = $_GET['act'];
67
if ($_POST['act']) {
68
	$act = $_POST['act'];
69
}
70

    
71
if ($act == "del") {
72

    
73
	if (!isset($a_ca[$id])) {
74
		pfSenseHeader("system_camanager.php");
75
		exit;
76
	}
77

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

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

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

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

    
116
if ($act == "new") {
117
	$pconfig['method'] = $_GET['method'];
118
	$pconfig['keylen'] = "2048";
119
	$pconfig['digest_alg'] = "sha256";
120
	$pconfig['lifetime'] = "3650";
121
	$pconfig['dn_commonname'] = "internal-ca";
122
}
123

    
124
if ($act == "exp") {
125

    
126
	if (!$a_ca[$id]) {
127
		pfSenseHeader("system_camanager.php");
128
		exit;
129
	}
130

    
131
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
132
	$exp_data = base64_decode($a_ca[$id]['crt']);
133
	$exp_size = strlen($exp_data);
134

    
135
	header("Content-Type: application/octet-stream");
136
	header("Content-Disposition: attachment; filename={$exp_name}");
137
	header("Content-Length: $exp_size");
138
	echo $exp_data;
139
	exit;
140
}
141

    
142
if ($act == "expkey") {
143

    
144
	if (!$a_ca[$id]) {
145
		pfSenseHeader("system_camanager.php");
146
		exit;
147
	}
148

    
149
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
150
	$exp_data = base64_decode($a_ca[$id]['prv']);
151
	$exp_size = strlen($exp_data);
152

    
153
	header("Content-Type: application/octet-stream");
154
	header("Content-Disposition: attachment; filename={$exp_name}");
155
	header("Content-Length: $exp_size");
156
	echo $exp_data;
157
	exit;
158
}
159

    
160
if ($_POST) {
161

    
162
	unset($input_errors);
163
	$input_errors = array();
164
	$pconfig = $_POST;
165

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

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

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

    
242
	/* save modifications */
243
	if (!$input_errors) {
244
		$ca = array();
245
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
246
			$ca['refid'] = uniqid();
247
		} else {
248
			$ca['refid'] = $pconfig['refid'];
249
		}
250

    
251
		if (isset($id) && $a_ca[$id]) {
252
			$ca = $a_ca[$id];
253
		}
254

    
255
		$ca['descr'] = $pconfig['descr'];
256

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

    
317
		pfSenseHeader("system_camanager.php");
318
	}
319
}
320

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
629
print $form;
630

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

    
638
include('foot.inc');
639
?>
(192-192/225)