ona  18.1.1
About: OpenNetAdmin provides a database managed inventory of your IP network (with Web and CLI interface).
  Fossies Dox: ona-18.1.1.tar.gz  ("inofficial" and yet experimental doxygen-generated source code documentation)  

subnet.inc.php
Go to the documentation of this file.
1 <?php
2 
3 // Make sure we have necessary functions & DB connectivity
4 require_once($conf['inc_functions_db']);
5 
6 
7 
8 
9 
10 
11 
13 // Function: subnet_display (string $options='')
14 //
15 // Description:
16 // Display an existing subnet.
17 //
18 // Input Options:
19 // $options = key=value pairs of options for this function.
20 // multiple sets of key=value pairs should be separated
21 // by an "&" symbol.
22 //
23 // Output:
24 // Returns a two part list:
25 // 1. The exit status of the function. 0 on success, non-zero on
26 // error. All errors messages are stored in $self['error'].
27 // 2. A textual message for display on the console or web interface.
28 //
29 // Example: list($status, $text) = subnet_display('subnet=10.44.0.0');
31 function subnet_display($options="") {
32  global $conf, $self, $onadb;
33  printmsg('DEBUG => subnet_display('.$options.') called', 3);
34 
35  $text_array = array();
36 
37  // Version - UPDATE on every edit!
38  $version = '1.04';
39 
40  // Parse incoming options string to an array
41  $options = parse_options($options);
42 
43  // Return the usage summary if we need to
44  if ($options['help'] or !$options['subnet']) {
45  // NOTE: Help message lines should not exceed 80 characters for proper display on a console
46  $self['error'] = 'ERROR => Insufficient parameters';
47  return(array(1,
48 <<<EOM
49 
51 Displays an subnet record from the database
52 
53  Synopsis: subnet_display [KEY=VALUE] ...
54 
55  Required:
56  subnet=[ID|IP] display subnet by search string
57 
58  Optional:
59  verbose=[yes|no] display additional info (yes)
60 
61  Notes:
62  * An error is returned if search string returns more than one subnet
63  * IP can be in dotted, numeric, or IPv6 format
64 \n
65 EOM
66  ));
67  }
68 
69  // Sanitize "options[verbose]" (yes is the default)
70  $options['verbose'] = sanitize_YN($options['verbose'], 'Y');
71 
72  // They provided a subnet ID or IP address
73  // Find a subnet record
74  list($status, $rows, $subnet) = ona_find_subnet($options['subnet']);
75  if ($status or !$rows) {
76  $self['error'] = "ERROR => Subnet not found";
77  return(array(2, $self['error'] . "\n"));
78  }
79 
80  // Gather sizing
81  list($percent,$total_used,$size) = get_subnet_usage($subnet['id']);
82  $subnet['total_allocated_percent'] = $percent;
83  $subnet['total_allocated'] = $total_used;
84  $subnet['total_available'] = $size;
85 
86  // get subnet type name
87  list($status, $rows, $sntype) = ona_get_subnet_type_record(array('id' => $subnet['subnet_type_id']));
88  $subnet['subnet_type_name'] = $sntype['display_name'];
89 
90  // Convert some data
91  $text_array = $subnet;
92  $text_array['ip_addr_text'] = ip_mangle($subnet['ip_addr'], 'dotted');
93  $text_array['ip_mask_text'] = ip_mangle($subnet['ip_mask'], 'dotted');
94  $text_array['ip_mask_cidr'] = ip_mangle($subnet['ip_mask'], 'cidr');
95 
96  // Build text to return
97  $text = "SUBNET RECORD\n";
98  $text .= format_array($subnet);
99 
100  // If 'verbose' is enabled, grab some additional info to display
101  if ($options['verbose'] == 'Y') {
102 
103  // Tag records
104  list($status, $rows, $tags) = db_get_records($onadb, 'tags', array('type' => 'subnet', 'reference' => $subnet['id']));
105  if ($rows) {
106  $text .= "\nASSOCIATED TAG RECORDS\n";
107  foreach ($tags as $tag) {
108  $text_array['tags'][] = $tag['name'];
109  $text .= " {$tag['name']}\n";
110  }
111  }
112 
113  // VLAN record
114  list($status, $rows, $vlan) = ona_get_vlan_record(array('id' => $subnet['vlan_id']));
115  if ($rows) {
116  $text_array['vlan'] = $vlan;
117  $text .= "\nASSOCIATED VLAN RECORD\n";
118  $text .= format_array($vlan);
119  }
120 
121  }
122 
123  // cleanup some un-used junk
124  unset($text_array['vlan_id']);
125 
126  // change the output format if other than default
127  if ($options['format'] == 'json') {
128  $text = $text_array;
129  }
130  if ($options['format'] == 'yaml') {
131  $text = $text_array;
132  }
133 
134  // Return the success notice
135  return(array(0, $text));
136 }
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
148 // Function: subnet_add (string $options='')
149 //
150 // Description:
151 // Add a new subnet.
152 //
153 // Input Options:
154 // $options = key=value pairs of options for this function.
155 // multiple sets of key=value pairs should be separated
156 // by an "&" symbol.
157 //
158 // Output:
159 // Returns a two part list:
160 // 1. The exit status of the function. 0 on success, non-zero on
161 // error. All errors messages are stored in $self['error'].
162 // 2. A textual message for display on the console or web interface.
163 //
164 // Example: list($status, $result) = subnet_add('');
166 function subnet_add($options="") {
167  global $conf, $self, $onadb;
168  printmsg('DEBUG => subnet_add('.$options.') called', 3);
169 
170  // Version - UPDATE on every edit!
171  $version = '1.08';
172 
173  // Parse incoming options string to an array
174  $options = parse_options($options);
175 
176  // Return the usage summary if we need to
177  if ($options['help'] or
178  !($options['ip'] and
179  $options['netmask'] and
180  $options['type'] and
181  $options['name'])
182  ) {
183  // NOTE: Help message lines should not exceed 80 characters for proper display on a console
184  $self['error'] = 'ERROR => Insufficient parameters';
185  return(array(1,
186 <<<EOM
187 
189 Adds a new subnet (subnet) record
190 
191  Synopsis: subnet_add [KEY=VALUE] ...
192 
193  Required:
194  name=TEXT subnet name (i.e. "LAN-1234")
195  ip=ADDRESS dotted (10.0.0.0), IPv6, or numeric subnet address
196  netmask=MASK dotted (255.0.0.0), CIDR (/8), or numeric netmask
197  type=TYPE subnet type name or id
198 
199  Optional:
200  vlan=VLAN vlan name, number
201  campus=CAMPUS vlan campus name or id to help identify vlan
202 \n
203 EOM
204  ));
205  }
206 
207  //
208  // Define the fields we're inserting
209  //
210  // This variable will contain the info we'll insert into the DB
211  $SET = array();
212 
213  // Set vlan_id to 0 initially
214  $SET['vlan_id'] = 0;
215 
216  // Prepare options[ip] - translate IP address to a number
217  $options['ip'] = $ourip = ip_mangle($options['ip'], 'numeric');
218  if ($ourip == -1) {
219  $self['error'] = "ERROR => The IP address specified is invalid!";
220  return(array(2, $self['error'] . "\n"));
221  }
222 
223  // Prepare options[netmask] - translate IP address to a number
224  $options['netmask'] = ip_mangle($options['netmask'], 'numeric');
225  if ($options['netmask'] == -1) {
226  $self['error'] = "ERROR => The netmask specified is invalid!";
227  return(array(3, $self['error'] . "\n"));
228  }
229 
230  // Validate the netmask is okay
231  $cidr = ip_mangle($options['netmask'], 'cidr');
232  if ($cidr == -1) {
233  $self['error'] = "ERROR => The netmask specified is invalid!";
234  return(array(4, $self['error'] . "\n"));
235  }
236 
237  if(is_ipv4($ourip)) {
238  // echo "ipv4";
239  $padding = 32;
240  $fmt = 'dotted';
241  $ip1 = ip_mangle($ourip, 'binary');
242  $num_hosts = 0xffffffff - $options['netmask'];
243  $last_host = ($options['ip'] + $num_hosts);
244  } else {
245  // echo "ipv6";
246  $padding = 128;
247  $fmt = 'ipv6gz';
248  $ip1 = ip_mangle($ourip, 'bin128');
249  $sub = gmp_sub("340282366920938463463374607431768211455", $options['netmask']);
250  $num_hosts = gmp_strval($sub);
251  $last_host = gmp_strval(gmp_add($options['ip'],$num_hosts));
252  }
253 
254  // Validate that the subnet IP & netmask combo are valid together.
255  $ip2 = str_pad(substr($ip1, 0, $cidr), $padding, '0');
256  $ip1 = ip_mangle($ip1, $fmt);
257  $ip2 = ip_mangle($ip2, $fmt);
258  if ($ip1 != $ip2) {
259  $self['error'] = "ERROR => Invalid subnet specified - did you mean: {$ip2}/{$cidr}?";
260  return(array(5, $self['error'] . "\n"));
261  }
262 
263  // *** Check to see if the new subnet overlaps any existing ONA subnets *** //
264  // I convert the IP address to dotted format when calling ona_find_subnet()
265  // because it saves it from doing a few unnecessary sql queries.
266 
267  // Look for overlaps like this (where new subnet address starts inside an existing subnet):
268  // [ -- new subnet -- ]
269  // [ -- old subnet --]
270  list($status, $rows, $subnet) = ona_find_subnet(ip_mangle($options['ip'], 'dotted'));
271  if ($rows != 0) {
272  $self['error'] = "ERROR => Subnet address conflict! New subnet starts inside an existing subnet.";
273  return(array(6, $self['error'] . "\n" .
274  "ERROR => Conflicting subnet record ID: {$subnet['id']}\n"));
275  }
276 
277 
278  // Look for overlaps like this (where the new subnet ends inside an existing subnet):
279  // [ -- new subnet -- ]
280  // [ -- old subnet --]
281  // Find last address of our subnet, and see if it's inside of any other subnet:
282  list($status, $rows, $subnet) = ona_find_subnet(ip_mangle($last_host, 'dotted'));
283  if ($rows != 0) {
284  $self['error'] = "ERROR => Subnet address conflict! New subnet ends inside an existing subnet.";
285  return(array(7, $self['error'] . "\n" .
286  "ERROR => Conflicting subnet record ID: {$subnet['id']}\n"));
287  }
288 
289 
290  // Look for overlaps like this (where the new subnet entirely overlaps an existing subnet):
291  // [ -------- new subnet --------- ]
292  // [ -- old subnet --]
293  //
294  // Do a cool SQL query to find any subnets whoose start address is >= or <= the
295  // new subnet base address.
296  $where = "ip_addr >= {$options['ip']} AND ip_addr <= {$last_host}";
297  list($status, $rows, $subnet) = ona_get_subnet_record($where);
298  if ($rows != 0) {
299  $self['error'] = "ERROR => Subnet address conflict! New subnet would encompass an existing subnet.";
300  return(array(8, $self['error'] . "\n" .
301  "ERROR => Conflicting subnet record ID: {$subnet['id']}\n"));
302  }
303 
304  // The IP/NETMASK look good, set them.
305  $SET['ip_addr'] = $options['ip'];
306  $SET['ip_mask'] = $options['netmask'];
307 
308 
309  // Find the type from $options[type]
310  list($status, $rows, $subnet_type) = ona_find_subnet_type($options['type']);
311  if ($status or $rows != 1) {
312  $self['error'] = "ERROR => Invalid subnet type specified!";
313  return(array(10, $self['error'] . "\n"));
314  }
315  printmsg("Subnet type selected: {$subnet_type['name']} ({$subnet_type['short_name']})", 1);
316  $SET['subnet_type_id'] = $subnet_type['id'];
317 
318 
319 
320  // Find the VLAN ID from $options[vlan] and $options[campus]
321  if ($options['vlan'] or $options['campus']) {
322  list($status, $rows, $vlan) = ona_find_vlan($options['vlan'], $options['campus']);
323  if ($status or $rows != 1) {
324  $self['error'] = "ERROR => The vlan/campus pair specified is invalid!";
325  return(array(11, $self['error'] . "\n"));
326  }
327  printmsg("VLAN selected: {$vlan['name']} in {$vlan['vlan_campus_name']} campus", 1);
328  $SET['vlan_id'] = $vlan['id'];
329  }
330 
331  // Sanitize "name" option
332  // We require subnet names to be in upper case and spaces are converted to -'s.
333  $options['name'] = trim($options['name']);
334  $options['name'] = preg_replace('/\s+/', '-', $options['name']);
335  $options['name'] = strtoupper($options['name']);
336  // Make sure there's not another subnet with this name
337  list($status, $rows, $tmp) = ona_get_subnet_record(array('name' => $options['name']));
338  if ($status or $rows) {
339  $self['error'] = "ERROR => That name is already used by another subnet!";
340  return(array(12, $self['error'] . "\n"));
341  }
342  $SET['name'] = $options['name'];
343 
344  // Check permissions
345  if (!auth('subnet_add')) {
346  $self['error'] = "Permission denied!";
347  printmsg($self['error'], 0);
348  return(array(14, $self['error'] . "\n"));
349  }
350 
351  // Get the next ID for the new interface
352  $id = ona_get_next_id('subnets');
353  if (!$id) {
354  $self['error'] = "ERROR => The ona_get_next_id() call failed!";
355  return(array(15, $self['error'] . "\n"));
356  }
357  printmsg("DEBUG => ID for new subnet: " . $id, 1);
358  $SET['id'] = $id;
359 
360  // Insert the new subnet record
361  list($status, $rows) = db_insert_record(
362  $onadb,
363  'subnets',
364  $SET
365  );
366 
367  // Report errors
368  if ($status or !$rows)
369  return(array(16, $self['error'] . "\n"));
370 
371  // Return the success notice
372  $self['error'] = "INFO => Subnet ADDED: {$ip1}/{$cidr}";
373  printmsg($self['error'], 0);
374  return(array(0, $self['error'] . "\n"));
375 }
376 
377 
378 
379 
380 
381 
382 
383 
384 
386 // Function: subnet_modify (string $options='')
387 //
388 // Description:
389 // Modify an existing subnet.
390 //
391 // Input Options:
392 // $options = key=value pairs of options for this function.
393 // multiple sets of key=value pairs should be separated
394 // by an "&" symbol.
395 //
396 // Output:
397 // Returns a two part list:
398 // 1. The exit status of the function. 0 on success, non-zero on
399 // error. All errors messages are stored in $self['error'].
400 // 2. A textual message for display on the console or web interface.
401 //
402 // Example: list($status, $result) = subnet_modify('');
404 function subnet_modify($options="") {
405  global $conf, $self, $onadb;
406  //printmsg('DEBUG => subnet_modify('.implode (";",$options).') called', 3);
407 
408  // Version - UPDATE on every edit!
409  $version = '1.09';
410 
411  // Parse incoming options string to an array
412  $options = parse_options($options);
413 
414  // Return the usage summary if we need to
415  if ($options['help'] or
416  !$options['subnet'] or
417  !($options['set_ip'] or
418  $options['set_netmask'] or
419  $options['set_type'] or
420  $options['set_name'] or
421  array_key_exists('set_vlan', $options) or
422  $options['set_security_level'])
423  ) {
424  // NOTE: Help message lines should not exceed 80 characters for proper display on a console
425  $self['error'] = 'ERROR => Insufficient parameters';
426  return(array(1,
427 <<<EOM
428 
430 Modify a subnet (subnet) record
431 
432  Synopsis: subnet_modify [KEY=VALUE] ...
433 
434  Where:
435  subnet=[ID|IP] select subnet by search string
436 
437  Update:
438  set_ip=IP change subnet "subnet" address
439  set_netmask=MASK change subnet netmask
440  set_name=TEXT change subnet name (i.e. "LAN-1234")
441  set_type=TYPE change subnet type by name or id
442  set_vlan=VLAN change vlan by name, number
443  campus=CAMPUS vlan campus name or id to help identify vlan
444  set_security_level=LEVEL numeric security level ({$conf['ona_lvl']})
445 
446 \n
447 EOM
448  ));
449  }
450 
451  $check_boundaries = 0;
452 
453  // Find the subnet record we're modifying
454  list($status, $rows, $subnet) = ona_find_subnet($options['subnet']);
455  if ($status or !$rows) {
456  $self['error'] = "ERROR => Subnet not found";
457  return(array(2, $self['error'] . "\n"));
458  }
459 
460 
461  // Check permissions
462  if (!auth('subnet_modify')) {
463  $self['error'] = "Permission denied!";
464  printmsg($self['error'], 0);
465  return(array(3, $self['error'] . "\n"));
466  }
467 
468  // Validate the ip address
469  if (!$options['set_ip']) {
470  $options['set_ip'] = $subnet['ip_addr'];
471  }
472  else {
473  $check_boundaries = 1;
474  $options['set_ip'] = $setip = ip_mangle($options['set_ip'], 'numeric');
475  // FIXME: what if ip_mangle returns a GMP object?
476  if ($options['set_ip'] == -1) {
477  $self['error'] = "ERROR => The IP address specified is invalid!";
478  return(array(4, $self['error'] . "\n"));
479  }
480  }
481 
482  // Validate the netmask is okay
483  if (!$options['set_netmask']) {
484  $options['set_netmask'] = $subnet['ip_mask'];
485  $cidr = ip_mangle($options['set_netmask'], 'cidr');
486  }
487  else {
488  $check_boundaries = 1;
489  $cidr = ip_mangle($options['set_netmask'], 'cidr');
490  // FIXME: what if ip_mangle returns a GMP object?
491  $options['set_netmask'] = ip_mangle($options['set_netmask'], 'numeric');
492  if ($cidr == -1 or $options['set_netmask'] == -1) {
493  $self['error'] = "ERROR => The netmask specified is invalid!";
494  return(array(5, $self['error'] . "\n"));
495  }
496  }
497 
498  if(is_ipv4($setip)) {
499  $padding = 32;
500  $fmt = 'dotted';
501  $ip1 = ip_mangle($setip, 'binary');
502  $num_hosts = 0xffffffff - $options['set_netmask'];
503  $first_host=$options['set_ip'] + 1;
504  $last_host = ($options['set_ip'] + $num_hosts);
505  $str_last_host=$last_host;
506  $last_last_host=$last_host -1;
507  } else {
508  $padding = 128;
509  $fmt = 'ipv6gz';
510  $ip1 = ip_mangle($setip, 'bin128');
511  $first_host=gmp_strval(gmp_add($options['set_ip'] , 1));
512  $sub = gmp_sub("340282366920938463463374607431768211455", $options['set_netmask']);
513  $last_host = gmp_add($options['set_ip'] , $sub);
514  $str_last_host=gmp_strval($last_host);
515  $last_last_host=gmp_strval(gmp_sub($last_host ,1));
516  }
517 
518  // Validate that the subnet IP & netmask combo are valid together.
519  $ip2 = str_pad(substr($ip1, 0, $cidr), $padding, '0');
520  $ip1 = ip_mangle($ip1, $fmt);
521  $ip2 = ip_mangle($ip2, $fmt);
522  if ($ip1 != $ip2) {
523  $self['error'] = "ERROR => Invalid subnet specified - did you mean: {$ip2}/{$cidr}?";
524  return(array(6, $self['error'] . "\n"));
525  }
526 
527  // If our IP or netmask changed we need to make sure that
528  // we won't abandon any host interfaces.
529  // We also need to verify that the new boundaries are valid and
530  // don't interefere with any other subnets.
531  if ($check_boundaries == 1) {
532 
533  // *** Check to see if the new subnet overlaps any existing ONA subnets *** //
534  // I convert the IP address to dotted format when calling ona_find_subnet()
535  // because it saves it from doing a few unnecessary sql queries.
536 
537  // Look for overlaps like this (where new subnet address starts inside an existing subnet):
538  // [ -- new subnet -- ]
539  // [ -- old subnet --]
540  list($status, $rows, $record) = ona_find_subnet(ip_mangle($options['set_ip'], 'dotted'));
541  if ($rows and $record['id'] != $subnet['id']) {
542  $self['error'] = "ERROR => Subnet address conflict! New subnet starts inside an existing subnet.";
543  return(array(7, $self['error'] . "\n" .
544  "ERROR => Conflicting subnet record ID: {$record['id']}\n"));
545  }
546 
547 
548  // Look for overlaps like this (where the new subnet ends inside an existing subnet):
549  // [ -- new subnet -- ]
550  // [ -- old subnet --]
551  // Find last address of our subnet, and see if it's inside of any other subnet:
552  list($status, $rows, $record) = ona_find_subnet(ip_mangle($str_last_host, 'dotted'));
553  if ($rows and $record['id'] != $subnet['id']) {
554  $self['error'] = "ERROR => Subnet address conflict! New subnet ends inside an existing subnet.";
555  return(array(8, $self['error'] . "\n" .
556  "ERROR => Conflicting subnet record ID: {$record['id']}\n"));
557  }
558 
559 
560  // Look for overlaps like this (where the new subnet entirely overlaps an existing subnet):
561  // [ -------- new subnet --------- ]
562  // [ -- old subnet --]
563  //
564  // Do a cool SQL query to find all subnets whose start address is >= or <= the
565  // new subnet base address.
566  $where = "ip_addr >= {$options['set_ip']} AND ip_addr <= {$str_last_host}";
567  list($status, $rows, $record) = ona_get_subnet_record($where);
568  if ( ($rows > 1) or ($rows == 1 and $record['id'] != $subnet['id']) ) {
569  $self['error'] = "ERROR => Subnet address conflict! New subnet would encompass an existing subnet.";
570  return(array(9, $self['error'] . "\n" .
571  "ERROR => Conflicting subnet record ID: {$record['id']}\n"));
572  }
573 
574  // Look for any hosts that are currently in our subnet that would be
575  // abandoned if we were to make the proposed changes.
576  // Look for hosts on either side of the new subnet boundaries:
577  // [--- new subnet ---]
578  // * ** * * <-- Hosts: the first and last host would be a problem!
579  // [------- old subnet --------]
580  //
581  $where1 = "subnet_id = {$subnet['id']} AND ip_addr < {$first_host}";
582  $where2 = "subnet_id = {$subnet['id']} AND ip_addr > {$last_last_host}";
583  list($status, $rows1, $record) = ona_get_interface_record($where1);
584  list($status, $rows2, $record) = ona_get_interface_record($where2);
585  if ($rows1 or $rows2) {
586  $num = $rows1 + $rows2;
587  $self['error'] = "ERROR => Changes would abandon {$num} hosts in an unallocated ip space";
588  return(array(10, $self['error'] . "\n"));
589  }
590 
591 
592  // Look for any dhcp pools that are currently in our subnet that would be
593  // abandoned if we were to make the proposed changes.
594  // Look for existin pools with start/end values outside of new subnet range
595  // [--- new subnet ---]
596  // [--cur pool--]
597  // [------- old subnet --------]
598  //
599  $where1 = "subnet_id = {$subnet['id']} AND ip_addr_start < {$options['set_ip']}";
600  $where2 = "subnet_id = {$subnet['id']} AND ip_addr_end > {$str_last_host}";
601  list($status, $rows1, $record) = ona_get_dhcp_pool_record($where1);
602  list($status, $rows2, $record) = ona_get_dhcp_pool_record($where2);
603  if ($rows1 or $rows2) {
604  $num = $rows1 + $rows2;
605  $self['error'] = "ERROR => Changes would abandon a DHCP pool in an unallocated ip space, adjust pool sizes first";
606  return(array(10, $self['error'] . "\n"));
607  }
608 
609  }
610 
611  //
612  // Define the fields we're updating
613  //
614  // This variable will contain the updated info we'll insert into the DB
615  $SET = array();
616  $SET['ip_addr'] = $options['set_ip'];
617  $SET['ip_mask'] = $options['set_netmask'];
618 
619 
620 
621  // Set options['set_security_level']?
622  // Sanitize "security_level" option
623  if (array_key_exists('set_security_level', $options)) {
624  $options['set_security_level'] = sanitize_security_level($options['set_security_level']);
625  if ($options['set_security_level'] == -1)
626  return(array(11, $self['error'] . "\n"));
627  $SET['lvl'] = $options['set_security_level'];
628  }
629 
630 
631  // Set options['set_name']?
632  if ($options['set_name']) {
633  // BUSINESS RULE: We require subnet names to be in upper case and spaces are converted to -'s.
634  $options['set_name'] = trim($options['set_name']);
635  $options['set_name'] = preg_replace('/\s+/', '-', $options['set_name']);
636  $options['set_name'] = strtoupper($options['set_name']);
637  // Make sure there's not another subnet with this name
638  list($status, $rows, $tmp) = ona_get_subnet_record(array('name' => $options['set_name']));
639  if ($status or $rows > 1 or ($rows == 1 and $tmp['id'] != $subnet['id'])) {
640  $self['error'] = "ERROR => That name is already used by another subnet!";
641  return(array(12, $self['error'] . "\n"));
642  }
643  $SET['name'] = $options['set_name'];
644  }
645 
646 
647  // Set options['set_type']?
648  if ($options['set_type']) {
649  // Find the type from $options[type]
650  list($status, $rows, $subnet_type) = ona_find_subnet_type($options['set_type']);
651  if ($status or $rows != 1) {
652  $self['error'] = "ERROR => Invalid subnet type specified!";
653  return(array(13, $self['error'] . "\n"));
654  }
655  printmsg("Subnet type selected: {$subnet_type['display_name']} ({$subnet_type['short_name']})", 1);
656  $SET['subnet_type_id'] = $subnet_type['id'];
657  }
658 
659 
660  // Set options['set_vlan']?
661  if (array_key_exists('set_vlan', $options) or $options['campus']) {
662  if (!$options['set_vlan'])
663  $SET['vlan_id'] = 0;
664  else {
665  // Find the VLAN ID from $options[set_vlan] and $options[campus]
666  list($status, $rows, $vlan) = ona_find_vlan($options['set_vlan'], $options['campus']);
667  if ($status or $rows != 1) {
668  $self['error'] = "ERROR => The vlan/campus pair specified is invalid!";
669  return(array(15, $self['error'] . "\n"));
670  }
671  printmsg("VLAN selected: {$vlan['name']} in {$vlan['vlan_campus_name']} campus", 1);
672  $SET['vlan_id'] = $vlan['id'];
673  }
674  }
675 
676 
677  // Update the subnet record
678  list($status, $rows) = db_update_record($onadb, 'subnets', array('id' => $subnet['id']), $SET);
679  if ($status or !$rows)
680  return(array(16, $self['error'] . "\n"));
681 
682  // Load the updated record for display
683  list($status, $rows, $subnet) = ona_get_subnet_record(array('id' => $subnet['id']));
684 
685  // Return the (human-readable) success notice
686  $text = format_array($SET);
687  $self['error'] = "INFO => Subnet UPDATED";
688  return(array(0, $self['error'] . ":\n{$text}\n"));
689 }
690 
691 
692 
693 
694 
695 
696 
697 
698 
700 // Function: subnet_del (string $options='')
701 //
702 // Description:
703 // Delete an existing subnet.
704 //
705 // Input Options:
706 // $options = key=value pairs of options for this function.
707 // multiple sets of key=value pairs should be separated
708 // by an "&" symbol.
709 //
710 // Output:
711 // Returns a two part list:
712 // 1. The exit status of the function. 0 on success, non-zero on
713 // error. All errors messages are stored in $self['error'].
714 // 2. A textual message for display on the console or web interface.
715 //
716 // Example: list($status, $result) = subnet_del('host=test');
718 function subnet_del($options="") {
719  global $conf, $self, $onadb;
720 
721  // Version - UPDATE on every edit!
722  $version = '1.06';
723 
724  printmsg('DEBUG => subnet_del('.$options.') called', 3);
725 
726  // Parse incoming options string to an array
727  $options = parse_options($options);
728 
729  // Sanitize options[commit] (default is no)
730  $options['commit'] = sanitize_YN($options['commit'], 'N');
731 
732  // Return the usage summary if we need to
733  if ($options['help'] or !$options['subnet'] ) {
734  // NOTE: Help message lines should not exceed 80 characters for proper display on a console
735  $self['error'] = 'ERROR => Insufficient parameters';
736  return(array(1,
737 <<<EOM
738 
740 Deletes a subnet (subnet) from the database
741 
742  Synopsis: subnet_del [KEY=VALUE] ...
743 
744  Required:
745  subnet=IP or ID select subnet by search string
746 
747  Optional:
748  commit=[yes|no] commit db transaction (no)
749 \n
750 EOM
751  ));
752  }
753 
754 
755  // Find the subnet record we're deleting
756  list($status, $rows, $subnet) = ona_find_subnet($options['subnet']);
757  if ($status or !$rows) {
758  $self['error'] = "ERROR => Subnet not found";
759  return(array(2, $self['error'] . "\n"));
760  }
761 
762 
763  // Check permissions
764  if (!auth('subnet_del') or !authlvl($subnet['lvl'])) {
765  $self['error'] = "Permission denied!";
766  printmsg($self['error'], 0);
767  return(array(3, $self['error'] . "\n"));
768  }
769 
770 
771  // If "commit" is yes, delete the subnet
772  if ($options['commit'] == 'Y') {
773  $text = "";
774 
775  // FIXME: (add all this) ...
776  // SUMMARY:
777  // Delete assignments to any DHCP servers
778  // Delete any DHCP pools on the current subnet
779  // Delete any DHCP options associated with this subnet
780  // Delete any interfaces belonging to hosts with more than one interface
781  // Delete any hosts (and all their associated info) that have only one interface
782  // Delete subnet Record
783  // Delete custom attributes
784  //
785  // FIXME: display a warning if there are no more subnets that a dhcp server is serving dhcp for?
786 
787  // Delete DHCP server assignments
788  list($status, $rows) = db_delete_records($onadb, 'dhcp_server_subnets', array('subnet_id' => $subnet['id']));
789  if ($status) {
790  $self['error'] = "ERROR => DHCP server assignment delete failed: {$self['error']}";
791  return(array(5, $self['error'] . "\n"));
792  }
793 
794  // Delete DHCP pools
795  list($status, $rows) = db_delete_records($onadb, 'dhcp_pools', array('subnet_id' => $subnet['id']));
796  if ($status) {
797  $self['error'] = "ERROR => DHCP pool delete failed: {$self['error']}";
798  return(array(5, $self['error'] . "\n"));
799  }
800 
801  // Delete DHCP options
802  list($status, $rows) = db_delete_records($onadb, 'dhcp_option_entries', array('subnet_id' => $subnet['id']));
803  if ($status) {
804  $self['error'] = "ERROR => DHCP parameter delete failed: {$self['error']}";
805  return(array(5, $self['error'] . "\n"));
806  }
807 
808  // Delete tag entries
809  list($status, $rows, $records) = db_get_records($onadb, 'tags', array('type' => 'subnet', 'reference' => $subnet['id']));
810  $log=array(); $i=0;
811  foreach ($records as $record) {
812  $log[$i]= "INFO => Tag DELETED: {$record['name']} from {$subnet['name']}";
813  $i++;
814  }
815  //do the delete
816  list($status, $rows) = db_delete_records($onadb, 'tags', array('type' => 'subnet', 'reference' => $subnet['id']));
817  if ($status) {
818  $self['error'] = "ERROR => subnet_del() Tag delete SQL Query failed: {$self['error']}";
819  printmsg($self['error'],0);
820  return(array(5, $add_to_error . $self['error'] . "\n"));
821  }
822  //log deletions
823  foreach($log as $log_msg) {
824  printmsg($log_msg,0);
825  $add_to_error .= $log_msg . "\n";
826  }
827 
828  // Delete custom attribute entries
829  // get list for logging
830  list($status, $rows, $records) = db_get_records($onadb, 'custom_attributes', array('table_name_ref' => 'subnets', 'table_id_ref' => $subnet['id']));
831  $log=array(); $i=0;
832  foreach ($records as $record) {
833  list($status, $rows, $ca) = ona_get_custom_attribute_record(array('id' => $record['id']));
834  $log[$i]= "INFO => Custom Attribute DELETED: {$ca['name']} ({$ca['value']}) from {$subnet['name']}";
835  $i++;
836  }
837 
838  //do the delete
839  list($status, $rows) = db_delete_records($onadb, 'custom_attributes', array('table_name_ref' => 'subnets', 'table_id_ref' => $subnet['id']));
840  if ($status) {
841  $self['error'] = "ERROR => subnet_del() Custom attribute delete SQL Query failed: {$self['error']}";
842  printmsg($self['error'],0);
843  return(array(5, $self['error'] . "\n"));
844  }
845 
846  //log deletions
847  foreach($log as $log_msg) {
848  printmsg($log_msg,0);
849  //$add_to_error .= $log_msg . "\n";
850  }
851 
852 
853 
854  // Delete associated host / interface records that need to be deleted
855  // BUSINESS RULE: We delete hosts that have only one interface (and it's on this subnet)
856  // BUSINESS RULE: We delete interfaces from hosts that have multiple interfaces
857  list($status, $rows, $interfaces) = db_get_records($onadb, 'interfaces', array('subnet_id' => $subnet['id']));
858  $hosts_to_delete = array();
859  $interfaces_to_delete = array();
860  foreach ($interfaces as $interface) {
861  // Select all interfaces for the associated host where the subnet ID is not our subnet ID
862  $where = "host_id = {$interface['host_id']} AND subnet_id != {$subnet['id']}";
863  list($status, $rows, $tmp) = db_get_records($onadb, 'interfaces', $where, '', 0);
864  // We'll delete hosts that have only one interface (i.e. no interfaces on any other subnets)
865  if ($rows == 0)
866  array_push($hosts_to_delete, $interface['host_id']);
867  // Otherwise .. we delete this interface since it belongs to a host with interfaces on other subnets
868  else
869  array_push($interfaces_to_delete, $interface['id']);
870  }
871  unset($interfaces);
872 
873  // make sure we only have one reference for each host and interface
874  $interfaces_to_delete = array_unique($interfaces_to_delete);
875  $hosts_to_delete = array_unique($hosts_to_delete);
876 
877  // Delete interfaces we have selected
878  foreach ($interfaces_to_delete as $interface_id) {
879  list($status, $output) = run_module('interface_del', array('interface' => $interface_id, 'commit' => 'Y'));
880  if ($status) return(array(5, $output));
881  }
882 
883  // Delete hosts we have selected
884  foreach ($hosts_to_delete as $host_id) {
885  list($status, $output) = run_module('host_del', array('host' => $host_id, 'commit' => 'Y'));
886  if ($status) return(array(5, $output));
887  }
888 
889  // Delete the subnet
890  list($status, $rows) = db_delete_records($onadb, 'subnets', array('id' => $subnet['id']));
891  if ($status or !$rows) {
892  $self['error'] = "ERROR => Subnet delete failed: {$self['error']}";
893  return(array(5, $self['error'] . "\n"));
894  }
895 
896  // Return the success notice
897  $ip = ip_mangle($subnet['ip_addr'], 'dotted');
898  $cidr = ip_mangle($subnet['ip_mask'], 'cidr');
899  $self['error'] = "INFO => Subnet DELETED: {$subnet['name']} IP: {$ip}/{$cidr}";
900  printmsg($self['error'], 0);
901  return(array(0, $self['error'] . "\n"));
902  }
903 
904 
905  //
906  // We are just displaying records that would have been deleted
907  //
908 
909  // SUMMARY:
910  // Display assignments to any DHCP servers
911  // Display any DHCP pools on the current subnet
912  // Display any DHCP parameters associated with this subnet
913  // Display subnet Record
914  // Display Host records (and all their sub-records)
915  // Display custom attributes
916 
917 
918  // Otherwise just display the host record for the host we would have deleted
919  $text = "Record(s) NOT DELETED (see \"commit\" option)\n" .
920  "Displaying record(s) that would have been deleted:\n";
921 
922  // Display the Subnet's complete record
923  list($status, $tmp) = subnet_display("subnet={$subnet['id']}&verbose=N");
924  $text .= "\n" . $tmp;
925 
926 
927 
928  // Display assignments to any DHCP servers
929  list($status, $rows, $records) = db_get_records($onadb, 'dhcp_server_subnets', array('subnet_id' => $subnet['id']));
930  if ($rows) $text .= "\nASSOCIATED DHCP SERVER ASSIGNMENT RECORDS ({$rows}):\n";
931  foreach ($records as $record) {
933  }
934 
935  // Display any DHCP pools on the current subnet
936  list($status, $rows, $records) = db_get_records($onadb, 'dhcp_pools', array('subnet_id' => $subnet['id']));
937  if ($rows) $text .= "\nASSOCIATED DHCP POOL RECORDS ({$rows}):\n";
938  foreach ($records as $record) {
940  }
941 
942  // Display associated DHCP entries
943  list($status, $rows, $records) = db_get_records($onadb, 'dhcp_option_entries', array('subnet_id' => $subnet['id']));
944  if ($rows) $text .= "\nASSOCIATED DHCP ENTRY RECORDS ({$rows}):\n";
945  foreach ($records as $record) {
946  list($status, $rows, $dhcp) = ona_get_dhcp_option_entry_record(array('id' => $record['id']));
947  $text .= " {$dhcp['display_name']} => {$dhcp['value']}\n";
948  }
949 
950  // Display associated tags
951  list($status, $rows, $records) = db_get_records($onadb, 'tags', array('type' => 'subnet', 'reference' => $subnet['id']));
952  if ($rows) $text .= "\nASSOCIATED TAG RECORDS ({$rows}):\n";
953  foreach ($records as $record) {
954  $text .= " {$record['name']}\n";
955  }
956 
957  // Display associated custom attributes
958  list($status, $rows, $records) = db_get_records($onadb, 'custom_attributes', array('table_name_ref' => 'subnets', 'table_id_ref' => $subnet['id']));
959  if ($rows) $text .= "\nASSOCIATED CUSTOM ATTRIBUTE RECORDS ({$rows}):\n";
960  foreach ($records as $record) {
961  list($status, $rows, $ca) = ona_get_custom_attribute_record(array('id' => $record['id']));
962  $text .= " {$ca['name']} => {$ca['value']}\n";
963  }
964 
965  // Display associated host / interface records that would be deleted
966  // BUSINESS RULE: We delete hosts that have only one interface (and it's on this subnet)
967  // BUSINESS RULE: We delete interfaces from hosts that have multiple interfaces (including at least one on a different subnet)
968  list($status, $rows, $interfaces) = db_get_records($onadb, 'interfaces', array('subnet_id' => $subnet['id']));
969  $hosts_to_delete = array();
970  $interfaces_to_delete = array();
971  foreach ($interfaces as $interface) {
972  // Select all interfaces for the associated host where the subnet ID is not our subnet ID
973  $where = "host_id = {$interface['host_id']} AND subnet_id != {$subnet['id']}";
974  list($status, $rows, $tmp) = db_get_records($onadb, 'interfaces', $where, '', 0);
975  // We'll delete hosts that have only one interface (i.e. no interfaces on any other subnets)
976  if ($rows == 0)
977  array_push($hosts_to_delete, $interface['host_id']);
978  // Otherwise .. we delete this interface since it belongs to a host with interfaces on other subnets
979  else
980  array_push($interfaces_to_delete, $interface['id']);
981  }
982  unset($interfaces);
983 
984  // make sure we only have one reference for each host and interface
985  $interfaces_to_delete = array_unique($interfaces_to_delete);
986  $hosts_to_delete = array_unique($hosts_to_delete);
987 
988  // Display interfaces we would have deleted
989  $rows = count($interfaces_to_delete);
990  if ($rows) $text .= "\n----- ASSOCIATED HOST INTERFACE RECORDS ({$rows}) -----\n";
991  foreach ($interfaces_to_delete as $interface_id) {
992  list($status, $output) = run_module('interface_del', array('interface' => $interface_id), false);
993  $output = preg_replace('/^(.*)?\n(.*)?\n/', '', $output);
994  $text .= $output;
995  }
996 
997  // Display hosts we would have deleted
998  $rows = count($hosts_to_delete);
999  if ($rows) $text .= "\n-----ASSOCIATED HOSTS ({$rows}) -----\n";
1000  foreach ($hosts_to_delete as $host_id) {
1001  list($status, $output) = run_module('host_del', array('host' => $host_id), false);
1002  $output = preg_replace('/^(.*)?\n(.*)?\n/', '', $output);
1003  $text .= $output;
1004  }
1005 
1006  return(array(7, $text));
1007 }
1008 
1009 
1010 
1011 
1012 
1013 
1014 
1015 
1016 
1017 
1019 // Function: subnet_nextip (string $options='')
1020 //
1021 // Description:
1022 // Return the next available IP address on a subnet. Optionally
1023 // start the search from a starting offset.
1024 //
1025 // Input Options:
1026 // $options = key=value pairs of options for this function.
1027 // multiple sets of key=value pairs should be separated
1028 // by an "&" symbol.
1029 //
1030 // Output:
1031 // Returns a two part list:
1032 // 1. The exit status of the function. 0 on success, non-zero on
1033 // error. All errors messages are stored in $self['error'].
1034 // 2. A textual message for display on the console or web interface.
1035 //
1036 // Example: list($status, $result) = subnet_nextip('subnet=test');
1038 function subnet_nextip($options="") {
1039  global $conf, $self, $onadb;
1040 
1041  // Version - UPDATE on every edit!
1042  $version = '1.00';
1043 
1044  printmsg('DEBUG => subnet_del('.$options.') called', 3);
1045 
1046  // Parse incoming options string to an array
1047  $options = parse_options($options);
1048 
1049  // Sanitize options[commit] (default is no)
1050  $options['commit'] = sanitize_YN($options['commit'], 'N');
1051 
1052  // Return the usage summary if we need to
1053  if ($options['help'] or !$options['subnet'] ) {
1054  // NOTE: Help message lines should not exceed 80 characters for proper display on a console
1055  $self['error'] = 'ERROR => Insufficient parameters';
1056  return(array(1,
1057 <<<EOM
1058 
1060 Return the next available IP address on a subnet.
1061 
1062  Synopsis: subnet_nextip [KEY=VALUE] ...
1063 
1064  Required:
1065  subnet=IP or ID select subnet by search string
1066 
1067  Optional:
1068  offset=NUMBER Starting offset to find next available IP
1069  output=[dotted|numeric] Return the number as a dotted or numeric value
1070  DEFAULT: numeric
1071 \n
1072 EOM
1073  ));
1074  }
1075 
1076 
1077  // Find the subnet record we're deleting
1078  list($status, $rows, $subnet) = ona_find_subnet($options['subnet']);
1079  if ($status or !$rows) {
1080  $self['error'] = "ERROR => Subnet not found";
1081  return(array(2, $self['error'] . "\n"));
1082  }
1083 
1084  // Create a few variables that will be handy later
1085  $num_ips = 0xffffffff - $subnet['ip_mask'];
1086  $last_ip = ($subnet['ip_addr'] + $num_ips) - 1;
1087 
1088  // check that offset is a number
1089  if (isset($options['offset']) and !is_numeric($options['offset'])) {
1090  $self['error'] = "ERROR => Offset must be a numeric number";
1091  return(array(3, $self['error'] . "\n"));
1092  } else {
1093  $offsetmsg = " beyond offset {$options['offset']}";
1094  }
1095 
1096  // make sure the offset does not extend beyond the specified subnet
1097  if ($options['offset'] >= $num_ips - 1) {
1098  $self['error'] = "ERROR => Offset extends beyond specified subnet boundary";
1099  return(array(4, $self['error'] . "\n"));
1100  }
1101 
1102  if (!isset($options['output'])) {
1103  $options['output'] = '1';
1104  }
1105 
1106  // check output option is dotted or numeric
1107  else if ($options['output'] != 'dotted' && $options['output'] != 'numeric') {
1108  $self['error'] = "ERROR => Output option must be 'dotted' or 'numeric'";
1109  return(array(5, $self['error'] . "\n"));
1110  }
1111 
1112  // Find the first number based on our subnet and offset
1113  $ip = $subnet['ip_addr'] + $options['offset'];
1114 
1115  // Make sure we skip past the subnet IP to the first usable IP
1116  if ($ip == $subnet['ip_addr']) $ip++;
1117 
1118  // Start looping through our IP addresses until we find an available one
1119  while ($ip <= $last_ip) {
1120  // Find out if the ip is used in an interface
1121  list($status, $rows, $interfaces) = db_get_records($onadb, 'interfaces', array('ip_addr' => $ip));
1122 
1123  // If we find a free address.. check that it is not in a DHCP pool
1124  if (!$rows) {
1125  list($status, $rows, $pool) = db_get_record($onadb, 'dhcp_pools', "{$ip} >= ip_addr_start AND {$ip} <= ip_addr_end");
1126  if ($rows) $ip = $pool['ip_addr_end'];
1127  else break;
1128  }
1129  $ip++; // increment by one and check again
1130  }
1131 
1132  // If we checked all the IPs, make sure we are not on the broadcast IP of the subnet
1133  if ($ip == $last_ip + 1) {
1134  $self['error'] = "ERROR => No available IP addresses found on subnet{$offsetmsg}";
1135  return(array(5, $self['error'] . "\n"));
1136  }
1137 
1138  // return the IP
1139  return(array(0, ip_mangle($ip,$options['output'])."\n"));
1140 
1141 
1142 }
1143 
1144 
1145 
1146 
1147 
1148 
1149 
1150 ?>
db_insert_record
db_insert_record($dbh=0, $table="", $insert="")
Definition: functions_db.inc.php:375
sanitize_YN
sanitize_YN($string="", $default="Y")
Definition: functions_general.inc.php:1637
ona_get_next_id
ona_get_next_id($tablename)
Definition: functions_db.inc.php:1369
ip_mangle
ip_mangle($ip="", $format="default")
Definition: functions_general.inc.php:308
ona_find_subnet
ona_find_subnet($search="")
Definition: functions_db.inc.php:2003
ona_get_interface_record
ona_get_interface_record($array='', $order='')
Definition: functions_db.inc.php:1126
$record
$record['display_name']
Definition: app_advanced_search.inc.php:12
db_get_records
db_get_records($dbh=0, $table="", $where="", $order="", $rows=-1, $offset=-1)
Definition: functions_db.inc.php:891
$status
$status
Definition: install.php:12
$onadb
global $onadb
Definition: 2-to-3.php:15
subnet_add
subnet_add($options="")
Definition: subnet.inc.php:166
db_delete_records
db_delete_records($dbh=0, $table="", $where="")
Definition: functions_db.inc.php:582
get_subnet_usage
get_subnet_usage($subnet_id)
Definition: functions_gui.inc.php:260
printmsg
if(6<=$conf['debug']) printmsg($msg="", $debugLevel=0)
Definition: functions_general.inc.php:48
format_array
format_array($array=array())
Definition: functions_general.inc.php:1745
ona_get_custom_attribute_record
ona_get_custom_attribute_record($array)
Definition: functions_db.inc.php:1183
authlvl
authlvl($level)
Definition: functions_general.inc.php:1349
ona_find_subnet_type
ona_find_subnet_type($search="")
Definition: functions_db.inc.php:2263
db_update_record
db_update_record($dbh=0, $table="", $where="", $insert="")
Definition: functions_db.inc.php:474
run_module
run_module($module='', $options='', $transaction=1)
Definition: functions_general.inc.php:1468
ona_get_dhcp_pool_record
ona_get_dhcp_pool_record($array)
Definition: functions_db.inc.php:1276
ona_get_vlan_record
ona_get_vlan_record($array)
Definition: functions_db.inc.php:1241
subnet_modify
subnet_modify($options="")
Definition: subnet.inc.php:404
$conf
global $conf
Definition: 2-to-3.php:15
$ip
$ip
Definition: main.inc.php:24
ona_get_subnet_type_record
ona_get_subnet_type_record($array)
Definition: functions_db.inc.php:1237
subnet_display
subnet_display($options="")
Definition: subnet.inc.php:31
subnet_nextip
subnet_nextip($options="")
Definition: subnet.inc.php:1038
$output
$output
Definition: dcm.php:16
$text
$text
Definition: install.php:11
$self
global $self
Definition: 2-to-3.php:15
db_get_record
db_get_record($dbh=0, $table="", $where="", $order="")
Definition: functions_db.inc.php:708
auth
auth($resource, $msg_level=1)
Definition: functions_general.inc.php:1330
ona_get_subnet_record
ona_get_subnet_record($array)
Definition: functions_db.inc.php:1226
ona_get_dhcp_option_entry_record
ona_get_dhcp_option_entry_record($array)
Definition: functions_db.inc.php:1262
ona_find_vlan
ona_find_vlan($vlan_search="", $campus_search="")
Definition: functions_db.inc.php:2467
parse_options
parse_options($options="")
Definition: functions_general.inc.php:1579
$version
$version
Definition: main.inc.php:22
sanitize_security_level
sanitize_security_level($string="", $default=-1)
Definition: functions_general.inc.php:860
subnet_del
subnet_del($options="")
Definition: subnet.inc.php:718