swreg.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <?php
  2. //-
  3. // Copyright (c) 2011-2016 Joe Clarke <jclarke@cisco.com>
  4. // All rights reserved.
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions
  7. // are met:
  8. // 1. Redistributions of source code must retain the above copyright
  9. // notice, this list of conditions and the following disclaimer.
  10. // 2. Redistributions in binary form must reproduce the above copyright
  11. // notice, this list of conditions and the following disclaimer in the
  12. // documentation and/or other materials provided with the distribution.
  13. // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  14. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  16. // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  17. // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  19. // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20. // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22. // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23. // SUCH DAMAGE.
  24. include_once '../db.inc.php';
  25. include_once 'swreg.inc.php';
  26. include_once '../swreg_creds.inc.php';
  27. require_once '../functions.php';
  28. require_once 'Log.php';
  29. function process_macro($name, $value, $hn, $sn, $pid, &$logger)
  30. {
  31. $contents = '';
  32. if (preg_match('/^file:(.*)/', $value, $match)) {
  33. if (!is_file($match[1])) {
  34. $logger->crit("Macro {$name} failed to evaluate: file {$match[1]} does not exist");
  35. exit(1);
  36. }
  37. @$contents = file_get_contents(trim($match[1]));
  38. } elseif (preg_match('/^script:(.*)/', $value, $match)) {
  39. $script = trim($match[1]);
  40. $output = array();
  41. $rc = 0;
  42. $hn = escapeshellarg($hn);
  43. $sn = escapeshellarg($sn);
  44. $pid = escapeshellarg($pid);
  45. @exec("$script $name $hn $sn $pid", $output, $rc);
  46. $contents = implode("\n", $output);
  47. if ($rc != 0) {
  48. $logger->crit("Macro {$name} failed to evaluate: {$contents}");
  49. exit(1);
  50. }
  51. } else {
  52. $contents = $value;
  53. }
  54. return trim($contents);
  55. }
  56. $dsn = "$db_driver:host=$db_host;dbname=$db_name";
  57. $options = [
  58. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  59. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  60. PDO::ATTR_EMULATE_PREPARES => false,
  61. ];
  62. try {
  63. $dbh = new PDO($dsn, $db_user, $db_pass, $options);
  64. } catch (PDOException $e) {
  65. die($e->getMessage());
  66. }
  67. $logger = Log::singleton('file', LOGFILE, TOOL_NAME . ' : Switch Registrar');
  68. if ($logger === false) {
  69. die("Failed to open logfile.\n");
  70. }
  71. $mask = Log::MAX(LOG_LEVEL);
  72. $logger->setMask($mask);
  73. $pid = $_GET['pid'];
  74. $sn = $_GET['sn'];
  75. $version = $_GET['version'];
  76. $num_ports = $_GET['num_ports'];
  77. $imagef = $_GET['imagef'];
  78. header('Content-type: text/plain');
  79. if (!isset($pid) || !isset($sn) || !isset($version) || !isset($num_ports)) {
  80. echo "ERROR: Invalid request!\r\n";
  81. $logger->emerg('Invalid request from ' . $_SERVER['REMOTE_ADDR'] . " pid = '$pid', sn = '$sn', version = '$version', num_ports = '$num_ports'");
  82. exit(1);
  83. }
  84. $logger->debug("Received request from $sn: pid = $pid, version = $version, num_ports = $num_ports");
  85. $image = null;
  86. $config = null;
  87. $found_pid = null;
  88. $not_configed = "ERROR: Tool not properly configured.\r\n";
  89. if (isset($PID_ALIASES[$pid])) {
  90. $pid = $PID_ALIASES[$pid];
  91. }
  92. foreach ($ZTP_PIDS as $zpid) {
  93. if (preg_match("/^$zpid$/", $pid)) {
  94. if (!isset($IMG_VERS[$zpid])) {
  95. echo $not_configed;
  96. $logger->emerg("\$IMG_VERS[$zpid] is not defined in swreg.inc.php!");
  97. exit(1);
  98. }
  99. if (!isset($IMG_FILES[$zpid])) {
  100. echo $not_configed;
  101. $logger->emerg("\$IMG_FILES[$zpid] is not defined in swreg.inc.php!");
  102. exit(1);
  103. }
  104. if ($version != $IMG_VERS[$zpid] || $imagef != $IMG_FILES[$zpid]) {
  105. $image = $IMG_FILES[$zpid];
  106. }
  107. if (!isset($START_PORTS[$zpid])) {
  108. echo $not_configed;
  109. $logger->emerg("\$START_PORTS[$zpid] is not defined in swreg.inc.php!");
  110. exit(1);
  111. }
  112. $start_port = $START_PORTS[$zpid];
  113. if (!isset($PORT_TYPES[$zpid])) {
  114. echo $not_configed;
  115. $logger->emerg("\$PORT_TYPES[$zpid] is not defined in swreg.inc.php!");
  116. exit(1);
  117. }
  118. $port_type = $PORT_TYPES[$zpid];
  119. $found_pid = $zpid;
  120. }
  121. }
  122. if ($found_pid === null) {
  123. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, "Request from unknown PID: {$pid}"));
  124. $logger->crit("Request from unknown PID: $pid");
  125. echo "ERROR: Unknown PID: $pid\r\n";
  126. exit(1);
  127. }
  128. $use_pnp = false;
  129. if (USE_PNP === true) {
  130. foreach ($PNP_PATTERNS as $pattern => $ipnp) {
  131. if (preg_match("/$pattern/", $image)) {
  132. $use_pnp = $ipnp;
  133. break;
  134. }
  135. }
  136. }
  137. $sql = 'SELECT assigned_switch, max_ports, checked_out FROM DEVICE_MAP WHERE serial_number = ?';
  138. $res = null;
  139. $sth = null;
  140. try {
  141. $sth = $dbh->prepare($sql);
  142. $res = $sth->execute(array($sn));
  143. } catch (PDOException $e) {
  144. echo 'ERROR: Failed to get switch list: ' . $e->getMessage() . "\r\n";
  145. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, 'Failed to get switch list: ' . $e->getMessage()));
  146. $logger->crit('Failed to get switch list: ' . $e->getMessage());
  147. exit(1);
  148. }
  149. $row = $sth->fetch();
  150. $sth->closeCursor();
  151. $max_ports = $row['max_ports'];
  152. if ($row['checked_out'] == 0) {
  153. echo "ERROR: Switch has not yet been provisioned via the web\r\n";
  154. $logger->crit("Switch $sn ({$_SERVER['REMOTE_ADDR']}) has not yet been provisioned via the web");
  155. call_hook('REQUEST:UNKNOWN', array($_SERVER['REMOTE_ADDR'], $sn, $pid));
  156. exit(1);
  157. }
  158. if ($max_ports > $num_ports) {
  159. echo "ERROR: Switch has been provisioned for $max_ports but this physical switch only has $num_ports\r\n";
  160. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, "Switch has been provisioned for {$max_ports} but this physical switch only has {$num_ports}"));
  161. $logger->crit("Switch $sn has been provisioned for $max_ports but this physical switch only has $num_ports");
  162. exit(1);
  163. }
  164. $sql = 'SELECT s.name AS hostname, s.location AS snmp_loc, s.is_idf AS is_idf, a.address AS ip, a.router AS gw, a.mask AS mask, a.mgmt_vlan AS mgmt_vlan, a.location AS location FROM SWITCHES s, ADDRESSES a WHERE s.name = ? AND s.ip_address = a.address';
  165. try {
  166. $sth = $dbh->prepare($sql);
  167. $res = $sth->execute(array($row['assigned_switch']));
  168. } catch (PDOException $e) {
  169. echo 'ERROR: Failed to get logical switch details: ' . $e->getMessage() . "\r\n";
  170. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, 'Failed to get logical switch details: ' . $e->getMessage()));
  171. $logger->crit('Failed to get logical switch details: ' . $e->getMessage());
  172. exit(1);
  173. }
  174. $row = $sth->fetch();
  175. $sth->closeCursor();
  176. call_hook('REQUEST:NEW', array($_SERVER['REMOTE_ADDR'], $row['hostname'], $sn, $pid, $version, $num_ports, $imagef));
  177. if ($image !== null && !file_exists(TFTPBOOT . '/' . $image)) {
  178. echo "ERROR: Image file {$image} does not exist in " . TFTPBOOT . "\r\n";
  179. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, "Image file {$image} does not exist in " . TFTPBOOT));
  180. $logger->crit("Image file {$image} for {$sn}, {$pid} does not exist in " . TFTPBOOT);
  181. exit(1);
  182. }
  183. $sql = 'UPDATE DEVICE_MAP SET provisioned_status = ? WHERE serial_number = ?';
  184. try {
  185. $sth = $dbh->prepare($sql);
  186. $sth->execute(array(PROVISION_IN_PROGRESS, $sn));
  187. $sth->closeCursor();
  188. } catch (PDOException $e) {
  189. call_hook('REQUEST:ERROR', array($_SERVER['REMOTE_ADDR'], $sn, $pid, "Failed to update switch provisioned status: {$e->getMessage()}"));
  190. echo "ERROR: Failed to update switch provisioned status: {$e->getMessage()}\r\n";
  191. $logger->crit("Failed to update switch provisioned status: {$e->getMessage()}");
  192. exit(1);
  193. }
  194. $port_contents = "interface range {$port_type}{$start_port} - $max_ports\n";
  195. if (file_exists(PORT_TMPL_DIR . '/devices/' . $row['hostname'] . '-ports.tmpl')) {
  196. $port_contents = file_get_contents(PORT_TMPL_DIR . '/devices/' . $row['hostname'] . '-ports.tmpl');
  197. if (file_exists(PORT_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-ports.tmpl')) {
  198. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-ports.tmpl');
  199. }
  200. } else {
  201. if (file_exists(PORT_TMPL_DIR . '/ports.tmpl')) {
  202. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/ports.tmpl');
  203. }
  204. if (file_exists(PORT_TMPL_DIR . '/' . $found_pid . '/ports.tmpl')) {
  205. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/' . $found_pid . '/ports.tmpl');
  206. }
  207. if (file_exists(PORT_TMPL_DIR . '/DYNAMIC/ports.tmpl')) {
  208. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/DYNAMIC/ports.tmpl');
  209. }
  210. if (file_exists(PORT_TMPL_DIR . '/DYNAMIC/' . $found_pid . '/ports.tmpl')) {
  211. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/DYNAMIC/' . $found_pid . '/ports.tmpl');
  212. }
  213. if (file_exists(PORT_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-ports.tmpl')) {
  214. $port_contents .= file_get_contents(PORT_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-ports.tmpl');
  215. }
  216. }
  217. $snmp_loc = $row['snmp_loc'];
  218. if (!$snmp_loc || $snmp_loc == '') {
  219. $snmp_loc = $row['location'];
  220. }
  221. $ew_keywords = preg_replace("/\s/", '_', $snmp_loc);
  222. $ew_role = 'ACCESS-SWITCH';
  223. if ($row['is_idf'] == 1) {
  224. $ew_role = 'IDF-SWITCH';
  225. }
  226. $contents = '';
  227. if (file_exists(DEVICE_TMPL_DIR . '/device.tmpl')) {
  228. $contents .= file_get_contents(DEVICE_TMPL_DIR . '/device.tmpl');
  229. }
  230. if (file_exists(DEVICE_TMPL_DIR . '/' . $found_pid . '/device.tmpl')) {
  231. $contents .= file_get_contents(DEVICE_TMPL_DIR . '/' . $found_pid . '/device.tmpl');
  232. }
  233. if (file_exists(DEVICE_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-device.tmpl')) {
  234. $contents .= file_get_contents(DEVICE_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-device.tmpl');
  235. }
  236. $contents .= "end\n";
  237. $eem_contents = '';
  238. if (file_exists(EEM_TMPL_DIR . '/eem.tmpl')) {
  239. $eem_contents = file_get_contents(EEM_TMPL_DIR . '/eem.tmpl');
  240. }
  241. if (file_exists(EEM_TMPL_DIR . '/' . $found_pid . '/eem.tmpl')) {
  242. $eem_contents .= file_get_contents(EEM_TMPL_DIR . '/' . $found_pid . '/eem.tmpl');
  243. }
  244. if (file_exists(EEM_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-eem.tmpl')) {
  245. $eem_contents .= file_get_contents(EEM_TMPL_DIR . '/device-overrides/' . $row['hostname'] . '-eem.tmpl');
  246. }
  247. $contents = str_replace('%%EEM_CONFIG%%', $eem_contents, $contents);
  248. $vlan_contents = file_get_contents(VLAN_TMPL_DIR . '/' . strtolower($row['location']) . '-vlan.tmpl');
  249. $contents = str_replace('%%VLAN_TMPL%%', $vlan_contents, $contents);
  250. $contents = str_replace('%%PORT_CONFIG%%', $port_contents, $contents);
  251. $contents = str_replace('%%MGMT_VLAN%%', $row['mgmt_vlan'], $contents);
  252. $contents = str_replace('%%HOSTNAME%%', $row['hostname'], $contents);
  253. $contents = str_replace('%%EW_DOMAIN%%', $row['location'], $contents);
  254. $contents = str_replace('%%EW_KEYWORDS%%', $ew_keywords, $contents);
  255. $contents = str_replace('%%EW_ROLE%%', $ew_role, $contents);
  256. $contents = str_replace('%%VTP_DOMAIN%%', $row['location'], $contents);
  257. $contents = str_replace('%%MGMT_IP%%', $row['ip'], $contents);
  258. $contents = str_replace('%%MGMT_MASK%%', $row['mask'], $contents);
  259. $contents = str_replace('%%MGMT_GW%%', $row['gw'], $contents);
  260. $contents = str_replace('%%SNMP_LOCATION%%', $snmp_loc, $contents);
  261. if (MAP_V6 === true) {
  262. $octets = explode('.', $row['ip']);
  263. $contents = str_replace('%%MGMT_IP6%%', IPV6_PREFIX . ':' . $octets[3], $contents);
  264. $contents = str_replace('%%MGMT_PREFIXLEN%%', IPV6_PREFIXLEN, $contents);
  265. $contents = str_replace('%%MGMT_GW6%%', IPV6_GW, $contents);
  266. }
  267. $contents = str_replace('%%START_PORT%%', $start_port, $contents);
  268. $contents = str_replace('%%MAX_PORT%%', $max_ports, $contents);
  269. // XXX: TODO
  270. $contents = str_replace('%%STACK_CONFIG%%', '!', $contents);
  271. // Replace creds
  272. $contents = str_replace('%%ADMIN_SECRET%%', ADMIN_SECRET, $contents);
  273. $contents = str_replace('%%TACACS_KEY%%', TACACS_KEY, $contents);
  274. $contents = str_replace('%%EW_SHARED_SECRET%%', EW_SHARED_SECRET, $contents);
  275. $contents = str_replace('%%EW_MGMT_SHARED_SECRET%%', EW_MGMT_SHARED_SECRET, $contents);
  276. $contents = str_replace('%%SNMPV3_USER%%', SNMPV3_USER, $contents);
  277. $contents = str_replace('%%SNMPV3_PASS%%', SNMPV3_PASS, $contents);
  278. if (isset($VLAN_OVERRIDES) && isset($VLAN_OVERRIDES[$row['location']])) {
  279. foreach ($VLAN_OVERRIDES[$row['location']] as $vn => $vi) {
  280. $VLANS[$vn] = $vi;
  281. }
  282. }
  283. foreach ($VLANS as $vn => $vi) {
  284. $vname = strtoupper($vn);
  285. $vname = str_replace(' ', '_', $vname);
  286. $macro = '%%' . $vname . '_VLAN%%';
  287. $contents = str_replace($macro, $vi, $contents);
  288. }
  289. $custom_macros = array();
  290. if (isset($CUSTOM_MACROS)) {
  291. foreach ($CUSTOM_MACROS as $macro => $mv) {
  292. $custom_macros[$macro] = process_macro($macro, $mv, $row['hostname'], $sn, $found_pid, $logger);
  293. }
  294. }
  295. if (isset($MDF_OVERRIDES) && isset($MDF_OVERRIDES[$row['location']])) {
  296. foreach ($MDF_OVERRIDES[$row['locations']] as $macro => $mv) {
  297. $custom_macros[$macro] = process_macro($macro, $mv, $row['hostname'], $sn, $found_pid, $logger);
  298. }
  299. }
  300. if (isset($PID_MACROS) && isset($PID_MACROS[$found_pid])) {
  301. foreach ($PID_MACROS[$found_pid] as $macro => $mv) {
  302. $custom_macros[$macro] = process_macro($macro, $mv, $row['hostname'], $sn, $found_pid, $logger);
  303. }
  304. }
  305. foreach ($custom_macros as $macro => $mv) {
  306. $contents = str_replace("%%$macro%%", $mv, $contents);
  307. }
  308. umask(0022);
  309. $fd = @fopen(DEVICE_CONFIG_DIR . '/' . $row['hostname'] . '-config.txt', 'w');
  310. fwrite($fd, $contents);
  311. fclose($fd);
  312. $fd = @fopen(DEVICE_TMP_DIR . '/' . $row['hostname'] . '-config.txt', 'w');
  313. fclose($fd);
  314. chmod(DEVICE_TMP_DIR . '/' . $row['hostname'] . '-config.txt', 0666);
  315. if ($use_pnp) {
  316. $error = '';
  317. $ticket = apicGetTicket(APIC_HOST, APIC_USER, APIC_PASS, $error);
  318. if ($ticket === false) {
  319. $logger->error("Failed to get ticket from APIC-EM: $error");
  320. $use_pnp = false;
  321. } else {
  322. $proj = apicGetProject(APIC_HOST, $ticket, APIC_PROJECT, $error);
  323. if ($proj === false) {
  324. $logger->error("Failed to get project from APIC-EM: $error");
  325. $use_pnp = false;
  326. } else {
  327. $ares = apicAddDevice(APIC_HOST, $ticket, $proj['id'], $row['hostname'], $sn, $pid, $error, $config, $image);
  328. if ($ares === false) {
  329. $logger->error("Failed to add device to APIC-EM: $error");
  330. $use_pnp = false;
  331. }
  332. }
  333. }
  334. }
  335. if ($use_pnp) {
  336. echo "PNP\r\n";
  337. $logger->info("Successfully provisioned switch $sn ($pid) {$row['hostname']} as a PnP device");
  338. } else {
  339. echo "Config: {$row['hostname']}-config.txt\r\n";
  340. if (isset($SDM_PROFILES[$pid])) {
  341. echo "SDM: {$SDM_PROFILES[$pid]}\r\n";
  342. }
  343. if ($image !== null) {
  344. echo "Image: $image\r\n";
  345. }
  346. $logger->info("Successfully provisioned switch $sn ($pid) {$row['hostname']}");
  347. }
  348. $logger->close();