IP addresses inherit tags from their parent networks (#375)
[racktables] / wwwroot / inc / functions.php
1 <?php
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 /*
8 *
9 * This file is a library of computational functions for RackTables.
10 *
11 */
12
13 $loclist[0] = 'front';
14 $loclist[1] = 'interior';
15 $loclist[2] = 'rear';
16 $loclist['front'] = 0;
17 $loclist['interior'] = 1;
18 $loclist['rear'] = 2;
19 $template[0] = array (TRUE, TRUE, TRUE);
20 $template[1] = array (TRUE, TRUE, FALSE);
21 $template[2] = array (FALSE, TRUE, TRUE);
22 $template[3] = array (TRUE, FALSE, FALSE);
23 $template[4] = array (FALSE, TRUE, FALSE);
24 $template[5] = array (FALSE, FALSE, TRUE);
25 $templateWidth[0] = 3;
26 $templateWidth[1] = 2;
27 $templateWidth[2] = 2;
28 $templateWidth[3] = 1;
29 $templateWidth[4] = 1;
30 $templateWidth[5] = 1;
31
32 define ('CHAP_OBJTYPE', 1);
33 define ('CHAP_PORTTYPE', 2);
34 // The latter matches both SunOS and Linux-styled formats.
35 define ('RE_L2_IFCFG', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){5}$/i');
36 define ('RE_L2_CISCO', '/^[0-9a-f]{4}(\.[0-9a-f]{4}){2}$/i');
37 define ('RE_L2_HUAWEI', '/^[0-9a-f]{4}(-[0-9a-f]{4}){2}$/i');
38 define ('RE_L2_SOLID', '/^[0-9a-f]{12}$/i');
39 define ('RE_L2_IPCFG', '/^[0-9a-f]{2}(-[0-9a-f]{2}){5}$/i');
40 define ('RE_L2_WWN_COLON', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){7}$/i');
41 define ('RE_L2_WWN_HYPHEN', '/^[0-9a-f]{2}(-[0-9a-f]{2}){7}$/i');
42 define ('RE_L2_WWN_SOLID', '/^[0-9a-f]{16}$/i');
43 define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#');
44 define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#');
45 define ('E_8021Q_NOERROR', 0);
46 define ('E_8021Q_VERSION_CONFLICT', 101);
47 define ('E_8021Q_PULL_REMOTE_ERROR', 102);
48 define ('E_8021Q_PUSH_REMOTE_ERROR', 103);
49 define ('E_8021Q_SYNC_DISABLED', 104);
50 define ('VLAN_MIN_ID', 1);
51 define ('VLAN_MAX_ID', 4094);
52 define ('VLAN_DFL_ID', 1);
53 define ('TAB_REMEMBER_TIMEOUT', 300);
54
55 // Entity type by page number mapping is 1:1 atm, but may change later.
56 $etype_by_pageno = array
57 (
58 'ipv4net' => 'ipv4net',
59 'ipv6net' => 'ipv6net',
60 'ipv4rspool' => 'ipv4rspool',
61 'ipv4vs' => 'ipv4vs',
62 'object' => 'object',
63 'rack' => 'rack',
64 'location' => 'location',
65 'user' => 'user',
66 'file' => 'file',
67 'vst' => 'vst',
68 );
69
70 // Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
71 $rtwidth = array
72 (
73 0 => 9,
74 1 => 21,
75 2 => 9
76 );
77
78 $location_obj_types = array
79 (
80 1560,
81 1561,
82 1562
83 );
84
85 // 802.1Q deploy queue titles
86 $dqtitle = array
87 (
88 'sync_aging' => 'Normal, aging',
89 'resync_aging' => 'Version conflict, aging',
90 'sync_ready' => 'Normal, ready for sync',
91 'resync_ready' => 'Version conflict, ready for retry',
92 'failed' => 'Failed',
93 'disabled' => 'Sync disabled',
94 'done' => 'Up to date',
95 );
96
97 $wdm_packs = array
98 (
99 '1000cwdm80' => array
100 (
101 'title' => '1000Base-CWDM80 (8 channels)',
102 'iif_ids' => array (3, 4),
103 'oif_ids' => array (1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216),
104 ),
105 '1000dwdm80' => array // ITU channels 20~61
106 (
107 'title' => '1000Base-DWDM80 (42 channels)',
108 'iif_ids' => array (3, 4),
109 'oif_ids' => array
110 (
111 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226,
112 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236,
113 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
114 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256,
115 1257, 1258
116 ),
117 ),
118 '10000dwdm80' => array // same channels for 10GE
119 (
120 'title' => '10GBase-ZR-DWDM80 (42 channels)',
121 'iif_ids' => array (9, 6, 5, 8, 7),
122 'oif_ids' => array
123 (
124 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268,
125 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
126 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288,
127 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298,
128 1299, 1300
129 ),
130 ),
131 '10000dwdm40' => array
132 (
133 'title' => '10GBase-ER-DWDM40 (42 channels)',
134 'iif_ids' => array (9, 6, 5, 8, 7),
135 'oif_ids' => array
136 (
137 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434,
138 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444,
139 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454,
140 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464,
141 1465, 1466
142 ),
143 ),
144 );
145
146 $log_messages = array(); // messages waiting for displaying
147
148 // This function assures that specified argument was passed
149 // and is a number greater than zero.
150 function assertUIntArg ($argname, $allow_zero = FALSE)
151 {
152 if (!isset ($_REQUEST[$argname]))
153 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
154 if (!is_numeric ($_REQUEST[$argname]))
155 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
156 if ($_REQUEST[$argname] < 0)
157 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
158 if (!$allow_zero and $_REQUEST[$argname] == 0)
159 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
160 }
161
162 function isInteger ($arg, $allow_zero = FALSE)
163 {
164 if (! is_numeric ($arg))
165 return FALSE;
166 if (! $allow_zero and ! $arg)
167 return FALSE;
168 return TRUE;
169 }
170
171 // make sure the arg is a parsable date
172 function assertDateArg ($argname, $ok_if_empty = FALSE)
173 {
174 assertStringArg ($argname, $ok_if_empty);
175 // different versions of PHP return false/-1
176 if ($_REQUEST[$argname] != '' and strtotime ($_REQUEST[$argname]) <= 0)
177 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a parsable date');
178 }
179
180
181 // This function assures that specified argument was passed
182 // and is a non-empty string.
183 function assertStringArg ($argname, $ok_if_empty = FALSE)
184 {
185 if (!isset ($_REQUEST[$argname]))
186 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
187 if (!is_string ($_REQUEST[$argname]))
188 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
189 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
190 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
191 }
192
193 function assertBoolArg ($argname, $ok_if_empty = FALSE)
194 {
195 if (!isset ($_REQUEST[$argname]))
196 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
197 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
198 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
199 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
200 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
201 }
202
203 // function returns binary IP address, or throws an exception
204 function assertIPArg ($argname)
205 {
206 assertStringArg ($argname, FALSE);
207 try
208 {
209 return ip_parse ($_REQUEST[$argname]);
210 }
211 catch (InvalidArgException $e)
212 {
213 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
214 }
215 }
216
217 // function returns binary IPv4 address, or throws an exception
218 function assertIPv4Arg ($argname)
219 {
220 try
221 {
222 return ip4_parse ($_REQUEST[$argname]);
223 }
224 catch (InvalidArgException $e)
225 {
226 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
227 }
228 }
229
230 // function returns binary IPv6 address, or throws an exception
231 function assertIPv6Arg ($argname)
232 {
233 try
234 {
235 return ip6_parse ($_REQUEST[$argname]);
236 }
237 catch (InvalidArgException $e)
238 {
239 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
240 }
241 }
242
243 function assertPCREArg ($argname)
244 {
245 assertStringArg ($argname, TRUE); // empty pattern is Ok
246 if (FALSE === preg_match ($_REQUEST[$argname], 'test'))
247 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'PCRE validation failed');
248 }
249
250 function isPCRE ($arg)
251 {
252 if (! isset ($arg) or FALSE === @preg_match ($arg, 'test'))
253 return FALSE;
254 return TRUE;
255 }
256
257 function genericAssertion ($argname, $argtype)
258 {
259 global $sic;
260 switch ($argtype)
261 {
262 case 'string':
263 assertStringArg ($argname);
264 break;
265 case 'string0':
266 assertStringArg ($argname, TRUE);
267 break;
268 case 'uint':
269 assertUIntArg ($argname);
270 break;
271 case 'uint-uint':
272 assertStringArg ($argname);
273 if (1 != preg_match ('/^[1-9][0-9]*-[1-9][0-9]*$/', $_REQUEST[$argname]))
274 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], 'illegal format');
275 break;
276 case 'uint0':
277 assertUIntArg ($argname, TRUE);
278 break;
279 case 'inet':
280 assertIPArg ($argname);
281 break;
282 case 'inet4':
283 assertIPv4Arg ($argname);
284 break;
285 case 'inet6':
286 assertIPv6Arg ($argname);
287 break;
288 case 'l2address':
289 assertStringArg ($argname);
290 case 'l2address0':
291 assertStringArg ($argname, TRUE);
292 try
293 {
294 l2addressForDatabase ($sic[$argname]);
295 }
296 catch (InvalidArgException $e)
297 {
298 throw new InvalidRequestArgException ($argname, $sic[$argname], 'malformed MAC/WWN address');
299 }
300 break;
301 case 'tag':
302 assertStringArg ($argname);
303 if (!validTagName ($sic[$argname]))
304 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name');
305 break;
306 case 'pcre':
307 assertPCREArg ($argname);
308 break;
309 case 'json':
310 assertStringArg ($argname);
311 if (NULL === json_decode ($sic[$argname], TRUE))
312 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client');
313 break;
314 case 'array':
315 if (! array_key_exists ($argname, $_REQUEST))
316 throw new InvalidRequestArgException ($argname, '(missing argument)');
317 if (! is_array ($_REQUEST[$argname]))
318 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
319 break;
320 case 'enum/attr_type':
321 assertStringArg ($argname);
322 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict','date')))
323 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
324 break;
325 case 'enum/vlan_type':
326 assertStringArg ($argname);
327 // "Alien" type is not valid until the logic is fixed to implement it in full.
328 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory')))
329 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
330 break;
331 case 'enum/wdmstd':
332 assertStringArg ($argname);
333 global $wdm_packs;
334 if (! array_key_exists ($sic[$argname], $wdm_packs))
335 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
336 break;
337 case 'enum/ipproto':
338 assertStringArg ($argname);
339 global $vs_proto;
340 if (!array_key_exists ($sic[$argname], $vs_proto))
341 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
342 break;
343 case 'enum/alloc_type':
344 assertStringArg ($argname);
345 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router')))
346 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
347 break;
348 case 'enum/dqcode':
349 assertStringArg ($argname);
350 global $dqtitle;
351 if (! array_key_exists ($sic[$argname], $dqtitle))
352 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
353 break;
354 case 'iif':
355 if (!array_key_exists ($sic[$argname], getPortIIFOptions()))
356 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
357 break;
358 case 'vlan':
359 case 'vlan1':
360 genericAssertion ($argname, 'uint');
361 if ($argtype == 'vlan' and $sic[$argname] == VLAN_DFL_ID)
362 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN cannot be changed');
363 if ($sic[$argname] > VLAN_MAX_ID or $sic[$argname] < VLAN_MIN_ID)
364 throw new InvalidRequestArgException ($argname, $sic[$argname], 'out of valid range');
365 break;
366 case 'rackcode/expr':
367 genericAssertion ($argname, 'string0');
368 if ($sic[$argname] == '')
369 return;
370 $parse = spotPayload ($sic[$argname], 'SYNT_EXPR');
371 if ($parse['result'] != 'ACK')
372 throw new InvalidRequestArgException ($argname, $sic[$argname], 'RackCode parsing error');
373 break;
374 default:
375 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input
376 }
377 }
378
379 // return HTML form checkbox value (TRUE of FALSE) by name of its input control
380 function isCheckSet ($input_name, $mode = 'bool')
381 {
382 $value = isset ($_REQUEST[$input_name]) && $_REQUEST[$input_name] == 'on';
383 switch ($mode)
384 {
385 case 'bool' : return $value;
386 case 'yesno': return $value ? 'yes' : 'no';
387 default: throw new InvalidArgException ('mode', $mode);
388 }
389 }
390
391 // Validate and return "bypass" value for the current context, if one is
392 // defined for it, or NULL otherwise.
393 function getBypassValue()
394 {
395 global $page, $pageno, $sic;
396 if (!array_key_exists ('bypass', $page[$pageno]))
397 return NULL;
398 if (!array_key_exists ('bypass_type', $page[$pageno]))
399 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL);
400 genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']);
401 return $sic[$page[$pageno]['bypass']];
402 }
403
404 // fills $args array with the bypass values of specified $pageno which are provided in $_REQUEST
405 function fillBypassValues ($pageno, &$args)
406 {
407 global $page, $sic;
408 if (isset ($page[$pageno]['bypass']))
409 {
410 $param_name = $page[$pageno]['bypass'];
411 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
412 $args[$param_name] = $sic[$param_name];
413 }
414 if (isset ($page[$pageno]['bypass_tabs']))
415 foreach ($page[$pageno]['bypass_tabs'] as $param_name)
416 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
417 $args[$param_name] = $sic[$param_name];
418 }
419
420 // Objects of some types should be explicitly shown as
421 // anonymous (labelless). This function is a single place where the
422 // decision about displayed name is made.
423 function setDisplayedName (&$cell)
424 {
425 if ($cell['realm'] == 'object')
426 {
427 if ($cell['name'] != '')
428 $cell['dname'] = $cell['name'];
429 else
430 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
431 }
432 elseif ($cell['realm'] == 'ipv4vs')
433 {
434 if ($cell['proto'] == 'MARK')
435 $cell['dname'] = "fwmark: " . implode ('', unpack('N', substr ($cell['vip_bin'], 0, 4)));
436 else
437 $cell['dname'] = $cell['vip'] . ':' . $cell['vport'] . '/' . $cell['proto'];
438 }
439 }
440
441 // This function finds height of solid rectangle of atoms, which are all
442 // assigned to the same object. Rectangle base is defined by specified
443 // template.
444 function rectHeight ($rackData, $startRow, $template_idx)
445 {
446 $height = 0;
447 // The first met object_id is used to match all the folowing IDs.
448 $object_id = 0;
449 global $template;
450 do
451 {
452 for ($locidx = 0; $locidx < 3; $locidx++)
453 {
454 // At least one value in template is TRUE, but the following block
455 // can meet 'skipped' atoms. Let's ensure we have something after processing
456 // the first row.
457 if ($template[$template_idx][$locidx])
458 {
459 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
460 break 2;
461 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
462 break 2;
463 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
464 break 2;
465 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
466 break 2;
467 if ($object_id == 0)
468 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
469 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
470 break 2;
471 }
472 }
473 // If the first row can't offer anything, bail out.
474 if ($height == 0 and $object_id == 0)
475 break;
476 $height++;
477 }
478 while ($startRow - $height > 0);
479 # echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
480 # echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
481 # echo ") height==${height}<br>\n";
482 return $height;
483 }
484
485 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
486 // attributes.
487 function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
488 {
489 global $template, $templateWidth;
490 $colspan = 0;
491 for ($height = 0; $height < $maxheight; $height++)
492 {
493 for ($locidx = 0; $locidx < 3; $locidx++)
494 {
495 if ($template[$template_idx][$locidx])
496 {
497 // Add colspan/rowspan to the first row met and mark the following ones to skip.
498 // Explicitly show even single-cell spanned atoms, because rectHeight()
499 // is expeciting this data for correct calculation.
500 if ($colspan != 0)
501 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
502 else
503 {
504 $colspan = $templateWidth[$template_idx];
505 if ($colspan >= 1)
506 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
507 if ($maxheight >= 1)
508 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
509 }
510 }
511 }
512 }
513 return;
514 }
515
516 // This function sets rowspan/solspan/skipped atom attributes for renderRack()
517 // What we actually have to do is to find _all_ possible rectangles for each unit
518 // and then select the widest of those with the maximal square.
519 function markAllSpans (&$rackData)
520 {
521 for ($i = $rackData['height']; $i > 0; $i--)
522 while (markBestSpan ($rackData, $i));
523 }
524
525 // Calculate height of 6 possible span templates (array is presorted by width
526 // descending) and mark the best (if any).
527 function markBestSpan (&$rackData, $i)
528 {
529 global $template, $templateWidth;
530 for ($j = 0; $j < 6; $j++)
531 {
532 $height[$j] = rectHeight ($rackData, $i, $j);
533 $square[$j] = $height[$j] * $templateWidth[$j];
534 }
535 // find the widest rectangle of those with maximal height
536 $maxsquare = max ($square);
537 if (!$maxsquare)
538 return FALSE;
539 $best_template_index = 0;
540 for ($j = 0; $j < 6; $j++)
541 if ($square[$j] == $maxsquare)
542 {
543 $best_template_index = $j;
544 $bestheight = $height[$j];
545 break;
546 }
547 // distribute span marks
548 markSpan ($rackData, $i, $bestheight, $best_template_index);
549 return TRUE;
550 }
551
552 // We can mount 'F' atoms and unmount our own 'T' atoms.
553 function applyObjectMountMask (&$rackData, $object_id)
554 {
555 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
556 for ($locidx = 0; $locidx < 3; $locidx++)
557 switch ($rackData[$unit_no][$locidx]['state'])
558 {
559 case 'F':
560 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
561 break;
562 case 'T':
563 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
564 break;
565 default:
566 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
567 }
568 }
569
570 // Design change means transition between 'F' and 'A' and back.
571 function applyRackDesignMask (&$rackData)
572 {
573 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
574 for ($locidx = 0; $locidx < 3; $locidx++)
575 switch ($rackData[$unit_no][$locidx]['state'])
576 {
577 case 'F':
578 case 'A':
579 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
580 break;
581 default:
582 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
583 }
584 }
585
586 // The same for 'F' and 'U'.
587 function applyRackProblemMask (&$rackData)
588 {
589 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
590 for ($locidx = 0; $locidx < 3; $locidx++)
591 switch ($rackData[$unit_no][$locidx]['state'])
592 {
593 case 'F':
594 case 'U':
595 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
596 break;
597 default:
598 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
599 }
600 }
601
602 // This function highlights specified object (and removes previous highlight).
603 function highlightObject (&$rackData, $object_id)
604 {
605 // Also highlight parent objects
606 $parents = getEntityRelatives ('parents', 'object', $object_id);
607 $parent_ids = array();
608 foreach ($parents as $parent)
609 $parent_ids[] = $parent['entity_id'];
610
611 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
612 for ($locidx = 0; $locidx < 3; $locidx++)
613 if
614 (
615 $rackData[$unit_no][$locidx]['state'] == 'T' and
616 ($rackData[$unit_no][$locidx]['object_id'] == $object_id or in_array($rackData[$unit_no][$locidx]['object_id'], $parent_ids))
617 )
618 $rackData[$unit_no][$locidx]['hl'] = 'h';
619 else
620 unset ($rackData[$unit_no][$locidx]['hl']);
621 }
622
623 // This function marks atoms to selected or not depending on their current state.
624 function markupAtomGrid (&$data, $checked_state)
625 {
626 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
627 for ($locidx = 0; $locidx < 3; $locidx++)
628 {
629 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
630 continue;
631 if ($data[$unit_no][$locidx]['state'] == $checked_state)
632 $data[$unit_no][$locidx]['checked'] = ' checked';
633 else
634 $data[$unit_no][$locidx]['checked'] = '';
635 }
636 }
637
638 // This function is almost a clone of processGridForm(), but doesn't save anything to database
639 // Return value is the changed rack data.
640 // Here we assume that correct filter has already been applied, so we just
641 // set or unset checkbox inputs w/o changing atom state.
642 function mergeGridFormToRack (&$rackData)
643 {
644 $rack_id = $rackData['id'];
645 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
646 for ($locidx = 0; $locidx < 3; $locidx++)
647 {
648 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
649 continue;
650 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
651 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
652 $rackData[$unit_no][$locidx]['checked'] = ' checked';
653 else
654 $rackData[$unit_no][$locidx]['checked'] = '';
655 }
656 }
657
658 // wrapper around ip4_mask and ip6_mask
659 // netmask conversion from length to binary string
660 // v4/v6 mode is toggled by $is_ipv6 parameter
661 // Throws exception if $prefix_len is invalid
662 function ip_mask ($prefix_len, $is_ipv6)
663 {
664 if ($is_ipv6)
665 return ip6_mask ($prefix_len);
666 else
667 return ip4_mask ($prefix_len);
668 }
669
670 // netmask conversion from length to binary string
671 // Throws exception if $prefix_len is invalid
672 function ip4_mask ($prefix_len)
673 {
674 static $mask = array
675 (
676 "\x00\x00\x00\x00", // 0
677 "\x80\x00\x00\x00", // 1
678 "\xC0\x00\x00\x00", // 2
679 "\xE0\x00\x00\x00", // 3
680 "\xF0\x00\x00\x00", // 4
681 "\xF8\x00\x00\x00", // 5
682 "\xFC\x00\x00\x00", // 6
683 "\xFE\x00\x00\x00", // 7
684 "\xFF\x00\x00\x00", // 8
685 "\xFF\x80\x00\x00", // 9
686 "\xFF\xC0\x00\x00", // 10
687 "\xFF\xE0\x00\x00", // 11
688 "\xFF\xF0\x00\x00", // 12
689 "\xFF\xF8\x00\x00", // 13
690 "\xFF\xFC\x00\x00", // 14
691 "\xFF\xFE\x00\x00", // 15
692 "\xFF\xFF\x00\x00", // 16
693 "\xFF\xFF\x80\x00", // 17
694 "\xFF\xFF\xC0\x00", // 18
695 "\xFF\xFF\xE0\x00", // 19
696 "\xFF\xFF\xF0\x00", // 20
697 "\xFF\xFF\xF8\x00", // 21
698 "\xFF\xFF\xFC\x00", // 22
699 "\xFF\xFF\xFE\x00", // 23
700 "\xFF\xFF\xFF\x00", // 24
701 "\xFF\xFF\xFF\x80", // 25
702 "\xFF\xFF\xFF\xC0", // 26
703 "\xFF\xFF\xFF\xE0", // 27
704 "\xFF\xFF\xFF\xF0", // 28
705 "\xFF\xFF\xFF\xF8", // 29
706 "\xFF\xFF\xFF\xFC", // 30
707 "\xFF\xFF\xFF\xFE", // 31
708 "\xFF\xFF\xFF\xFF", // 32
709 );
710
711 if ($prefix_len >= 0 and $prefix_len <= 32)
712 return $mask[$prefix_len];
713 else
714 throw new InvalidArgException ('prefix_len', $prefix_len);
715 }
716
717 // netmask conversion from length to binary string
718 // Throws exception if $prefix_len is invalid
719 function ip6_mask ($prefix_len)
720 {
721 static $mask = array
722 (
723 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 0
724 "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 1
725 "\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 2
726 "\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 3
727 "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 4
728 "\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 5
729 "\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 6
730 "\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 7
731 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 8
732 "\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 9
733 "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 10
734 "\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 11
735 "\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 12
736 "\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 13
737 "\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 14
738 "\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 15
739 "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 16
740 "\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 17
741 "\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 18
742 "\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 19
743 "\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 20
744 "\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 21
745 "\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 22
746 "\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 23
747 "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 24
748 "\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 25
749 "\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 26
750 "\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 27
751 "\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 28
752 "\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 29
753 "\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 30
754 "\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 31
755 "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 32
756 "\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 33
757 "\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 34
758 "\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 35
759 "\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 36
760 "\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 37
761 "\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 38
762 "\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 39
763 "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 40
764 "\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 41
765 "\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 42
766 "\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 43
767 "\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 44
768 "\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 45
769 "\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 46
770 "\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 47
771 "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 48
772 "\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 49
773 "\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 50
774 "\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 51
775 "\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 52
776 "\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 53
777 "\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 54
778 "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 55
779 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 56
780 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00", // 57
781 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00", // 58
782 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00", // 59
783 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00", // 60
784 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00", // 61
785 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00", // 62
786 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00", // 63
787 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", // 64
788 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", // 65
789 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00", // 66
790 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00", // 67
791 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00", // 68
792 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00", // 69
793 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00", // 70
794 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00", // 71
795 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00", // 72
796 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00", // 73
797 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00", // 74
798 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00", // 75
799 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00", // 76
800 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00", // 77
801 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00", // 78
802 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00", // 79
803 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00", // 80
804 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00", // 81
805 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00", // 82
806 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00", // 83
807 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00", // 84
808 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00", // 85
809 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00", // 86
810 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00", // 87
811 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00", // 88
812 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00", // 89
813 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00", // 90
814 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00", // 91
815 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00", // 92
816 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00", // 93
817 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00", // 94
818 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", // 95
819 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00", // 96
820 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00", // 97
821 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00", // 98
822 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00", // 99
823 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00", // 100
824 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00", // 101
825 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00", // 102
826 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00", // 103
827 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", // 104
828 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00", // 105
829 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00", // 106
830 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00", // 107
831 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00", // 108
832 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00", // 109
833 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00", // 110
834 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00", // 111
835 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", // 112
836 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00", // 113
837 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00", // 114
838 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00", // 115
839 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00", // 116
840 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", // 117
841 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00", // 118
842 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00", // 119
843 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", // 120
844 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80", // 121
845 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0", // 122
846 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0", // 123
847 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0", // 124
848 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8", // 125
849 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC", // 126
850 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", // 127
851 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", // 128
852 );
853
854 if ($prefix_len >= 0 and $prefix_len <= 128)
855 return $mask[$prefix_len];
856 else
857 throw new InvalidArgException ('prefix_len', $prefix_len);
858 }
859
860 // This function looks up 'has_problems' flag for 'T' atoms
861 // and modifies 'hl' key. May be, this should be better done
862 // in amplifyCell(). We don't honour 'skipped' key, because
863 // the function is also used for thumb creation.
864 function markupObjectProblems (&$rackData)
865 {
866 for ($i = $rackData['height']; $i > 0; $i--)
867 for ($locidx = 0; $locidx < 3; $locidx++)
868 if ($rackData[$i][$locidx]['state'] == 'T')
869 {
870 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
871 if ($object['has_problems'] == 'yes')
872 {
873 // Object can be already highlighted.
874 if (isset ($rackData[$i][$locidx]['hl']))
875 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
876 else
877 $rackData[$i][$locidx]['hl'] = 'w';
878 }
879 }
880 }
881
882 // Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
883 // in the provided string, an empty string for an empty string or raise an exception.
884 function l2addressForDatabase ($string)
885 {
886 $string = strtoupper ($string);
887 switch (TRUE)
888 {
889 case ($string == '' or preg_match (RE_L2_SOLID, $string) or preg_match (RE_L2_WWN_SOLID, $string)):
890 return $string;
891 case (preg_match (RE_L2_IFCFG, $string) or preg_match (RE_L2_WWN_COLON, $string)):
892 // reformat output of SunOS ifconfig
893 $ret = '';
894 foreach (explode (':', $string) as $byte)
895 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
896 return $ret;
897 case (preg_match (RE_L2_CISCO, $string)):
898 return str_replace ('.', '', $string);
899 case (preg_match (RE_L2_HUAWEI, $string)):
900 return str_replace ('-', '', $string);
901 case (preg_match (RE_L2_IPCFG, $string) or preg_match (RE_L2_WWN_HYPHEN, $string)):
902 return str_replace ('-', '', $string);
903 default:
904 throw new InvalidArgException ('$string', $string, 'malformed MAC/WWN address');
905 }
906 }
907
908 function l2addressFromDatabase ($string)
909 {
910 switch (strlen ($string))
911 {
912 case 12: // Ethernet
913 case 16: // FireWire/Fibre Channel
914 $ret = implode (':', str_split ($string, 2));
915 break;
916 default:
917 $ret = $string;
918 break;
919 }
920 return $ret;
921 }
922
923 // The following 2 functions return previous and next rack IDs for
924 // a given rack ID. The order of racks is the same as in renderRackspace()
925 // or renderRow().
926 function getPrevIDforRack ($row_id, $rack_id)
927 {
928 $rackList = listCells ('rack', $row_id);
929 doubleLink ($rackList);
930 if (isset ($rackList[$rack_id]['prev_key']))
931 return $rackList[$rack_id]['prev_key'];
932 return NULL;
933 }
934
935 function getNextIDforRack ($row_id, $rack_id)
936 {
937 $rackList = listCells ('rack', $row_id);
938 doubleLink ($rackList);
939 if (isset ($rackList[$rack_id]['next_key']))
940 return $rackList[$rack_id]['next_key'];
941 return NULL;
942 }
943
944 // This function finds previous and next array keys for each array key and
945 // modifies its argument accordingly.
946 function doubleLink (&$array)
947 {
948 $prev_key = NULL;
949 foreach (array_keys ($array) as $key)
950 {
951 if ($prev_key)
952 {
953 $array[$key]['prev_key'] = $prev_key;
954 $array[$prev_key]['next_key'] = $key;
955 }
956 $prev_key = $key;
957 }
958 }
959
960 function sortTokenize ($a, $b)
961 {
962 $aold='';
963 while ($a != $aold)
964 {
965 $aold=$a;
966 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
967 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
968 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
969 }
970
971 $bold='';
972 while ($b != $bold)
973 {
974 $bold=$b;
975 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
976 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
977 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
978 }
979
980
981
982 $ar = explode(' ', $a);
983 $br = explode(' ', $b);
984 for ($i=0; $i<count($ar) && $i<count($br); $i++)
985 {
986 $ret = 0;
987 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
988 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
989 else
990 $ret = strcasecmp($ar[$i], $br[$i]);
991 if ($ret != 0)
992 return $ret;
993 }
994 if ($i<count($ar))
995 return 1;
996 if ($i<count($br))
997 return -1;
998 return 0;
999 }
1000
1001 // This function returns an array of single element of object's FQDN attribute,
1002 // if FQDN is set. The next choice is object's common name, if it looks like a
1003 // hostname. Otherwise an array of all 'regular' IP addresses of the
1004 // object is returned (which may appear 0 and more elements long).
1005 function findAllEndpoints ($object_id, $fallback = '')
1006 {
1007 foreach (getAttrValues ($object_id) as $record)
1008 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
1009 return array ($record['value']);
1010 $regular = array();
1011 foreach (getObjectIPv4AllocationList ($object_id) as $ip_bin => $alloc)
1012 if ($alloc['type'] == 'regular')
1013 $regular[] = ip4_format ($ip_bin);
1014 // FIXME: add IPv6 allocations to this list
1015 if (!count ($regular) && strlen ($fallback))
1016 return array ($fallback);
1017 return $regular;
1018 }
1019
1020 // Some records in the dictionary may be written as plain text or as Wiki
1021 // link in the following syntax:
1022 // 1. word
1023 // 2. [[word URL]] // FIXME: this isn't working
1024 // 3. [[word word word | URL]]
1025 // This function parses the line in $record['value'] and modifies $record:
1026 // $record['o_value'] is set to be the first part of link (word word word)
1027 // $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied
1028 // $record['href'] is set to URL if it is specified in the input value
1029 function parseWikiLink (&$record)
1030 {
1031 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches))
1032 $record['o_value'] = $record['value'];
1033 else
1034 {
1035 $s = explode ('|', $matches[1]);
1036 if (isset ($s[1]))
1037 $record['href'] = trim ($s[1]);
1038 $record['o_value'] = trim ($s[0]);
1039 }
1040 $record['a_value'] = execGMarker ($record['o_value']);
1041 }
1042
1043 // FIXME: should this be saved as "P-data"?
1044 function execGMarker ($line)
1045 {
1046 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
1047 }
1048
1049 // rackspace usage for a single rack
1050 // (T + W + U) / (height * 3 - A)
1051 function getRSUforRack ($data)
1052 {
1053 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1054 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1055 for ($locidx = 0; $locidx < 3; $locidx++)
1056 $counter[$data[$unit_no][$locidx]['state']]++;
1057 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1058 }
1059
1060 // Same for row.
1061 function getRSUforRow ($rowData)
1062 {
1063 if (!count ($rowData))
1064 return 0;
1065 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1066 $total_height = 0;
1067 foreach (array_keys ($rowData) as $rack_id)
1068 {
1069 $data = spotEntity ('rack', $rack_id);
1070 amplifyCell ($data);
1071 $total_height += $data['height'];
1072 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1073 for ($locidx = 0; $locidx < 3; $locidx++)
1074 $counter[$data[$unit_no][$locidx]['state']]++;
1075 }
1076 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1077 }
1078
1079 // Find any URL in a string and replace it with a clickable link
1080 // Adopted from UrlLinker: https://bitbucket.org/kwi/urllinker/src
1081 function string_insert_hrefs ($s)
1082 {
1083 if (getConfigVar ('DETECT_URLS') != 'yes')
1084 return $s;
1085
1086 $rexProtocol = '(https?://)?';
1087 $rexDomain = '(?:[-a-zA-Z0-9]{1,63}\.)+[a-zA-Z][-a-zA-Z0-9]{1,62}';
1088 $rexIp = '(?:[1-9][0-9]{0,2}\.|0\.){3}(?:[1-9][0-9]{0,2}|0)'; // doesn't support IPv6 addresses
1089 $rexPort = '(:[0-9]{1,5})?';
1090 $rexPath = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';
1091 $rexQuery = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1092 $rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1093 $rexUsername = '[^]\\\\\x00-\x20\"(),:-<>[\x7f-\xff]{1,64}';
1094 $rexPassword = $rexUsername; // allow the same characters as in the username
1095 $rexUrl = "$rexProtocol(?:($rexUsername)(:$rexPassword)?@)?($rexDomain|$rexIp)($rexPort$rexPath$rexQuery$rexFragment)";
1096 $rexUrlLinker = "{\\b$rexUrl(?=[?.!,;:\"]?(\s|$))}";
1097
1098 $html = '';
1099 $position = 0;
1100 while (preg_match($rexUrlLinker, $s, $match, PREG_OFFSET_CAPTURE, $position))
1101 {
1102 list($url, $urlPosition) = $match[0];
1103
1104 // Add the text leading up to the URL.
1105 $html .= substr($s, $position, $urlPosition - $position);
1106
1107 $protocol = $match[1][0];
1108 $username = $match[2][0];
1109 $password = $match[3][0];
1110 $domain = $match[4][0];
1111 $afterDomain = $match[5][0]; // everything following the domain
1112 $port = $match[6][0];
1113 $path = $match[7][0];
1114
1115 // Do not permit implicit protocol if a password is specified, as
1116 // this causes too many errors (e.g. "my email:foo@example.org").
1117 if (!$protocol && $password)
1118 {
1119 $html .= htmlspecialchars($username);
1120
1121 // Continue text parsing at the ':' following the "username".
1122 $position = $urlPosition + strlen($username);
1123 continue;
1124 }
1125
1126 if (!$protocol && $username && !$password && !$afterDomain)
1127 {
1128 // Looks like an email address.
1129 $emailUrl = TRUE;
1130 $completeUrl = "mailto:$url";
1131 $linkText = $url;
1132 }
1133 else
1134 {
1135 // Prepend http:// if no protocol specified
1136 $completeUrl = $protocol ? $url : "http://$url";
1137 $linkText = "$protocol$domain$port$path";
1138 }
1139
1140 $linkHtml = '<a href="' . htmlspecialchars($completeUrl) . '">'
1141 . htmlspecialchars($linkText)
1142 . '</a>';
1143
1144 // It's not an e-mail address, provide an additional link with a new window/tab as the target
1145 if (!isset($emailUrl))
1146 $linkHtml .= ' [<a href="' . htmlspecialchars($completeUrl) . '" target="_blank">^</a>]';
1147 unset($emailUrl);
1148
1149 // Add the hyperlink.
1150 $html .= $linkHtml;
1151
1152 // Continue text parsing from after the URL.
1153 $position = $urlPosition + strlen($url);
1154 }
1155
1156 // Add the remainder of the text.
1157 $html .= substr($s, $position);
1158 return $html;
1159 }
1160
1161 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1162 // for the requested object_type_id.
1163 function getAutoPorts ($type_id)
1164 {
1165 $ret = array();
1166 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1167 foreach ($typemap as $equation)
1168 {
1169 $tmp = explode ('=', $equation);
1170 if (count ($tmp) != 2)
1171 continue;
1172 $objtype_id = $tmp[0];
1173 if ($objtype_id != $type_id)
1174 continue;
1175 $portlist = $tmp[1];
1176 foreach (explode ('+', $portlist) as $product)
1177 {
1178 $tmp = explode ('*', $product);
1179 if (count ($tmp) > 4 || count ($tmp) < 3)
1180 continue;
1181 # format: <number of ports>*<port_type_id>[*<sprintf_name>*<startnumber>]
1182 $nports = $tmp[0];
1183 $port_type = $tmp[1];
1184 $format = $tmp[2];
1185 $startnum = isset ($tmp[3]) ? $tmp[3] : 0;
1186 for ($i = 0; $i < $nports; $i++)
1187 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i + $startnum));
1188 }
1189 }
1190 return $ret;
1191 }
1192
1193 // Use pre-served trace to traverse the tree, then place given node where it belongs.
1194 function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
1195 {
1196 // This function needs the trace to be followed FIFO-way. The fastest
1197 // way to do so is to use array_push() for putting values into the
1198 // list and array_shift() for getting them out. This exposed up to 11%
1199 // performance gain compared to other patterns of array_push/array_unshift/
1200 // array_reverse/array_pop/array_shift conjunction.
1201 $myid = array_shift ($trace);
1202 if (!count ($trace)) // reached the target
1203 {
1204 if (!$threshold or ($threshold and $tree[$myid]['kidc'] + 1 < $threshold))
1205 $tree[$myid]['kids'][$key] = $value;
1206 // Reset accumulated records once, when the limit is reached, not each time
1207 // after that.
1208 if (++$tree[$myid]['kidc'] == $threshold)
1209 $tree[$myid]['kids'] = array();
1210 }
1211 else // not yet
1212 {
1213 $self = __FUNCTION__;
1214 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
1215 }
1216 }
1217
1218 // Likewise traverse the tree with the trace and return the final node.
1219 function peekNode ($tree, $trace, $target_id)
1220 {
1221 $self = __FUNCTION__;
1222 if (NULL === ($next = array_shift ($trace))) // warm
1223 {
1224 foreach ($tree as $node)
1225 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
1226 return $node;
1227 }
1228 else // cold
1229 {
1230 foreach ($tree as $node)
1231 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
1232 return $self ($node['kids'], $trace, $target_id);
1233 }
1234 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL);
1235 }
1236
1237 function treeItemCmp ($a, $b)
1238 {
1239 return $a['__tree_index'] - $b['__tree_index'];
1240 }
1241
1242 // Build a tree from the item list and return it. Input and output data is
1243 // indexed by item id (nested items in output are recursively stored in 'kids'
1244 // key, which is in turn indexed by id. Functions, which are ready to handle
1245 // tree collapsion/expansion themselves, may request non-zero threshold value
1246 // for smaller resulting tree.
1247 function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
1248 {
1249 $tree = array();
1250 $nodelist = $orig_nodelist;
1251
1252 // index the tree items by their order in $orig_nodelist
1253 $ti = 0;
1254 foreach ($nodelist as &$node)
1255 $node['__tree_index'] = $ti++;
1256
1257 // Array equivalent of traceEntity() function.
1258 $trace = array();
1259 // set kidc and kids only once
1260 foreach (array_keys ($nodelist) as $nodeid)
1261 {
1262 $nodelist[$nodeid]['kidc'] = 0;
1263 $nodelist[$nodeid]['kids'] = array();
1264 }
1265 do
1266 {
1267 $nextpass = FALSE;
1268 foreach (array_keys ($nodelist) as $nodeid)
1269 {
1270 // When adding a node to the working tree, book another
1271 // iteration, because the new item could make a way for
1272 // others onto the tree. Also remove any item added from
1273 // the input list, so iteration base shrinks.
1274 // First check if we can assign directly.
1275 if ($nodelist[$nodeid]['parent_id'] == NULL)
1276 {
1277 $tree[$nodeid] = $nodelist[$nodeid];
1278 $trace[$nodeid] = array(); // Trace to root node is empty
1279 unset ($nodelist[$nodeid]);
1280 $nextpass = TRUE;
1281 }
1282 // Now look if it fits somewhere on already built tree.
1283 elseif (isset ($trace[$nodelist[$nodeid]['parent_id']]))
1284 {
1285 // Trace to a node is a trace to its parent plus parent id.
1286 $trace[$nodeid] = $trace[$nodelist[$nodeid]['parent_id']];
1287 $trace[$nodeid][] = $nodelist[$nodeid]['parent_id'];
1288 pokeNode ($tree, $trace[$nodeid], $nodeid, $nodelist[$nodeid], $threshold);
1289 // path to any other node is made of all parent nodes plus the added node itself
1290 unset ($nodelist[$nodeid]);
1291 $nextpass = TRUE;
1292 }
1293 }
1294 }
1295 while ($nextpass);
1296 if (!$return_main_payload)
1297 return $nodelist;
1298 // update each input node with its backtrace route
1299 foreach ($trace as $nodeid => $route)
1300 $orig_nodelist[$nodeid]['trace'] = $route;
1301 sortTree ($tree, 'treeItemCmp'); // sort the resulting tree by the order in original list
1302 return $tree;
1303 }
1304
1305 // Build a tree from the tag list and return everything _except_ the tree.
1306 // IOW, return taginfo items, which have parent_id set and pointing outside
1307 // of the "normal" tree, which originates from the root.
1308 function getOrphanedTags ()
1309 {
1310 global $taglist;
1311 return treeFromList ($taglist, 0, FALSE);
1312 }
1313
1314 // removes implicit tags from ['etags'] array and fills ['itags'] array
1315 // Replaces call sequence "getExplicitTagsOnly, getImplicitTags"
1316 function sortEntityTags (&$cell)
1317 {
1318 global $taglist;
1319 if (! is_array ($cell['etags']))
1320 throw new InvalidArgException ('$cell[etags]', $cell['etags']);
1321 $cell['itags'] = array();
1322 foreach ($cell['etags'] as $tag_id => $taginfo)
1323 foreach ($taglist[$tag_id]['trace'] as $parent_id)
1324 {
1325 $cell['itags'][$parent_id] = $taglist[$parent_id];
1326 unset ($cell['etags'][$parent_id]);
1327 }
1328 }
1329
1330 // Return the list of missing implicit tags.
1331 function getImplicitTags ($oldtags)
1332 {
1333 global $taglist;
1334 $tmp = array();
1335 foreach ($oldtags as $taginfo)
1336 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1337 // don't call array_unique here, it is in the function we will call now
1338 return buildTagChainFromIds ($tmp);
1339 }
1340
1341 // Minimize the chain: exclude all implicit tags and return the result.
1342 // This function makes use of an external cache with a miss/hit ratio
1343 // about 3/7 (ticket:255).
1344 function getExplicitTagsOnly ($chain)
1345 {
1346 global $taglist, $tagRelCache;
1347 $ret = array();
1348 foreach (array_keys ($chain) as $keyA) // check each A
1349 {
1350 $tagidA = $chain[$keyA]['id'];
1351 // do not include A in result, if A is seen on the trace of any B!=A
1352 foreach (array_keys ($chain) as $keyB)
1353 {
1354 $tagidB = $chain[$keyB]['id'];
1355 if ($tagidA == $tagidB)
1356 continue;
1357 if (!isset ($tagRelCache[$tagidA][$tagidB]))
1358 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
1359 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1360 continue 2; // skip this A
1361 }
1362 $ret[] = $chain[$keyA];
1363 }
1364 return $ret;
1365 }
1366
1367 // Check, if the given tag is present on the chain (will only work
1368 // for regular tags with tag ID set.
1369 function tagOnChain ($taginfo, $tagchain)
1370 {
1371 if (!isset ($taginfo['id']))
1372 return FALSE;
1373 foreach ($tagchain as $test)
1374 if ($test['id'] == $taginfo['id'])
1375 return TRUE;
1376 return FALSE;
1377 }
1378
1379 function tagNameOnChain ($tagname, $tagchain)
1380 {
1381 foreach ($tagchain as $test)
1382 if ($test['tag'] == $tagname)
1383 return TRUE;
1384 return FALSE;
1385 }
1386
1387 // Return TRUE, if two tags chains differ (order of tags doesn't matter).
1388 // Assume, that neither of the lists contains duplicates.
1389 // FIXME: a faster, than O(x^2) method is possible for this calculation.
1390 function tagChainCmp ($chain1, $chain2)
1391 {
1392 if (count ($chain1) != count ($chain2))
1393 return TRUE;
1394 foreach ($chain1 as $taginfo1)
1395 if (!tagOnChain ($taginfo1, $chain2))
1396 return TRUE;
1397 return FALSE;
1398 }
1399
1400 function redirectIfNecessary ()
1401 {
1402 global
1403 $trigger,
1404 $pageno,
1405 $tabno;
1406 @session_start();
1407 if
1408 (
1409 ! isset ($_REQUEST['tab']) and
1410 isset ($_SESSION['RTLT'][$pageno]) and
1411 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1412 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) and
1413 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1414 )
1415 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname']));
1416
1417 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
1418 if
1419 (
1420 isset ($trigger[$pageno][$tabno]) and
1421 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1422 )
1423 {
1424 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
1425 redirectUser (buildRedirectURL ($pageno, 'default'));
1426 }
1427 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember']))
1428 unset ($_SESSION['RTLT'][$pageno]['dont_remember']);
1429 // store the last visited tab name
1430 if (isset ($_REQUEST['tab']))
1431 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time());
1432 session_commit(); // if we are continuing to run, unlock session data
1433 }
1434
1435 function prepareNavigation()
1436 {
1437 global
1438 $pageno,
1439 $tabno;
1440 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1441
1442 if (isset ($_REQUEST['tab']))
1443 $tabno = $_REQUEST['tab'];
1444 else
1445 $tabno = 'default';
1446 }
1447
1448 function fixContext ($target = NULL)
1449 {
1450 global
1451 $pageno,
1452 $auto_tags,
1453 $expl_tags,
1454 $impl_tags,
1455 $target_given_tags,
1456 $user_given_tags,
1457 $etype_by_pageno,
1458 $page;
1459
1460 if ($target !== NULL)
1461 {
1462 $target_given_tags = $target['etags'];
1463 // Don't reset autochain, because auth procedures could push stuff there in.
1464 // Another important point is to ignore 'user' realm, so we don't infuse effective
1465 // context with autotags of the displayed account.
1466 if ($target['realm'] != 'user')
1467 $auto_tags = array_merge ($auto_tags, $target['atags']);
1468 }
1469 elseif (array_key_exists ($pageno, $etype_by_pageno))
1470 {
1471 // Each page listed in the map above requires one uint argument.
1472 $target = spotEntity ($etype_by_pageno[$pageno], getBypassValue());
1473 $target_given_tags = $target['etags'];
1474 if ($target['realm'] != 'user')
1475 $auto_tags = array_merge ($auto_tags, $target['atags']);
1476 }
1477 elseif ($pageno == 'ipaddress' && $net = spotNetworkByIP (ip_parse (getBypassValue())))
1478 {
1479 // IP addresses inherit context tags from their parent networks
1480 $target_given_tags = $net['etags'];
1481 $auto_tags = array_merge ($auto_tags, $net['atags']);
1482 }
1483 // Explicit and implicit chains should be normally empty at this point, so
1484 // overwrite the contents anyway.
1485 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1486 $impl_tags = getImplicitTags ($expl_tags);
1487 }
1488
1489 # Merge e/i/a-tags of the given cell structures into current context, when
1490 # these aren't there yet.
1491 function spreadContext ($extracell)
1492 {
1493 global
1494 $auto_tags,
1495 $expl_tags,
1496 $impl_tags,
1497 $target_given_tags,
1498 $user_given_tags;
1499 foreach ($extracell['atags'] as $taginfo)
1500 if (! tagNameOnChain ($taginfo['tag'], $auto_tags))
1501 $auto_tags[] = $taginfo;
1502 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']);
1503 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1504 $impl_tags = getImplicitTags ($expl_tags);
1505 }
1506
1507 # return a structure suitable for feeding into restoreContext()
1508 function getContext()
1509 {
1510 global
1511 $auto_tags,
1512 $expl_tags,
1513 $impl_tags,
1514 $target_given_tags;
1515 return array
1516 (
1517 'auto_tags' => $auto_tags,
1518 'expl_tags' => $expl_tags,
1519 'impl_tags' => $impl_tags,
1520 'target_given_tags' => $target_given_tags,
1521 );
1522 }
1523
1524 function restoreContext ($ctx)
1525 {
1526 global
1527 $auto_tags,
1528 $expl_tags,
1529 $impl_tags,
1530 $target_given_tags;
1531 $auto_tags = $ctx['auto_tags'];
1532 $expl_tags = $ctx['expl_tags'];
1533 $impl_tags = $ctx['impl_tags'];
1534 $target_given_tags = $ctx['target_given_tags'];
1535 }
1536
1537 // Take a list of user-supplied tag IDs to build a list of valid taginfo
1538 // records indexed by tag IDs (tag chain).
1539 function buildTagChainFromIds ($tagidlist)
1540 {
1541 global $taglist;
1542 $ret = array();
1543 foreach (array_unique ($tagidlist) as $tag_id)
1544 if (isset ($taglist[$tag_id]))
1545 $ret[] = $taglist[$tag_id];
1546 return $ret;
1547 }
1548
1549 function buildTagIdsFromChain ($tagchain)
1550 {
1551 $ret = array();
1552 foreach ($tagchain as $taginfo)
1553 $ret[] = $taginfo['id'];
1554 return array_unique ($ret);
1555 }
1556
1557 // Process a given tag tree and return only meaningful branches. The resulting
1558 // (sub)tree will have refcnt leaves on every last branch.
1559 function getObjectiveTagTree ($tree, $realm, $preselect)
1560 {
1561 $self = __FUNCTION__;
1562 $ret = array();
1563 foreach ($tree as $taginfo)
1564 {
1565 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1566 // If the current node addresses something, add it to the result
1567 // regardless of how many sub-nodes it features.
1568 if
1569 (
1570 isset ($taginfo['refcnt'][$realm]) or
1571 count ($subsearch) > 1 or
1572 in_array ($taginfo['id'], $preselect)
1573 )
1574 $ret[] = array
1575 (
1576 'id' => $taginfo['id'],
1577 'tag' => $taginfo['tag'],
1578 'parent_id' => $taginfo['parent_id'],
1579 'refcnt' => $taginfo['refcnt'],
1580 'kids' => $subsearch
1581 );
1582 else
1583 $ret = array_merge ($ret, $subsearch);
1584 }
1585 return $ret;
1586 }
1587
1588 // Preprocess tag tree to get only tags which can effectively reduce given filter result,
1589 // than passes shrinked tag tree to getObjectiveTagTree and return its result.
1590 // This makes sense only if andor mode is 'and', otherwise function does not modify tree.
1591 // 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1592 // 'Effectively' means reduce to non-empty result.
1593 function getShrinkedTagTree($entity_list, $realm, $preselect) {
1594 global $tagtree;
1595 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
1596 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1597
1598 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1599 foreach ($entity_list as $entity)
1600 {
1601 foreach ($entity['etags'] as $etag)
1602 if (! array_key_exists($etag['id'], $used_tags))
1603 $used_tags[$etag['id']] = 1;
1604 else
1605 $used_tags[$etag['id']]++;
1606
1607 foreach ($entity['itags'] as $itag)
1608 if (! array_key_exists($itag['id'], $used_tags))
1609 $used_tags[$itag['id']] = 0;
1610 }
1611
1612 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1613 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1614 }
1615
1616 // deletes item from tag subtree unless it exists in $used_tags and not preselected
1617 function shrinkSubtree($tree, $used_tags, $preselect, $realm) {
1618 $self = __FUNCTION__;
1619
1620 foreach($tree as $i => &$item) {
1621 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1622 $item['kidc'] = count($item['kids']);
1623 if
1624 (
1625 ! array_key_exists($item['id'], $used_tags) &&
1626 ! in_array($item['id'], $preselect['tagidlist']) &&
1627 ! $item['kidc']
1628 )
1629 unset($tree[$i]);
1630 else {
1631 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']])
1632 $item['refcnt'][$realm] = $used_tags[$item['id']];
1633 else
1634 unset($item['refcnt'][$realm]);
1635 }
1636 }
1637 return $tree;
1638 }
1639
1640 // Get taginfo record by tag name, return NULL, if record doesn't exist.
1641 function getTagByName ($target_name)
1642 {
1643 global $taglist;
1644 foreach ($taglist as $taginfo)
1645 if ($taginfo['tag'] == $target_name)
1646 return $taginfo;
1647 return NULL;
1648 }
1649
1650 // Merge two chains, filtering dupes out. Return the resulting superset.
1651 function mergeTagChains ($chainA, $chainB)
1652 {
1653 // $ret = $chainA;
1654 // Reindex by tag id in any case.
1655 $ret = array();
1656 foreach ($chainA as $tag)
1657 $ret[$tag['id']] = $tag;
1658 foreach ($chainB as $tag)
1659 if (!isset ($ret[$tag['id']]))
1660 $ret[$tag['id']] = $tag;
1661 return $ret;
1662 }
1663
1664 # Return a list consisting of tag ID of the given tree node and IDs of all
1665 # nodes it contains.
1666 function getTagIDListForNode ($treenode)
1667 {
1668 $self = __FUNCTION__;
1669 $ret = array ($treenode['id']);
1670 foreach ($treenode['kids'] as $item)
1671 $ret = array_merge ($ret, $self ($item));
1672 return $ret;
1673 }
1674
1675 function getCellFilter ()
1676 {
1677 global $sic;
1678 global $pageno;
1679 $andor_used = FALSE;
1680 @session_start();
1681 // if the page is submitted we get an andor value so we know they are trying to start a new filter or clearing the existing one.
1682 if (isset($_REQUEST['andor']))
1683 $andor_used = TRUE;
1684 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST))
1685 unset($_SESSION[$pageno]); // delete saved filter
1686
1687 // otherwise inject saved filter to the $_REQUEST and $sic vars
1688 elseif (isset ($_SESSION[$pageno]['filter']) and is_array ($_SESSION[$pageno]['filter']) and getConfigVar ('STATIC_FILTER') == 'yes')
1689 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param)
1690 {
1691 $param = str_replace ('[]', '', $param, $is_array);
1692 if (! isset ($_REQUEST[$param]) and isset ($_SESSION[$pageno]['filter'][$param]) and (!$is_array or is_array ($_SESSION[$pageno]['filter'][$param])))
1693 {
1694 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param];
1695 if (! $is_array)
1696 $sic[$param] = $_REQUEST[$param];
1697 }
1698 }
1699
1700 $ret = array
1701 (
1702 'tagidlist' => array(),
1703 'tnamelist' => array(),
1704 'pnamelist' => array(),
1705 'negatedlist' => array(),
1706 'andor' => '',
1707 'text' => '',
1708 'extratext' => '',
1709 'expression' => array(),
1710 'urlextra' => '', // Just put text here and let makeHref call urlencode().
1711 'is_empty' => TRUE,
1712 );
1713 switch (TRUE)
1714 {
1715 case (!isset ($_REQUEST['andor'])):
1716 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR');
1717 break;
1718 case ($_REQUEST['andor'] == 'and'):
1719 case ($_REQUEST['andor'] == 'or'):
1720 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor'];
1721 $ret['andor'] = $andor = $_REQUEST['andor'];
1722 break;
1723 default:
1724 showWarning ('Invalid and/or switch value in submitted form');
1725 return NULL;
1726 }
1727 // Both tags and predicates, which don't exist, should be
1728 // handled somehow. Discard them silently for now.
1729 global $taglist, $pTable;
1730 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param)
1731 if (isset ($_REQUEST[$param]) and is_array ($_REQUEST[$param]))
1732 {
1733 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param];
1734 foreach ($_REQUEST[$param] as $req_key)
1735 {
1736 if (strpos ($param, 'ft') !== FALSE)
1737 {
1738 // param is a taglist
1739 if (! isset ($taglist[$req_key]))
1740 continue;
1741 $ret['tagidlist'][] = $req_key;
1742 $ret['tnamelist'][] = $taglist[$req_key]['tag'];
1743 $text = '{' . $taglist[$req_key]['tag'] . '}';
1744 }
1745 else
1746 {
1747 // param is a predicate list
1748 if (! isset ($pTable[$req_key]))
1749 continue;
1750 $ret['pnamelist'][] = $req_key;
1751 $text = '[' . $req_key . ']';
1752 }
1753 if (strpos ($param, 'nf') === 0)
1754 {
1755 $text = "not $text";
1756 $ret['negatedlist'][] = $req_key;
1757 }
1758 if (! empty ($ret['text']))
1759 {
1760 $andor_used = TRUE;
1761 $ret['text'] .= " $andor ";
1762 }
1763 $ret['text'] .= $text;
1764 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key;
1765 }
1766 }
1767 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1768 if (isset ($sic['cfe']))
1769 {
1770 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe'];
1771 // Only consider extra text, when it is a correct RackCode expression.
1772 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1773 if ($parse['result'] == 'ACK')
1774 {
1775 $ret['extratext'] = trim ($sic['cfe']);
1776 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1777 }
1778 }
1779 $finaltext = array();
1780 if (strlen ($ret['text']))
1781 $finaltext[] = '(' . $ret['text'] . ')';
1782 if (strlen ($ret['extratext']))
1783 $finaltext[] = '(' . $ret['extratext'] . ')';
1784 $andor_used = $andor_used || (count($finaltext) > 1);
1785 $finaltext = implode (' ' . $andor . ' ', $finaltext);
1786 if (strlen ($finaltext))
1787 {
1788 $ret['is_empty'] = FALSE;
1789 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1790 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
1791 // It's not quite fair enough to put the blame of the whole text onto
1792 // non-empty "extra" portion of it, but it's the only user-generated portion
1793 // of it, thus the most probable cause of parse error.
1794 if (strlen ($ret['extratext']))
1795 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
1796 }
1797 if (! $andor_used)
1798 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1799 else
1800 $ret['urlextra'] .= '&andor=' . $ret['andor'];
1801 return $ret;
1802 }
1803
1804 function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array())
1805 {
1806 global $page, $pageno, $tabno;
1807 if ($nextpage === NULL)
1808 $nextpage = $pageno;
1809 if ($nexttab === NULL)
1810 $nexttab = $tabno;
1811 $url = "index.php?page=${nextpage}&tab=${nexttab}";
1812
1813 if ($nextpage === $pageno)
1814 fillBypassValues ($nextpage, $moreArgs);
1815 foreach ($moreArgs as $arg => $value)
1816 if (is_array ($value))
1817 foreach ($value as $v)
1818 $url .= '&' . urlencode ($arg . '[]') . '=' . urlencode ($v);
1819 elseif ($arg != 'module')
1820 $url .= '&' . urlencode ($arg) . '=' . urlencode ($value);
1821 return $url;
1822 }
1823
1824 // store the accumulated message list into he $SESSION array to display them later
1825 function backupLogMessages()
1826 {
1827 global $log_messages;
1828 if (! empty ($log_messages))
1829 {
1830 @session_start();
1831 $_SESSION['log'] = $log_messages;
1832 }
1833 }
1834
1835 function redirectUser ($url)
1836 {
1837 backupLogMessages();
1838 header ("Location: " . $url);
1839 die;
1840 }
1841
1842 function getRackCodeStats ()
1843 {
1844 global $rackCode;
1845 $defc = $grantc = $modc = 0;
1846 foreach ($rackCode as $s)
1847 switch ($s['type'])
1848 {
1849 case 'SYNT_DEFINITION':
1850 $defc++;
1851 break;
1852 case 'SYNT_GRANT':
1853 $grantc++;
1854 break;
1855 case 'SYNT_CTXMOD':
1856 $modc++;
1857 break;
1858 default:
1859 break;
1860 }
1861 $ret = array
1862 (
1863 'Definition sentences' => $defc,
1864 'Grant sentences' => $grantc,
1865 'Context mod sentences' => $modc
1866 );
1867 return $ret;
1868 }
1869
1870 function getRackImageWidth ()
1871 {
1872 global $rtwidth;
1873 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
1874 }
1875
1876 function getRackImageHeight ($units)
1877 {
1878 return 3 + 3 + $units * 2;
1879 }
1880
1881 // Indicate occupation state of each IP address: none, ordinary or problematic.
1882 function markupIPAddrList (&$addrlist)
1883 {
1884 foreach (array_keys ($addrlist) as $ip_bin)
1885 {
1886 $refc = array
1887 (
1888 'shared' => 0, // virtual
1889 'virtual' => 0, // loopback
1890 'regular' => 0, // connected host
1891 'router' => 0 // connected gateway
1892 );
1893 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1894 $refc[$a['type']]++;
1895 $nvirtloopback = ($refc['shared'] + $refc['virtual'] + $refc['router'] > 0) ? 1 : 0; // modulus of virtual + shared + router
1896 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1897 $nrealms = $nreserved + $nvirtloopback + $refc['regular']; // last is connected allocation
1898
1899 if ($nrealms == 1)
1900 $addrlist[$ip_bin]['class'] = 'trbusy';
1901 elseif ($nrealms > 1)
1902 $addrlist[$ip_bin]['class'] = 'trerror';
1903 elseif (! empty ($addrlist[$ip_bin]['vslist']) or ! empty ($addrlist[$ip_bin]['rsplist']))
1904 $addrlist[$ip_bin]['class'] = 'trbusy';
1905 else
1906 $addrlist[$ip_bin]['class'] = '';
1907 }
1908 }
1909
1910 // Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
1911 function findRouters ($addrlist)
1912 {
1913 $ret = array();
1914 foreach ($addrlist as $addr)
1915 foreach ($addr['allocs'] as $alloc)
1916 if ($alloc['type'] == 'router')
1917 $ret[] = array
1918 (
1919 'id' => $alloc['object_id'],
1920 'iface' => $alloc['name'],
1921 'dname' => $alloc['object_name'],
1922 'ip_bin' => $addr['ip_bin'],
1923 );
1924 return $ret;
1925 }
1926
1927 // Compare networks. When sorting a tree, the records on the list will have
1928 // distinct base IP addresses.
1929 // valid return values are: 1, 0, -1, -2
1930 // -2 has special meaning: $netA includes $netB
1931 // "The comparison function must return an integer less than, equal to, or greater
1932 // than zero if the first argument is considered to be respectively less than,
1933 // equal to, or greater than the second." (c) PHP manual
1934 function IPNetworkCmp ($netA, $netB)
1935 {
1936 if (strlen ($netA['ip_bin']) !== strlen ($netB['ip_bin']))
1937 return strlen ($netA['ip_bin']) < strlen ($netB['ip_bin']) ? -1 : 1;
1938 $ret = strcmp ($netA['ip_bin'], $netB['ip_bin']);
1939 $ret = ($ret > 0 ? 1 : ($ret < 0 ? -1 : 0));
1940 if ($ret == 0)
1941 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0);
1942 if ($ret == -1 and $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin']))
1943 $ret = -2;
1944 return $ret;
1945 }
1946
1947 function IPNetContainsOrEqual ($netA, $netB)
1948 {
1949 $res = IPNetworkCmp ($netA, $netB);
1950 return ($res == -2 || $res == 0);
1951 }
1952
1953 function IPNetContains ($netA, $netB)
1954 {
1955 return (-2 == IPNetworkCmp ($netA, $netB));
1956 }
1957
1958 function ip_in_range ($ip_bin, $range)
1959 {
1960 return ($ip_bin & $range['mask_bin']) === $range['ip_bin'];
1961 }
1962
1963 // Modify the given tag tree so, that each level's items are sorted alphabetically.
1964 function sortTree (&$tree, $sortfunc = '')
1965 {
1966 if (!strlen ($sortfunc))
1967 return;
1968 $self = __FUNCTION__;
1969 usort ($tree, $sortfunc);
1970 // Don't make a mistake of directly iterating over the items of current level, because this way
1971 // the sorting will be performed on a _copy_ if each item, not the item itself.
1972 foreach (array_keys ($tree) as $tagid)
1973 $self ($tree[$tagid]['kids'], $sortfunc);
1974 }
1975
1976 function iptree_fill (&$netdata)
1977 {
1978 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
1979 return;
1980 // If we really have nested prefixes, they must fit into the tree.
1981 $worktree = $netdata;
1982 foreach ($netdata['kids'] as $pfx)
1983 iptree_embed ($worktree, $pfx);
1984 $netdata['kids'] = iptree_construct ($worktree);
1985 $netdata['kidc'] = count ($netdata['kids']);
1986 }
1987
1988 function iptree_construct ($node)
1989 {
1990 $self = __FUNCTION__;
1991
1992 if (!isset ($node['right']))
1993 {
1994 if (!isset ($node['kids']))
1995 {
1996 $node['kids'] = array();
1997 $node['kidc'] = 0;
1998 $node['name'] = '';
1999 }
2000 return array ($node);
2001 }
2002 else
2003 return array_merge ($self ($node['left']), $self ($node['right']));
2004 }
2005
2006 function ip_format ($ip_bin)
2007 {
2008 switch (strlen ($ip_bin))
2009 {
2010 case 4: return ip4_format ($ip_bin);
2011 case 16: return ip6_format ($ip_bin);
2012 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2013 }
2014 }
2015
2016 function ip4_format ($ip_bin)
2017 {
2018 if (4 != ($len = strlen ($ip_bin)))
2019 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2020 return implode ('.', unpack ('C*', $ip_bin));
2021 }
2022
2023 function ip6_format ($ip_bin)
2024 {
2025 // maybe this is IPv6-to-IPv4 address?
2026 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
2027 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4)));
2028
2029 $result = array();
2030 $hole_index = NULL;
2031 $max_hole_index = NULL;
2032 $hole_length = 0;
2033 $max_hole_length = 0;
2034
2035 for ($i = 0; $i < 8; $i++)
2036 {
2037 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2));
2038 $value = array_shift ($unpacked);
2039 $result[] = dechex ($value & 0xffff);
2040 if ($value != 0)
2041 {
2042 unset ($hole_index);
2043 $hole_length = 0;
2044 }
2045 else
2046 {
2047 if (! isset ($hole_index))
2048 $hole_index = $i;
2049 if (++$hole_length >= $max_hole_length)
2050 {
2051 $max_hole_index = $hole_index;
2052 $max_hole_length = $hole_length;
2053 }
2054 }
2055 }
2056 if (isset ($max_hole_index))
2057 {
2058 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
2059 if ($max_hole_index == 0 && $max_hole_length == 8)
2060 return '::';
2061 elseif ($max_hole_index == 0)
2062 return ':' . implode (':', $result);
2063 elseif ($max_hole_index + $max_hole_length == 8)
2064 return implode (':', $result) . ':';
2065 }
2066 return implode (':', $result);
2067 }
2068
2069 function ip_parse ($ip)
2070 {
2071 if (FALSE !== strpos ($ip, ':'))
2072 return ip6_parse ($ip);
2073 else
2074 return ip4_parse ($ip);
2075 }
2076
2077 function ip4_parse ($ip)
2078 {
2079 if (FALSE === ($int = ip2long ($ip)))
2080 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address");
2081 else
2082 return pack ('N', $int);
2083 }
2084
2085 // returns 16-byte string ip_bin
2086 // throws exception if unable to parse
2087 function ip6_parse ($ip)
2088 {
2089 do {
2090 if (empty ($ip))
2091 break;
2092
2093 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
2094 // remove one of double beginning/tailing colons
2095 if (substr ($ip, 0, 2) == '::')
2096 $ip = substr ($ip, 1);
2097 elseif (substr ($ip, -2, 2) == '::')
2098 $ip = substr ($ip, 0, strlen ($ip) - 1);
2099
2100 $tokens = explode (':', $ip);
2101 $last_token = $tokens[count ($tokens) - 1];
2102 $split = explode ('.', $last_token);
2103 if (count ($split) == 4)
2104 {
2105 $hex_tokens = array();
2106 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
2107 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
2108 array_splice ($tokens, -1, 1, $hex_tokens);
2109 }
2110 if (count ($tokens) > 8)
2111 break;
2112 for ($i = 0; $i < count ($tokens); $i++)
2113 {
2114 if ($tokens[$i] != '')
2115 {
2116 if (! set_word_value ($result, $i, $tokens[$i]))
2117 break;
2118 }
2119 else
2120 {
2121 $k = 8; //index in result string (last word)
2122 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
2123 if ($tokens[$j] == '')
2124 break;
2125 elseif (! set_word_value ($result, --$k, $tokens[$j]))
2126 break;
2127 if ($i != $j)
2128 break; //error, more than 1 '::' range
2129 break;
2130 }
2131 }
2132 if (! isset ($k) && count ($tokens) != 8)
2133 break;
2134 return $result;
2135 } while (FALSE);
2136
2137 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address");
2138 }
2139
2140 function ip_get_arpa ($ip_bin)
2141 {
2142 switch (strlen ($ip_bin))
2143 {
2144 case 4: return ip4_get_arpa ($ip_bin);
2145 case 16: return ip6_get_arpa ($ip_bin);
2146 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2147 }
2148 }
2149
2150 function ip4_get_arpa ($ip_bin)
2151 {
2152 $ret = '';
2153 for ($i = 3; $i >= 0; $i--)
2154 $ret .= ord($ip_bin[$i]) . '.';
2155 return $ret . 'in-addr.arpa';
2156 }
2157
2158 function ip6_get_arpa ($ip_bin)
2159 {
2160 $ret = '';
2161 $hex = implode ('', unpack('H32', $ip_bin));
2162 for ($i = 31; $i >= 0; $i--)
2163 $ret .= $hex[$i] . '.';
2164 return $ret . 'ip6.arpa';
2165 }
2166
2167 function set_word_value (&$haystack, $nword, $hexvalue)
2168 {
2169 // check that $hexvalue is like /^[0-9a-fA-F]*$/
2170 for ($i = 0; $i < strlen ($hexvalue); $i++)
2171 {
2172 $char = ord ($hexvalue[$i]);
2173 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
2174 return FALSE;
2175 }
2176 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
2177 return TRUE;
2178 }
2179
2180 // returns binary IP or FALSE
2181 function ip_checkparse ($ip)
2182 {
2183 try
2184 {
2185 return ip_parse ($ip);
2186 }
2187 catch (InvalidArgException $e)
2188 {
2189 return FALSE;
2190 }
2191 }
2192
2193 // returns binary IP or FALSE
2194 function ip4_checkparse ($ip)
2195 {
2196 try
2197 {
2198 return ip4_parse ($ip);
2199 }
2200 catch (InvalidArgException $e)
2201 {
2202 return FALSE;
2203 }
2204 }
2205
2206 // returns binary IP or FALSE
2207 function ip6_checkparse ($ip)
2208 {
2209 try
2210 {
2211 return ip6_parse ($ip);
2212 }
2213 catch (InvalidArgException $e)
2214 {
2215 return FALSE;
2216 }
2217 }
2218
2219 function ip4_int2bin ($ip_int)
2220 {
2221 return pack ('N', $ip_int + 0);
2222 }
2223
2224 function ip4_bin2int ($ip_bin)
2225 {
2226 if (4 != strlen ($ip_bin))
2227 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2228 $list = unpack ('N', $ip_bin);
2229 return array_shift ($list);
2230 }
2231
2232 // Use this function only when you need to export binary ip out of PHP running context (e.g., DB)
2233 // !DO NOT perform arithmetic and bitwise operations with the result of this function!
2234 function ip4_bin2db ($ip_bin)
2235 {
2236 $ip_int = ip4_bin2int ($ip_bin);
2237 if ($ip_int < 0)
2238 return sprintf ('%u', 0x00000000 + $ip_int);
2239 else
2240 return $ip_int;
2241 }
2242
2243 function ip_last ($net)
2244 {
2245 return $net['ip_bin'] | ~$net['mask_bin'];
2246 }
2247
2248 function ip_next ($ip_bin)
2249 {
2250 $ret = $ip_bin;
2251 $p = 1;
2252 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2253 {
2254 $oct = $p + ord ($ret[$i]);
2255 $ret[$i] = chr ($oct & 0xff);
2256 if ($oct <= 255 and $oct >= 0)
2257 break;
2258 }
2259 return $ret;
2260 }
2261
2262 function ip_prev ($ip_bin)
2263 {
2264 $ret = $ip_bin;
2265 $p = -1;
2266 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2267 {
2268 $oct = $p + ord ($ret[$i]);
2269 $ret[$i] = chr ($oct & 0xff);
2270 if ($oct <= 255 and $oct >= 0)
2271 break;
2272 }
2273 return $ret;
2274 }
2275
2276 function ip4_range_size ($range)
2277 {
2278 return ip4_mask_size ($range['mask']);
2279 }
2280
2281 function ip4_mask_size ($mask)
2282 {
2283 switch (TRUE)
2284 {
2285 case ($mask > 1 && $mask <= 32):
2286 return (0x7fffffff >> ($mask - 1)) + 1;
2287 // constants below are not representable in 32-bit PHP's int type,
2288 // so they are literally hardcoded and returned as strings on 32-bit architecture.
2289 case ($mask == 1):
2290 return 2147483648;
2291 case ($mask == 0):
2292 return 4294967296;
2293 default:
2294 throw new InvalidArgException ('mask', $mask, 'Invalid IPv4 prefix length');
2295 }
2296 }
2297
2298 // returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin'
2299 function constructIPRange ($ip_bin, $mask)
2300 {
2301 $node = array();
2302 switch (strlen ($ip_bin))
2303 {
2304 case 4: // IPv4
2305 if ($mask < 0 || $mask > 32)
2306 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length");
2307 $node['mask_bin'] = ip4_mask ($mask);
2308 $node['mask'] = $mask;
2309 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2310 $node['ip'] = ip4_format ($node['ip_bin']);
2311 break;
2312 case 16: // IPv6
2313 if ($mask < 0 || $mask > 128)
2314 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length");
2315 $node['mask_bin'] = ip6_mask ($mask);
2316 $node['mask'] = $mask;
2317 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2318 $node['ip'] = ip6_format ($node['ip_bin']);
2319 break;
2320 default:
2321 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2322 }
2323 return $node;
2324 }
2325
2326 // Return minimal IP address structure
2327 function constructIPAddress ($ip_bin)
2328 {
2329 // common v4/v6 part
2330 $ret = array
2331 (
2332 'ip' => ip_format ($ip_bin),
2333 'ip_bin' => $ip_bin,
2334 'name' => '',
2335 'comment' => '',
2336 'reserved' => 'no',
2337 'allocs' => array(),
2338 'vslist' => array(),
2339 'rsplist' => array(),
2340 );
2341
2342 // specific v4 part
2343 if (strlen ($ip_bin) == 4)
2344 $ret = array_merge
2345 (
2346 $ret,
2347 array
2348 (
2349 'outpf' => array(),
2350 'inpf' => array(),
2351 )
2352 );
2353 return $ret;
2354 }
2355
2356 function iptree_embed (&$node, $pfx)
2357 {
2358 $self = __FUNCTION__;
2359
2360 // hit?
2361 if (0 == IPNetworkCmp ($node, $pfx))
2362 {
2363 $node = $pfx;
2364 return;
2365 }
2366 if ($node['mask'] == $pfx['mask'])
2367 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
2368
2369 // split?
2370 if (!isset ($node['right']))
2371 {
2372 $node['left'] = constructIPRange ($node['ip_bin'], $node['mask'] + 1);
2373 $node['right'] = constructIPRange (ip_last ($node), $node['mask'] + 1);
2374 }
2375
2376 if (IPNetContainsOrEqual ($node['left'], $pfx))
2377 $self ($node['left'], $pfx);
2378 elseif (IPNetContainsOrEqual ($node['right'], $pfx))
2379 $self ($node['right'], $pfx);
2380 else
2381 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
2382 }
2383
2384 function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
2385 {
2386 if (!strlen ($func))
2387 return;
2388 $self = __FUNCTION__;
2389 foreach (array_keys ($tree) as $key)
2390 {
2391 $func ($tree[$key]);
2392 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
2393 continue;
2394 $self ($tree[$key]['kids'], $func);
2395 }
2396 }
2397
2398 function nodeIsCollapsed ($node)
2399 {
2400 return $node['symbol'] == 'node-collapsed';
2401 }
2402
2403 // sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node
2404 // 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively
2405 // 'own_addrlist' is empty if the node network has no children
2406 function loadIPAddrList (&$node)
2407 {
2408 $node['addrlist'] = scanIPSpace (array (array ('first' => $node['ip_bin'], 'last' => ip_last ($node))));
2409
2410 if (! isset ($node['id']))
2411 $node['own_addrlist'] = $node['addrlist'];
2412 else
2413 {
2414 if ($node['kidc'] == 0)
2415 $node['own_addrlist'] = $node['addrlist'];
2416 //$node['own_addrlist'] = array();
2417 else
2418 {
2419 $node['own_addrlist'] = array();
2420 // node has childs
2421 foreach ($node['spare_ranges'] as $mask => $spare_list)
2422 foreach ($spare_list as $spare_ip)
2423 {
2424 $spare_range = constructIPRange ($spare_ip, $mask);
2425 foreach ($node['addrlist'] as $bin_ip => $addr)
2426 if (($bin_ip & $spare_range['mask_bin']) == $spare_range['ip_bin'])
2427 $node['own_addrlist'][$bin_ip] = $addr;
2428 }
2429 }
2430 }
2431 $node['addrc'] = count ($node['addrlist']);
2432 $node['own_addrc'] = count ($node['own_addrlist']);
2433 }
2434
2435 // returns the array of structure described by constructIPAddress
2436 function getIPAddress ($ip_bin)
2437 {
2438 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin)));
2439 if (empty ($scanres))
2440 return constructIPAddress ($ip_bin);
2441 markupIPAddrList ($scanres);
2442 return $scanres[$ip_bin];
2443 }
2444
2445 function getIPv4Address ($ip_bin)
2446 {
2447 if (strlen ($ip_bin) != 4)
2448 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2449 return getIPAddress ($ip_bin);
2450 }
2451
2452 function getIPv6Address ($ip_bin)
2453 {
2454 if (strlen ($ip_bin) != 16)
2455 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2456 return getIPAddress ($ip_bin);
2457 }
2458
2459 function makeIPTree ($netlist)
2460 {
2461 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
2462 // so perform necessary pre-processing to calculate parent_id of each item of $netlist.
2463 $stack = array();
2464 foreach ($netlist as $net_id => &$net)
2465 {
2466 while (! empty ($stack))
2467 {
2468 $top_id = $stack[count ($stack) - 1];
2469 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top
2470 array_pop ($stack);
2471 else
2472 {
2473 $net['parent_id'] = $top_id;
2474 break;
2475 }
2476 }
2477 if (empty ($stack))
2478 $net['parent_id'] = NULL;
2479 array_push ($stack, $net_id);
2480 }
2481 unset ($stack);
2482
2483 $tree = treeFromList ($netlist); // medium call
2484 return $tree;
2485 }
2486
2487 function prepareIPTree ($netlist, $expanded_id = 0)
2488 {
2489 $tree = makeIPTree ($netlist);
2490 // complement the tree before markup to make the spare networks have "symbol" set
2491 treeApplyFunc ($tree, 'iptree_fill');
2492 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2493 return $tree;
2494 }
2495
2496 # Traverse IPv4/IPv6 tree and return a list of all networks, which
2497 # exist in DB and don't have any sub-networks.
2498 function getTerminalNetworks ($tree)
2499 {
2500 $self = __FUNCTION__;
2501 $ret = array();
2502 foreach ($tree as $node)
2503 if ($node['kidc'] == 0 and isset ($node['realm']))
2504 $ret[] = $node;
2505 else
2506 $ret = array_merge ($ret, $self ($node['kids']));
2507 return $ret;
2508 }
2509
2510 // Check all items of the tree recursively, until the requested target id is
2511 // found. Mark all items leading to this item as "expanded", collapsing all
2512 // the rest, which exceed the given threshold (if the threshold is given).
2513 function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2514 {
2515 $self = __FUNCTION__;
2516 $ret = FALSE;
2517 foreach (array_keys ($tree) as $key)
2518 {
2519 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
2520 $below = $self ($tree[$key]['kids'], $threshold, $target);
2521 $expand_enabled = ($target !== 'NONE');
2522 if (!$tree[$key]['kidc']) // terminal node
2523 $tree[$key]['symbol'] = 'spacer';
2524 elseif ($expand_enabled and $tree[$key]['kidc'] < $threshold)
2525 $tree[$key]['symbol'] = 'node-expanded-static';
2526 elseif ($expand_enabled and ($here or $below))
2527 $tree[$key]['symbol'] = 'node-expanded';
2528 else
2529 $tree[$key]['symbol'] = 'node-collapsed';
2530 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
2531 }
2532 return $ret;
2533 }
2534
2535 // Convert entity name to human-readable value
2536 function formatEntityName ($name) {
2537 switch ($name)
2538 {
2539 case 'ipv4net':
2540 return 'IPv4 Network';
2541 case 'ipv6net':
2542 return 'IPv6 Network';
2543 case 'ipv4rspool':
2544 return 'IPv4 RS Pool';
2545 case 'ipv4vs':
2546 return 'IPv4 Virtual Service';
2547 case 'object':
2548 return 'Object';
2549 case 'rack':
2550 return 'Rack';
2551 case 'location':
2552 return 'Location';
2553 case 'user':
2554 return 'User';
2555 }
2556 return 'invalid';
2557 }
2558
2559 // Display hrefs for all of a file's parents. If scissors are requested,
2560 // prepend cutting button to each of them.
2561 function serializeFileLinks ($links, $scissors = FALSE)
2562 {
2563 $comma = '';
2564 $ret = '';
2565 foreach ($links as $link_id => $li)
2566 {
2567 switch ($li['entity_type'])
2568 {
2569 case 'ipv4net':
2570 $params = "page=ipv4net&id=";
2571 break;
2572 case 'ipv6net':
2573 $params = "page=ipv6net&id=";
2574 break;
2575 case 'ipv4rspool':
2576 $params = "page=ipv4rspool&pool_id=";
2577 break;
2578 case 'ipv4vs':
2579 $params = "page=ipv4vs&vs_id=";
2580 break;
2581 case 'object':
2582 $params = "page=object&object_id=";
2583 break;
2584 case 'rack':
2585 $params = "page=rack&rack_id=";
2586 break;
2587 case 'location':
2588 $params = "page=location&location_id=";
2589 break;
2590 case 'user':
2591 $params = "page=user&user_id=";
2592 break;
2593 }
2594 $ret .= $comma;
2595 if ($scissors)
2596 {
2597 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
2598 $ret .= getImageHREF ('cut') . '</a> ';
2599 }
2600 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
2601 $comma = '<br>';
2602 }
2603 return $ret;
2604 }
2605
2606 // Convert filesize to appropriate unit and make it human-readable
2607 function formatFileSize ($bytes) {
2608 // bytes
2609 if($bytes < 1024) // bytes
2610 return "${bytes} bytes";
2611
2612 // kilobytes
2613 if ($bytes < 1024000)
2614 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2615
2616 // megabytes
2617 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2618 }
2619
2620 // Reverse of formatFileSize, it converts human-readable value to bytes
2621 function convertToBytes ($value) {
2622 $value = trim($value);
2623 $last = strtolower($value[strlen($value)-1]);
2624 switch ($last)
2625 {
2626 case 'g':
2627 $value *= 1024;
2628 case 'm':
2629 $value *= 1024;
2630 case 'k':
2631 $value *= 1024;
2632 }
2633
2634 return $value;
2635 }
2636
2637 // make "A" HTML element
2638 function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL)
2639 {
2640 global $page, $tab;
2641 if ($text == '')
2642 throw new InvalidArgException ('text', $text);
2643 if (! array_key_exists ($nextpage, $page))
2644 throw new InvalidArgException ('nextpage', $nextpage, 'not found');
2645 $args = array ('page' => $nextpage);
2646 if ($nexttab !== NULL)
2647 {
2648 if (! array_key_exists ($nexttab, $tab[$nextpage]))
2649 throw new InvalidArgException ('nexttab', $nexttab, 'not found');
2650 $args['tab'] = $nexttab;
2651 }
2652 if (array_key_exists ('bypass', $page[$nextpage]))
2653 {
2654 if ($bypass === NULL)
2655 throw new InvalidArgException ('bypass', '(NULL)');
2656 $args[$page[$nextpage]['bypass']] = $bypass;
2657 }
2658 return '<a href="' . makeHref ($args) . '">' . $text . '</a>';
2659 }
2660
2661 // make "HREF" HTML attribute
2662 function makeHref ($params = array())
2663 {
2664 $tmp = array();
2665 foreach ($params as $key => $value)
2666 {
2667 if (is_array ($value))
2668 $key .= "[]";
2669 else
2670 $value = array ($value);
2671 if (!count ($value))
2672 $tmp[] = urlencode ($key) . '=';
2673 else
2674 foreach ($value as $sub_value)
2675 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value);
2676 }
2677 return 'index.php?' . implode ('&', $tmp);
2678 }
2679
2680 function makeHrefProcess ($params = array())
2681 {
2682 global $pageno, $tabno, $page;
2683 $tmp = array();
2684 if (! array_key_exists ('page', $params))
2685 $params['page'] = $pageno;
2686 if (! array_key_exists ('tab', $params))
2687 $params['tab'] = $tabno;
2688 if ($params['page'] === $pageno)
2689 fillBypassValues ($pageno, $params);
2690 foreach ($params as $key => $value)
2691 $tmp[] = urlencode ($key) . '=' . urlencode ($value);
2692 return '?module=redirect&' . implode ('&', $tmp);
2693 }
2694
2695 function makeHrefForHelper ($helper_name, $params = array())
2696 {
2697 $ret = '?module=popup&helper=' . $helper_name;
2698 foreach($params as $key=>$value)
2699 $ret .= '&'.urlencode($key).'='.urlencode($value);
2700 return $ret;
2701 }
2702
2703 // Process the given list of records to build data suitable for printNiftySelect()
2704 // (like it was formerly executed by printSelect()). Screen out vendors according
2705 // to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2706 // selected OPTION is protected from being screened.
2707 function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2708 {
2709 $ret = array();
2710 // Always keep "other" OPTGROUP at the SELECT bottom.
2711 $therest = array();
2712 foreach ($recordList as $dict_key => $dict_value)
2713 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
2714 {
2715 $tmp = explode ('%GSKIP%', $dict_value, 2);
2716 $ret[$tmp[0]][$dict_key] = $tmp[1];
2717 }
2718 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
2719 {
2720 $tmp = explode ('%GPASS%', $dict_value, 2);
2721 $ret[$tmp[0]][$dict_key] = $tmp[1];
2722 }
2723 else
2724 $therest[$dict_key] = $dict_value;
2725 if ($object_type_id != 0)
2726 {
2727 $screenlist = array();
2728 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
2729 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
2730 $screenlist[] = $regs[1];
2731 }
2732 foreach (array_keys ($ret) as $vendor)
2733 if (in_array ($vendor, $screenlist))
2734 {
2735 $ok_to_screen = TRUE;
2736 if ($existing_value)
2737 foreach (array_keys ($ret[$vendor]) as $recordkey)
2738 if ($recordkey == $existing_value)
2739 {
2740 $ok_to_screen = FALSE;
2741 break;
2742 }
2743 if ($ok_to_screen)
2744 unset ($ret[$vendor]);
2745 }
2746 }
2747 $ret['other'] = $therest;
2748 return $ret;
2749 }
2750
2751 function unix2dos ($text)
2752 {
2753 return str_replace ("\n", "\r\n", $text);
2754 }
2755
2756 function buildPredicateTable ($parsetree)
2757 {
2758 $ret = array();
2759 foreach ($parsetree as $sentence)
2760 if ($sentence['type'] == 'SYNT_DEFINITION')
2761 $ret[$sentence['term']] = $sentence['definition'];
2762 // Now we have predicate table filled in with the latest definitions of each
2763 // particular predicate met. This isn't as chik, as on-the-fly predicate
2764 // overloading during allow/deny scan, but quite sufficient for this task.
2765 return $ret;
2766 }
2767
2768 // Take a list of records and filter against given RackCode expression. Return
2769 // the original list intact, if there was no filter requested, but return an
2770 // empty list, if there was an error.
2771 function filterCellList ($list_in, $expression = array())
2772 {
2773 if ($expression === NULL)
2774 return array();
2775 if (!count ($expression))
2776 return $list_in;
2777 $list_out = array();
2778 foreach ($list_in as $item_key => $item_value)
2779 if (TRUE === judgeCell ($item_value, $expression))
2780 $list_out[$item_key] = $item_value;
2781 return $list_out;
2782 }
2783
2784 function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
2785 {
2786 $self = __FUNCTION__;
2787 switch ($expr['type'])
2788 {
2789 // Return true, if given tag is present on the tag chain.
2790 case 'LEX_TAG':
2791 case 'LEX_AUTOTAG':
2792 foreach ($tagchain as $tagInfo)
2793 if ($expr['load'] == $tagInfo['tag'])
2794 return TRUE;
2795 return FALSE;
2796 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
2797 $pname = $expr['load'];
2798 if (!isset ($ptable[$pname]))
2799 {
2800 if (!$silent)
2801 showWarning ("Predicate '${pname}' is referenced before declaration");
2802 return NULL;
2803 }
2804 return $self ($ptable[$pname], $tagchain, $ptable);
2805 case 'LEX_TRUE':
2806 return TRUE;
2807 case 'LEX_FALSE':
2808 return FALSE;
2809 case 'SYNT_NOT_EXPR':
2810 $tmp = $self ($expr['load'], $tagchain, $ptable);
2811 if ($tmp === TRUE)
2812 return FALSE;
2813 elseif ($tmp === FALSE)
2814 return TRUE;
2815 else
2816 return $tmp;
2817 case 'SYNT_AND_EXPR': // binary AND
2818 if (FALSE == $self ($expr['left'], $tagchain, $ptable))
2819 return FALSE; // early failure
2820 return $self ($expr['right'], $tagchain, $ptable);
2821 case 'SYNT_EXPR': // binary OR
2822 if (TRUE == $self ($expr['left'], $tagchain, $ptable))
2823 return TRUE; // early success
2824 return $self ($expr['right'], $tagchain, $ptable);
2825 default:
2826 if (!$silent)
2827 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
2828 return NULL;
2829 break;
2830 }
2831 }
2832
2833 // Tell, if the given expression is true for the given entity. Take complete record on input.
2834 function judgeCell ($cell, $expression)
2835 {
2836 global $pTable;
2837 return eval_expression
2838 (
2839 $expression,
2840 array_merge
2841 (
2842 $cell['etags'],
2843 $cell['itags'],
2844 $cell['atags']
2845 ),
2846 $pTable,
2847 TRUE
2848 );
2849 }
2850
2851 function judgeContext ($expression)
2852 {
2853 global $pTable, $expl_tags, $impl_tags, $auto_tags;
2854 return eval_expression
2855 (
2856 $expression,
2857 array_merge
2858 (
2859 $expl_tags,
2860 $impl_tags,
2861 $auto_tags
2862 ),
2863 $pTable,
2864 TRUE
2865 );
2866 }
2867
2868 // Tell, if a constraint from config option permits given record.
2869 // An undefined $cell means current context.
2870 function considerConfiguredConstraint ($cell, $varname)
2871 {
2872 if (!strlen (getConfigVar ($varname)))
2873 return TRUE; // no restriction
2874 global $parseCache;
2875 if (!isset ($parseCache[$varname]))
2876 // getConfigVar() doesn't re-read the value from DB because of its
2877 // own cache, so there is no race condition here between two calls.
2878 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2879 if ($parseCache[$varname]['result'] != 'ACK')
2880 return FALSE; // constraint set, but cannot be used due to compilation error
2881 if (isset ($cell))
2882 return judgeCell ($cell, $parseCache[$varname]['load']);
2883 else
2884 return judgeContext ($parseCache[$varname]['load']);
2885 }
2886
2887 // Tell, if the given arbitrary RackCode text addresses the given record
2888 // (an empty text matches any record).
2889 // An undefined $cell means current context.
2890 function considerGivenConstraint ($cell, $filtertext)
2891 {
2892 if ($filtertext == '')
2893 return TRUE;
2894 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2895 if ($parse['result'] != 'ACK')
2896 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
2897 if (isset ($cell))
2898 return judgeCell ($cell, $parse['load']);
2899 else
2900 return judgeContext ($parse['load']);
2901 }
2902
2903 // Return list of records in the given realm, which conform to
2904 // the given RackCode expression. If the realm is unknown or text
2905 // doesn't validate as a RackCode expression, return NULL.
2906 // Otherwise (successful scan) return a list of all matched
2907 // records, even if the list is empty (array() !== NULL). If the
2908 // text is an empty string, return all found records in the given
2909 // realm.
2910 function scanRealmByText ($realm = NULL, $ftext = '')
2911 {
2912 switch ($realm)
2913 {
2914 case 'object':
2915 case 'rack':
2916 case 'user':
2917 case 'ipv4net':
2918 case 'ipv6net':
2919 case 'file':
2920 case 'ipv4vs':
2921 case 'ipv4rspool':
2922 case 'vst':
2923 if (!strlen ($ftext = trim ($ftext)))
2924 $fexpr = array();
2925 else
2926 {
2927 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2928 if ($fparse['result'] != 'ACK')
2929 return NULL;
2930 $fexpr = $fparse['load'];
2931 }
2932 return filterCellList (listCells ($realm), $fexpr);
2933 default:
2934 throw new InvalidArgException ('$realm', $realm);
2935 }
2936
2937 }
2938
2939 function getVSTOptions()
2940 {
2941 $ret = array();
2942 foreach (listCells ('vst') as $vst)
2943 $ret[$vst['id']] = niftyString ($vst['description'], 30, FALSE);
2944 return $ret;
2945 }
2946
2947 # Return a 2-dimensional array in the format understood by getNiftySelect(),
2948 # which would contain all VLANs of all VLAN domains except domains, which IDs
2949 # are present in $except_domains argument.
2950 function getAllVLANOptions ($except_domains = array())
2951 {
2952 $ret = array();
2953 foreach (getVLANDomainStats() as $domain)
2954 if (! in_array ($domain['id'], $except_domains))
2955 foreach (getDomainVLANs ($domain['id']) as $vlan)
2956 $ret[$domain['description']]["${domain['id']}-${vlan['vlan_id']}"] =
2957 "${vlan['vlan_id']} (${vlan['netc']}) ${vlan['vlan_descr']}";
2958 return $ret;
2959 }
2960
2961 // Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2962 function dump ($var)
2963 {
2964 echo '<div align=left><pre>';
2965 print_r ($var);
2966 echo '</pre></div>';
2967 }
2968
2969 function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2970 {
2971 global $taglist;
2972 // first build top-N chart...
2973 $toplist = array();
2974 foreach ($taglist as $taginfo)
2975 if (isset ($taginfo['refcnt'][$realm]))
2976 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2977 arsort ($toplist, SORT_NUMERIC);
2978 $ret = array();
2979 $done = 0;
2980 foreach (array_keys ($toplist) as $tag_id)
2981 {
2982 $ret[$tag_id] = $taglist[$tag_id];
2983 if (++$done == $limit)
2984 break;
2985 }
2986 // ...then make sure, that every item of the special list is shown
2987 // (using the same sort order)
2988 $extra = array();
2989 foreach ($special_tags as $taginfo)
2990 if (!array_key_exists ($taginfo['id'], $ret))
2991 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2992 arsort ($extra, SORT_NUMERIC);
2993 foreach (array_keys ($extra) as $tag_id)
2994 $ret[] = $taglist[$tag_id];
2995 return $ret;
2996 }
2997
2998 function decodeObjectType ($objtype_id, $style = 'r')
2999 {
3000 static $types = array();
3001 if (!count ($types))
3002 $types = array
3003 (
3004 'r' => readChapter (CHAP_OBJTYPE),
3005 'a' => readChapter (CHAP_OBJTYPE, 'a'),
3006 'o' => readChapter (CHAP_OBJTYPE, 'o')
3007 );
3008 return $types[$style][$objtype_id];
3009 }
3010
3011 function isolatedPermission ($p, $t, $cell)
3012 {
3013 // This function is called from both "file" page and a number of other pages,
3014 // which have already fixed security context and authorized the user for it.
3015 // OTOH, it is necessary here to authorize against the current file, which
3016 // means saving the current context and building a new one.
3017 global
3018 $expl_tags,
3019 $impl_tags,
3020 $target_given_tags,
3021 $auto_tags;
3022 // push current context
3023 $orig_expl_tags = $expl_tags;
3024 $orig_impl_tags = $impl_tags;
3025 $orig_target_given_tags = $target_given_tags;
3026 $orig_auto_tags = $auto_tags;
3027 // retarget
3028 fixContext ($cell);
3029 // remember decision
3030 $ret = permitted ($p, $t);
3031 // pop context
3032 $expl_tags = $orig_expl_tags;
3033 $impl_tags = $orig_impl_tags;
3034 $target_given_tags = $orig_target_given_tags;
3035 $auto_tags = $orig_auto_tags;
3036 return $ret;
3037 }
3038
3039 function getPortListPrefs()
3040 {
3041 $ret = array();
3042 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
3043 $ret['iif_pick'] = 1;
3044 $ret['oif_picks'] = array();
3045 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
3046 {
3047 $tmp = explode ('=', trim ($tmp));
3048 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
3049 $ret['oif_picks'][$tmp[0]] = $tmp[1];
3050 }
3051 // enforce default value
3052 if (!array_key_exists (1, $ret['oif_picks']))
3053 $ret['oif_picks'][1] = 24;
3054 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
3055 return $ret;
3056 }
3057
3058 // Return data for printNiftySelect() with port type options. All OIF options
3059 // for the default IIF will be shown, but only the default OIFs will be present
3060 // for each other IIFs. IIFs, for which there is no default OIF, will not
3061 // be listed.
3062 // This SELECT will be used for the "add new port" form.
3063 function getNewPortTypeOptions()
3064 {
3065 $ret = array();
3066 $prefs = getPortListPrefs();
3067 foreach (getPortInterfaceCompat() as $row)
3068 {
3069 if ($row['iif_id'] == $prefs['iif_pick'])
3070 $optgroup = $row['iif_name'];
3071 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
3072 $optgroup = 'other';
3073 else
3074 continue;
3075 if (!array_key_exists ($optgroup, $ret))
3076 $ret[$optgroup] = array();
3077 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
3078 }
3079 return $ret;
3080 }
3081
3082 // Return a serialized version of VLAN configuration for a port.
3083 // If a native VLAN is defined, print it first. All other VLANs
3084 // are tagged and are listed after a plus sign. When no configuration
3085 // is set for a port, return "default" string.
3086 function serializeVLANPack ($vlanport)
3087 {
3088 if (!array_key_exists ('mode', $vlanport))
3089 return 'error';
3090 switch ($vlanport['mode'])
3091 {
3092 case 'none':
3093 return 'none';
3094 case 'access':
3095 $ret = 'A';
3096 break;
3097 case 'trunk':
3098 $ret = 'T';
3099 break;
3100 case 'uplink':
3101 $ret = 'U';
3102 break;
3103 case 'downlink':
3104 $ret = 'D';
3105 break;
3106 default:
3107 return 'error';
3108 }
3109 if ($vlanport['native'])
3110 $ret .= $vlanport['native'];
3111 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']);
3112 if (count ($tagged_bits))
3113 $ret .= '+' . implode (', ', $tagged_bits);
3114 return strlen ($ret) ? $ret : 'default';
3115 }
3116
3117 function groupIntsToRanges ($list, $exclude_value = NULL)
3118 {
3119 $result = array();
3120 sort ($list);
3121 $id_from = $id_to = 0;
3122 $list[] = -1;
3123 foreach ($list as $next_id)
3124 if (!isset ($exclude_value) or $next_id != $exclude_value)
3125 if ($id_to && $next_id == $id_to + 1)
3126 $id_to = $next_id; // merge
3127 else
3128 {
3129 if ($id_to)
3130 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush
3131 $id_from = $id_to = $next_id; // start next pair
3132 }
3133 return $result;
3134 }
3135
3136 // Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
3137 // return the numbers as an array of two.
3138 function decodeVLANCK ($string)
3139 {
3140 $matches = array();
3141 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
3142 throw new InvalidArgException ('VLAN compound key', $string);
3143 return array ($matches[1], $matches[2]);
3144 }
3145
3146 // Return VLAN name formatted for HTML output (note, that input
3147 // argument comes from database unescaped).
3148 function formatVLANName ($vlaninfo, $context = 'markup long')
3149 {
3150 switch ($context)
3151 {
3152 case 'option':
3153 $ret = $vlaninfo['vlan_id'];
3154 if ($vlaninfo['vlan_descr'] != '')
3155 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
3156 return $ret;
3157 case 'label':
3158 $ret = $vlaninfo['vlan_id'];
3159 if ($vlaninfo['vlan_descr'] != '')
3160 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
3161 return $ret;
3162 case 'plain long':
3163 $ret = 'VLAN' . $vlaninfo['vlan_id'];
3164 if (