function host_add($options = "") { global $conf, $self, $onadb; // Version - UPDATE on every edit! $version = '1.11'; printmsg("DEBUG => host_add({$options}) called", 3); // Parse incoming options string to an array $options = parse_options($options); // Return the usage summary if we need to if ($options['help'] or !($options['host'] and $options['type'] and $options['ip'])) { // NOTE: Help message lines should not exceed 80 characters for proper display on a console $self['error'] = 'ERROR => Insufficient parameters'; return array(1, <<<EOM host_add-v{$version} Add a new host Synopsis: host_add [KEY=VALUE] ... Required: host=NAME[.DOMAIN] Hostname for new DNS record type=TYPE or ID Device/model type or ID ip=ADDRESS IP address (numeric or dotted) Optional: notes=NOTES Textual notes location=REF Reference of location device=NAME|ID The device this host is associated with Optional, add an interface too: mac=ADDRESS Mac address (most formats are ok) name=NAME Interface name (i.e. "FastEthernet0/1.100") description=TEXT Brief description of the interface addptr=Y|N Auto add a PTR record for new host/IP (default: Y) EOM ); } // Sanitize addptr.. set it to Y if it is not set $options['addptr'] = sanitize_YN($options['addptr'], 'Y'); // clean up what is passed in $options['ip'] = trim($options['ip']); $options['mac'] = trim($options['mac']); $options['name'] = trim($options['name']); $options['host'] = trim($options['host']); // Validate that there isn't already another interface with the same IP address list($status, $rows, $interface) = ona_get_interface_record(array('ip_addr' => $options['ip'])); if ($rows) { printmsg("DEBUG => host_add() IP conflict: That IP address (" . ip_mangle($orig_ip, 'dotted') . ") is already in use!", 3); $self['error'] = "ERROR => host_add() IP conflict: That IP address (" . ip_mangle($orig_ip, 'dotted') . ") is already in use!"; return array(4, $self['error'] . "\n" . "INFO => Conflicting interface record ID: {$interface['id']}\n"); } // Find the Location ID to use if ($options['location']) { list($status, $rows, $loc) = ona_find_location($options['location']); if ($status or !$rows) { printmsg("DEBUG => The location specified, {$options['location']}, does not exist!", 3); $self['error'] = "ERROR => The location specified, {$options['location']}, does not exist!"; return array(2, "{$self['error']}\n"); } printmsg("DEBUG => Location selected: {$loc['reference']}, location name: {$loc['name']}", 3); } else { $loc['id'] = 0; } // Find the Device Type ID (i.e. Type) to use list($status, $rows, $device_type) = ona_find_device_type($options['type']); if ($status or $rows != 1 or !$device_type['id']) { printmsg("DEBUG => The device type specified, {$options['type']}, does not exist!", 3); return array(3, "ERROR => The device type specified, {$options['type']}, does not exist!\n"); } printmsg("DEBUG => Device type selected: {$device_type['model_description']} Device ID: {$device_type['id']}", 3); // Sanitize "security_level" option $options['security_level'] = sanitize_security_level($options['security_level']); if ($options['security_level'] == -1) { printmsg("DEBUG => Sanitize security level failed either ({$options['security_level']}) is invalid or is higher than user's level!", 3); return array(3, $self['error'] . "\n"); } // Determine the real hostname to be used -- // i.e. add .something.com, or find the part of the name provided // that will be used as the "domain". This means testing many // domain names against the DB to see what's valid. // // Find the domain name piece of $search. // If we are specifically passing in a domain, use its value. If we dont have a domain // then try to find it in the name that we are setting. if ($options['domain']) { // Find the domain name piece of $search list($status, $rows, $domain) = ona_find_domain($options['domain'], 0); } else { list($status, $rows, $domain) = ona_find_domain($options['host'], 0); } if (!isset($domain['id'])) { printmsg("ERROR => Unable to determine domain name portion of ({$options['host']})!", 3); $self['error'] = "ERROR => Unable to determine domain name portion of ({$options['host']})!"; return array(3, $self['error'] . "\n"); } printmsg("DEBUG => ona_find_domain({$options['host']}) returned: {$domain['fqdn']}", 3); // Now find what the host part of $search is $hostname = str_replace(".{$domain['fqdn']}", '', $options['host']); // Validate that the DNS name has only valid characters in it $hostname = sanitize_hostname($hostname); if (!$hostname) { printmsg("ERROR => Invalid host name ({$options['host']})!", 3); $self['error'] = "ERROR => Invalid host name ({$options['host']})!"; return array(4, $self['error'] . "\n"); } // Debugging printmsg("DEBUG => Using hostname: {$hostname} Domainname: {$domain['fqdn']}, Domain ID: {$domain['id']}", 3); // Validate that there isn't already any dns record named $host['name'] in the domain $host_domain_id. $h_status = $h_rows = 0; // does the domain $host_domain_id even exist? list($d_status, $d_rows, $d_record) = ona_get_dns_record(array('name' => $hostname, 'domain_id' => $domain['id'])); if ($d_status or $d_rows) { printmsg("DEBUG => The name {$hostname}.{$domain['fqdn']} is already in use, the primary name for a host should be unique!", 3); $self['error'] = "ERROR => Another DNS record named {$hostname}.{$domain['fqdn']} is already in use, the primary name for a host should be unique!"; return array(5, $self['error'] . "\n"); } // Check permissions if (!auth('host_add')) { $self['error'] = "Permission denied!"; printmsg($self['error'], 0); return array(10, $self['error'] . "\n"); } // Get the next ID for the new host record $id = ona_get_next_id('hosts'); if (!$id) { $self['error'] = "ERROR => The ona_get_next_id('hosts') call failed!"; printmsg($self['error'], 0); return array(7, $self['error'] . "\n"); } printmsg("DEBUG => ID for new host record: {$id}", 3); // Get the next ID for the new device record or use the one passed in the CLI if (!$options['device']) { $host['device_id'] = ona_get_next_id('devices'); if (!$id) { $self['error'] = "ERROR => The ona_get_next_id('device') call failed!"; printmsg($self['error'], 0); return array(7, $self['error'] . "\n"); } printmsg("DEBUG => ID for new device record: {$id}", 3); } else { list($status, $rows, $devid) = ona_find_device($options['device']); if (!$rows) { printmsg("DEBUG => The device specified, {$options['device']}, does not exist!", 3); $self['error'] = "ERROR => The device specified, {$options['device']}, does not exist!"; return array(7, $self['error'] . "\n"); } $host['device_id'] = $devid['id']; } // There is an issue with escaping '=' and '&'. We need to avoid adding escape characters $options['notes'] = str_replace('\\=', '=', $options['notes']); $options['notes'] = str_replace('\\&', '&', $options['notes']); // Add the device record // FIXME: (MP) quick add of device record. more detail should be looked at here to ensure it is done right // FIXME: MP this should use the run_module('device_add')!!! when it is ready list($status, $rows) = db_insert_record($onadb, 'devices', array('id' => $host['device_id'], 'device_type_id' => $device_type['id'], 'location_id' => $loc['id'], 'primary_host_id' => $id)); if ($status or !$rows) { $self['error'] = "ERROR => host_add() SQL Query failed adding device: " . $self['error']; printmsg($self['error'], 0); return array(6, $self['error'] . "\n"); } // Add the host record // FIXME: (PK) Needs to insert to multiple tables for e.g. name and domain_id. list($status, $rows) = db_insert_record($onadb, 'hosts', array('id' => $id, 'primary_dns_id' => '', 'device_id' => $host['device_id'], 'notes' => $options['notes'])); if ($status or !$rows) { $self['error'] = "ERROR => host_add() SQL Query failed adding host: " . $self['error']; printmsg($self['error'], 0); return array(6, $self['error'] . "\n"); } // Else start an output message $text = "INFO => Host ADDED: {$hostname}.{$domain['fqdn']}"; printmsg($text, 0); $text .= "\n"; // We must always have an IP now to add an interface, call that module now: // since we have no name yet, we need to use the ID of the new host as the host option for the following module calls $options['host'] = $id; // for annoying reasons we need to keep track of what was set first $options['addptrsave'] = $options['addptr']; // Interface adds can add PTR records, lets let the A record add that happens next add it instead. $options['addptr'] = '0'; printmsg("DEBUG => host_add() ({$hostname}.{$domain['fqdn']}) calling interface_add() ({$options['ip']})", 3); list($status, $output) = run_module('interface_add', $options); if ($status) { return array($status, $output); } $text .= $output; // Find the interface_id for the interface we just added list($status, $rows, $int) = ona_find_interface($options['ip']); // make the dns record type A $options['type'] = 'A'; // FIXME: MP I had to force the name value here. name is comming in as the interface name. this is nasty! $options['name'] = "{$hostname}.{$domain['fqdn']}"; $options['domain'] = $domain['fqdn']; // And we will go ahead and auto add the ptr. the user can remove it later if they dont want it. FIXME: maybe create a checkbox on the host edit $options['addptr'] = $options['addptrsave']; // Add the DNS entry with the IP address etc printmsg("DEBUG => host_add() ({$hostname}.{$domain['fqdn']}) calling dns_record_add() ({$options['ip']})", 3); list($status, $output) = run_module('dns_record_add', $options); if ($status) { return array($status, $output); } $text .= $output; // find the dns record we just added so we can use its ID as the primary_dns_id for the host. list($status, $rows, $dnsrecord) = ona_get_dns_record(array('name' => $hostname, 'domain_id' => $domain['id'], 'interface_id' => $int['id'], 'type' => 'A')); // Set the primary_dns_id to the dns record that was just added list($status, $rows) = db_update_record($onadb, 'hosts', array('id' => $id), array('primary_dns_id' => $dnsrecord['id'])); if ($status or !$rows) { $self['error'] = "ERROR => host_add() SQL Query failed to update primary_dns_id for host: " . $self['error']; printmsg($self['error'], 0); return array(8, $self['error'] . "\n"); } return array(0, $text); }
function subnet_modify($options = "") { global $conf, $self, $onadb; //printmsg('DEBUG => subnet_modify('.implode (";",$options).') called', 3); // Version - UPDATE on every edit! $version = '1.08'; // Parse incoming options string to an array $options = parse_options($options); // Return the usage summary if we need to if ($options['help'] or !$options['subnet'] or !($options['set_ip'] or $options['set_netmask'] or $options['set_type'] or $options['set_name'] or array_key_exists('set_vlan', $options) or $options['set_security_level'])) { // NOTE: Help message lines should not exceed 80 characters for proper display on a console $self['error'] = 'ERROR => Insufficient parameters'; return array(1, <<<EOM subnet_modify-v{$version} Modify a subnet (subnet) record Synopsis: subnet_modify [KEY=VALUE] ... Where: subnet=[ID|IP] select subnet by search string Update: set_ip=IP change subnet "subnet" address set_netmask=MASK change subnet netmask set_name=TEXT change subnet name (i.e. "LAN-1234") set_type=TYPE change subnet type by name or id set_vlan=VLAN change vlan by name, number campus=CAMPUS vlan campus name or id to help identify vlan set_security_level=LEVEL numeric security level ({$conf['ona_lvl']}) EOM ); } $check_boundaries = 0; // Find the subnet record we're modifying list($status, $rows, $subnet) = ona_find_subnet($options['subnet']); if ($status or !$rows) { $self['error'] = "ERROR => Subnet not found"; return array(2, $self['error'] . "\n"); } // Check permissions if (!auth('subnet_modify')) { $self['error'] = "Permission denied!"; printmsg($self['error'], 0); return array(3, $self['error'] . "\n"); } // Validate the ip address if (!$options['set_ip']) { $options['set_ip'] = $subnet['ip_addr']; } else { $check_boundaries = 1; $options['set_ip'] = $setip = ip_mangle($options['set_ip'], 'numeric'); // FIXME: what if ip_mangle returns a GMP object? if ($options['set_ip'] == -1) { $self['error'] = "ERROR => The IP address specified is invalid!"; return array(4, $self['error'] . "\n"); } } // Validate the netmask is okay if (!$options['set_netmask']) { $options['set_netmask'] = $subnet['ip_mask']; $cidr = ip_mangle($options['set_netmask'], 'cidr'); } else { $check_boundaries = 1; $cidr = ip_mangle($options['set_netmask'], 'cidr'); // FIXME: what if ip_mangle returns a GMP object? $options['set_netmask'] = ip_mangle($options['set_netmask'], 'numeric'); if ($cidr == -1 or $options['set_netmask'] == -1) { $self['error'] = "ERROR => The netmask specified is invalid!"; return array(5, $self['error'] . "\n"); } } if (is_ipv4($setip)) { $padding = 32; $fmt = 'dotted'; $ip1 = ip_mangle($setip, 'binary'); $num_hosts = 0xffffffff - $options['set_netmask']; $first_host = $options['set_ip'] + 1; $last_host = $options['set_ip'] + $num_hosts; $str_last_host = $last_host; $last_last_host = $last_host - 1; } else { $padding = 128; $fmt = 'ipv6gz'; $ip1 = ip_mangle($setip, 'bin128'); $first_host = gmp_strval(gmp_add($options['set_ip'], 1)); $sub = gmp_sub("340282366920938463463374607431768211455", $options['set_netmask']); $last_host = gmp_add($options['set_ip'], $sub); $str_last_host = gmp_strval($last_host); $last_last_host = gmp_strval(gmp_sub($last_host, 1)); } // Validate that the subnet IP & netmask combo are valid together. $ip2 = str_pad(substr($ip1, 0, $cidr), $padding, '0'); $ip1 = ip_mangle($ip1, $fmt); $ip2 = ip_mangle($ip2, $fmt); if ($ip1 != $ip2) { $self['error'] = "ERROR => Invalid subnet specified - did you mean: {$ip2}/{$cidr}?"; return array(6, $self['error'] . "\n"); } // If our IP or netmask changed we need to make sure that // we won't abandon any host interfaces. // We also need to verify that the new boundaries are valid and // don't interefere with any other subnets. if ($check_boundaries == 1) { // *** Check to see if the new subnet overlaps any existing ONA subnets *** // // I convert the IP address to dotted format when calling ona_find_subnet() // because it saves it from doing a few unnecessary sql queries. // Look for overlaps like this (where new subnet address starts inside an existing subnet): // [ -- new subnet -- ] // [ -- old subnet --] list($status, $rows, $record) = ona_find_subnet(ip_mangle($options['set_ip'], 'dotted')); if ($rows and $record['id'] != $subnet['id']) { $self['error'] = "ERROR => Subnet address conflict! New subnet starts inside an existing subnet."; return array(7, $self['error'] . "\n" . "ERROR => Conflicting subnet record ID: {$record['id']}\n"); } // Look for overlaps like this (where the new subnet ends inside an existing subnet): // [ -- new subnet -- ] // [ -- old subnet --] // Find last address of our subnet, and see if it's inside of any other subnet: list($status, $rows, $record) = ona_find_subnet(ip_mangle($str_last_host, 'dotted')); if ($rows and $record['id'] != $subnet['id']) { $self['error'] = "ERROR => Subnet address conflict! New subnet ends inside an existing subnet."; return array(8, $self['error'] . "\n" . "ERROR => Conflicting subnet record ID: {$record['id']}\n"); } // Look for overlaps like this (where the new subnet entirely overlaps an existing subnet): // [ -------- new subnet --------- ] // [ -- old subnet --] // // Do a cool SQL query to find all subnets whose start address is >= or <= the // new subnet base address. $where = "ip_addr >= {$options['set_ip']} AND ip_addr <= {$str_last_host}"; list($status, $rows, $record) = ona_get_subnet_record($where); if ($rows > 1 or $rows == 1 and $record['id'] != $subnet['id']) { $self['error'] = "ERROR => Subnet address conflict! New subnet would encompass an existing subnet."; return array(9, $self['error'] . "\n" . "ERROR => Conflicting subnet record ID: {$record['id']}\n"); } // Look for any hosts that are currently in our subnet that would be // abandoned if we were to make the proposed changes. // Look for hosts on either side of the new subnet boundaries: // [--- new subnet ---] // * ** * * <-- Hosts: the first and last host would be a problem! // [------- old subnet --------] // $where1 = "subnet_id = {$subnet['id']} AND ip_addr < {$first_host}"; $where2 = "subnet_id = {$subnet['id']} AND ip_addr > {$last_last_host}"; list($status, $rows1, $record) = ona_get_interface_record($where1); list($status, $rows2, $record) = ona_get_interface_record($where2); if ($rows1 or $rows2) { $num = $rows1 + $rows2; $self['error'] = "ERROR => Changes would abandon {$num} hosts in an unallocated ip space"; return array(10, $self['error'] . "\n"); } // Look for any dhcp pools that are currently in our subnet that would be // abandoned if we were to make the proposed changes. // Look for existin pools with start/end values outside of new subnet range // [--- new subnet ---] // [--cur pool--] // [------- old subnet --------] // $where1 = "subnet_id = {$subnet['id']} AND ip_addr_start < {$options['set_ip']}"; $where2 = "subnet_id = {$subnet['id']} AND ip_addr_end > {$str_last_host}"; list($status, $rows1, $record) = ona_get_dhcp_pool_record($where1); list($status, $rows2, $record) = ona_get_dhcp_pool_record($where2); if ($rows1 or $rows2) { $num = $rows1 + $rows2; $self['error'] = "ERROR => Changes would abandon a DHCP pool in an unallocated ip space, adjust pool sizes first"; return array(10, $self['error'] . "\n"); } } // // Define the fields we're updating // // This variable will contain the updated info we'll insert into the DB $SET = array(); $SET['ip_addr'] = $options['set_ip']; $SET['ip_mask'] = $options['set_netmask']; // Set options['set_security_level']? // Sanitize "security_level" option if (array_key_exists('set_security_level', $options)) { $options['set_security_level'] = sanitize_security_level($options['set_security_level']); if ($options['set_security_level'] == -1) { return array(11, $self['error'] . "\n"); } $SET['lvl'] = $options['set_security_level']; } // Set options['set_name']? if ($options['set_name']) { // BUSINESS RULE: We require subnet names to be in upper case and spaces are converted to -'s. $options['set_name'] = trim($options['set_name']); $options['set_name'] = preg_replace('/\\s+/', '-', $options['set_name']); $options['set_name'] = strtoupper($options['set_name']); // Make sure there's not another subnet with this name list($status, $rows, $tmp) = ona_get_subnet_record(array('name' => $options['set_name'])); if ($status or $rows > 1 or $rows == 1 and $tmp['id'] != $subnet['id']) { $self['error'] = "ERROR => That name is already used by another subnet!"; return array(12, $self['error'] . "\n"); } $SET['name'] = $options['set_name']; } // Set options['set_type']? if ($options['set_type']) { // Find the type from $options[type] list($status, $rows, $subnet_type) = ona_find_subnet_type($options['set_type']); if ($status or $rows != 1) { $self['error'] = "ERROR => Invalid subnet type specified!"; return array(13, $self['error'] . "\n"); } printmsg("Subnet type selected: {$subnet_type['display_name']} ({$subnet_type['short_name']})", 1); $SET['subnet_type_id'] = $subnet_type['id']; } // Set options['set_vlan']? if (array_key_exists('set_vlan', $options) or $options['campus']) { if (!$options['set_vlan']) { $SET['vlan_id'] = ''; } else { // Find the VLAN ID from $options[set_vlan] and $options[campus] list($status, $rows, $vlan) = ona_find_vlan($options['set_vlan'], $options['campus']); if ($status or $rows != 1) { $self['error'] = "ERROR => The vlan/campus pair specified is invalid!"; return array(15, $self['error'] . "\n"); } printmsg("VLAN selected: {$vlan['name']} in {$vlan['vlan_campus_name']} campus", 1); $SET['vlan_id'] = $vlan['id']; } } // Update the subnet record list($status, $rows) = db_update_record($onadb, 'subnets', array('id' => $subnet['id']), $SET); if ($status or !$rows) { return array(16, $self['error'] . "\n"); } // Load the updated record for display list($status, $rows, $subnet) = ona_get_subnet_record(array('id' => $subnet['id'])); // Return the (human-readable) success notice $text = format_array($SET); $self['error'] = "INFO => Subnet UPDATED"; return array(0, $self['error'] . ":\n{$text}\n"); }