r4236 gwSendFile(): add type check
[racktables] / wwwroot / inc / gateways.php
CommitLineData
b325120a 1<?php
d33645ff
DO
2/*
3*
4* This file contains gateway functions for RackTables.
5* A gateway is an external executable, which provides
6* read-only or read-write access to some external entities.
7* Each gateway accepts its own list of command-line args
8* and then reads its stdin for requests. Each request consists
9* of one line and results in exactly one line of reply.
10* The replies must have the following syntax:
11* OK<space>any text up to the end of the line
12* ERR<space>any text up to the end of the line
13*
14*/
15
ad541266
DO
16// translating functions maps
17$gwrxlator = array();
ffd829af
DO
18$gwrxlator['getcdpstatus'] = array
19(
20 'ios12' => 'ios12ReadCDPStatus',
21 'nxos4' => 'ios12ReadCDPStatus',
22);
50636557
DO
23$gwrxlator['getlldpstatus'] = array
24(
1ebbf889 25 'ios12' => 'ios12ReadLLDPStatus',
50636557
DO
26 'xos12' => 'xos12ReadLLDPStatus',
27 'vrp53' => 'vrp53ReadLLDPStatus',
e11d4936 28 'vrp55' => 'vrp55ReadLLDPStatus',
50636557 29);
db64180c 30$gwrxlator['get8021q'] = array
ad541266
DO
31(
32 'ios12' => 'ios12ReadVLANConfig',
33 'fdry5' => 'fdry5ReadVLANConfig',
34 'vrp53' => 'vrp53ReadVLANConfig',
ec523868 35 'vrp55' => 'vrp55Read8021QConfig',
ad541266 36 'nxos4' => 'nxos4Read8021QConfig',
1aa648ca 37 'xos12' => 'xos12Read8021QConfig',
ad541266 38);
65e557dd
AA
39$gwrxlator['getportstatus'] = array
40(
41 'ios12' => 'ciscoReadInterfaceStatus',
42 'vrp53' => 'vrpReadInterfaceStatus',
43 'vrp55' => 'vrpReadInterfaceStatus',
44 'nxos4' => 'ciscoReadInterfaceStatus',
45);
46$gwrxlator['getmaclist'] = array
47(
48 'ios12' => 'ios12ReadMacList',
49 'vrp53' => 'vrp53ReadMacList',
50 'vrp55' => 'vrp55ReadMacList',
51 'nxos4' => 'nxos4ReadMacList',
52);
d33645ff 53
0328f6d6
DO
54$gwrxlator['gethndp']['vrp53'] = 'vrp53ReadHNDPStatus';
55
50520b40
DO
56$gwpushxlator = array
57(
58 'ios12' => 'ios12TranslatePushQueue',
59 'fdry5' => 'fdry5TranslatePushQueue',
60 'vrp53' => 'vrp53TranslatePushQueue',
ec523868 61 'vrp55' => 'vrp55TranslatePushQueue',
50520b40
DO
62 'nxos4' => 'ios12TranslatePushQueue', // employ syntax compatibility
63 'xos12' => 'xos12TranslatePushQueue',
64);
65
d33645ff
DO
66// This function launches specified gateway with specified
67// command-line arguments and feeds it with the commands stored
68// in the second arg as array.
69// The answers are stored in another array, which is returned
70// by this function. In the case when a gateway cannot be found,
71// finishes prematurely or exits with non-zero return code,
72// a single-item array is returned with the only "ERR" record,
73// which explains the reason.
2da7c9b0 74function queryGateway ($gwname, $questions)
d33645ff
DO
75{
76 $execpath = "./gateways/{$gwname}/main";
d33645ff
DO
77 $dspec = array
78 (
79 0 => array ("pipe", "r"),
80 1 => array ("pipe", "w"),
81 2 => array ("file", "/dev/null", "a")
82 );
83 $pipes = array();
2da7c9b0 84 $gateway = proc_open ($execpath, $dspec, $pipes);
d33645ff 85 if (!is_resource ($gateway))
4d2e93f2 86 return array ('ERR proc_open() failed in ' . __FUNCTION__);
d33645ff
DO
87
88// Dialogue starts. Send all questions.
89 foreach ($questions as $q)
90 fwrite ($pipes[0], "$q\n");
91 fclose ($pipes[0]);
92
93// Fetch replies.
2da7c9b0 94 $answers = array ();
d33645ff
DO
95 while (!feof($pipes[1]))
96 {
97 $a = fgets ($pipes[1]);
59a83bd8 98 if (!strlen ($a))
d33645ff 99 continue;
075aeb88
DO
100 // Somehow I got a space appended at the end. Kick it.
101 $answers[] = trim ($a);
d33645ff
DO
102 }
103 fclose($pipes[1]);
104
105 $retval = proc_close ($gateway);
106 if ($retval != 0)
3a089a44 107 throw new RTGatewayError ("gateway failed with code ${retval}");
7a60526f 108 if (!count ($answers))
3a089a44 109 throw new RTGatewayError ('no response from gateway');
7a60526f 110 if (count ($answers) != count ($questions))
3a089a44 111 throw new RTGatewayError ('protocol violation');
7a60526f 112 foreach ($answers as $a)
793ba0cc 113 if (strpos ($a, 'OK!') !== 0)
3a089a44 114 throw new RTGatewayError ("subcommand failed with status: ${a}");
d33645ff
DO
115 return $answers;
116}
117
0a1b1268 118// This functions returns an array for VLAN list, and an array for port list (both
18cb9495 119// form another array themselves) and another one with MAC address list.
0a1b1268
DO
120// The ports in the latter array are marked with either VLAN ID or 'trunk'.
121// We don't sort the port list, as the gateway is believed to have done this already
122// (or at least the underlying switch software ought to). This is important, as the
123// port info is transferred to/from form not by names, but by numbers.
124function getSwitchVLANs ($object_id = 0)
125{
126 global $remote_username;
6297d584 127 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
128 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
129 if (count ($endpoints) == 0)
3a089a44 130 throw new RTGatewayError ('no management address set');
0a1b1268 131 if (count ($endpoints) > 1)
3a089a44 132 throw new RTGatewayError ('cannot pick management address');
0a1b1268 133 $hwtype = $swtype = 'unknown';
7fa7047a 134 foreach (getAttrValues ($object_id) as $record)
0a1b1268 135 {
e609f5c8 136 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 137 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
e609f5c8 138 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 139 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
0a1b1268 140 }
ee2174dd 141 $endpoint = str_replace (' ', '+', $endpoints[0]);
18cb9495 142 $commands = array
0a1b1268 143 (
ee2174dd 144 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
18cb9495
DO
145 'listvlans',
146 'listports',
147 'listmacs'
0a1b1268 148 );
18cb9495 149 $data = queryGateway ('switchvlans', $commands);
0a1b1268 150 if (strpos ($data[0], 'OK!') !== 0)
3a089a44 151 throw new RTGatewayError ("gateway failed with status: ${data[0]}.");
0a1b1268
DO
152 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
153 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
154 if (count ($tmp) == 0)
3a089a44 155 throw new RTGatewayError ('gateway returned no records');
0a1b1268
DO
156 $vlanlist = array();
157 foreach ($tmp as $record)
158 {
159 list ($vlanid, $vlandescr) = explode ('=', $record);
160 $vlanlist[$vlanid] = $vlandescr;
161 }
162 $portlist = array();
0a73c02f 163 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
0a1b1268 164 {
0a73c02f
DO
165 list ($portname, $pair2) = explode ('=', $pair);
166 list ($status, $vlanid) = explode (',', $pair2);
167 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
0a1b1268
DO
168 }
169 if (count ($portlist) == 0)
3a089a44 170 throw new RTGatewayError ('gateway returned no records');
18cb9495
DO
171 $maclist = array();
172 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
173 {
174 list ($macaddr, $pair2) = explode ('=', $pair);
59a83bd8 175 if (!strlen ($pair2))
18cb9495
DO
176 continue;
177 list ($vlanid, $ifname) = explode ('@', $pair2);
178 $maclist[$ifname][$vlanid][] = $macaddr;
179 }
180 return array ($vlanlist, $portlist, $maclist);
0a1b1268
DO
181}
182
183function setSwitchVLANs ($object_id = 0, $setcmd)
184{
185 global $remote_username;
6297d584 186 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
187 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
188 if (count ($endpoints) == 0)
3f052a67 189 throw new RTGatewayError ('no management address set');
0a1b1268 190 if (count ($endpoints) > 1)
3f052a67 191 throw new RTGatewayError ('cannot pick management address');
0a1b1268 192 $hwtype = $swtype = 'unknown';
7fa7047a 193 foreach (getAttrValues ($object_id) as $record)
0a1b1268 194 {
e609f5c8 195 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 196 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
e609f5c8 197 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 198 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
0a1b1268 199 }
15bb23dc 200 $endpoint = str_replace (' ', '+', $endpoints[0]);
0a1b1268
DO
201 $data = queryGateway
202 (
203 'switchvlans',
15bb23dc 204 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
0a1b1268 205 );
0a1b1268 206 // Finally we can parse the response into message array.
c0042a75 207 $log = emptyLog();
b591ac21 208 foreach (explode (';', substr ($data[1], strlen ('OK!'))) as $text)
0a1b1268 209 {
24dcb9d8
DO
210 if (strpos ($text, 'C!') === 0)
211 {
c0042a75 212 // gateway-encoded message
b591ac21 213 $tmp = explode ('!', $text);
f0ff4930
DO
214 array_shift ($tmp);
215 $code = array_shift ($tmp);
c0042a75 216 $log = mergeLogs ($log, oneLiner ($code, $tmp));
24dcb9d8
DO
217 }
218 elseif (strpos ($text, 'I!') === 0)
c0042a75 219 $log = mergeLogs ($log, oneLiner (62, array (substr ($text, 2)))); // generic gateway success
0a1b1268 220 elseif (strpos ($text, 'W!') === 0)
c0042a75 221 $log = mergeLogs ($log, oneLiner (202, array (substr ($text, 2)))); // generic gateway warning
0a1b1268 222 else // All improperly formatted messages must be treated as error conditions.
c0042a75 223 $log = mergeLogs ($log, oneLiner (166, array (substr ($text, 2)))); // generic gateway error
0a1b1268 224 }
c0042a75 225 return $log;
0a1b1268
DO
226}
227
e77d763c
DO
228// Drop a file off RackTables platform. The gateway will catch the file and pass it to the given
229// installer script.
1ee5488d 230function gwSendFile ($endpoint, $handlername, $filetext = array())
c030232f 231{
b063cc8e
DO
232 if (! is_array ($filetext))
233 throw new InvalidArgException ('filetext', '(suppressed)', 'is not an array');
c030232f 234 global $remote_username;
1ee5488d 235 $tmpnames = array();
f3d274bf 236 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
1ee5488d
DO
237 $command = "submit ${remote_username} ${endpoint} ${handlername}";
238 foreach ($filetext as $text)
239 {
240 $name = tempnam ('', 'RackTables-sendfile-');
241 $tmpnames[] = $name;
01af4445 242 if (FALSE === $name or FALSE === file_put_contents ($name, $text))
1d34465d
DO
243 {
244 foreach ($tmpnames as $name)
245 unlink ($name);
3a089a44 246 throw new RTGatewayError ('failed to write to temporary file');
1d34465d 247 }
1ee5488d
DO
248 $command .= " ${name}";
249 }
e11d4936
DO
250 try
251 {
252 queryGateway ('sendfile', array ($command));
253 foreach ($tmpnames as $name)
254 unlink ($name);
255 }
256 catch (RTGatewayError $e)
257 {
258 foreach ($tmpnames as $name)
259 unlink ($name);
260 throw $e;
261 }
f3d274bf 262}
1b494df6 263
9bc28e53
DO
264// Query something through a gateway and get some text in return. Return that text.
265function gwRecvFile ($endpoint, $handlername, &$output)
266{
267 global $remote_username;
268 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
269 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
e11d4936
DO
270 try
271 {
272 queryGateway ('sendfile', array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}"));
273 $output = file_get_contents ($tmpfilename);
274 unlink ($tmpfilename);
275 }
276 catch (RTGatewayError $e)
277 {
278 unlink ($tmpfilename);
279 throw $e;
280 }
ec523868
DO
281 if ($output === FALSE)
282 throw new RTGatewayError ('failed to read temporary file');
9bc28e53
DO
283}
284
3f052a67 285function gwSendFileToObject ($object_id, $handlername, $filetext = '')
f3d274bf 286{
3f052a67
DO
287 if (!mb_strlen ($handlername))
288 throw new InvalidArgException ('$handlername');
6297d584 289 $objectInfo = spotEntity ('object', $object_id);
f3d274bf
DO
290 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
291 if (count ($endpoints) == 0)
3a089a44 292 throw new RTGatewayError ('no management address set');
f3d274bf 293 if (count ($endpoints) > 1)
3a089a44 294 throw new RTGatewayError ('cannot pick management address');
01af4445 295 gwSendFile (str_replace (' ', '+', $endpoints[0]), $handlername, array ($filetext));
c030232f
DO
296}
297
3f052a67 298function gwRecvFileFromObject ($object_id, $handlername, &$output)
9bc28e53 299{
3f052a67
DO
300 if (!mb_strlen ($handlername))
301 throw new InvalidArgException ('$handlername');
6297d584 302 $objectInfo = spotEntity ('object', $object_id);
9bc28e53
DO
303 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
304 if (count ($endpoints) == 0)
3f052a67 305 throw new RTGatewayError ('no management address set');
9bc28e53 306 if (count ($endpoints) > 1)
3f052a67
DO
307 throw new RTGatewayError ('cannot pick management address');
308 gwRecvFile (str_replace (' ', '+', $endpoints[0]), $handlername, $output);
9bc28e53
DO
309}
310
9dd73255 311function detectDeviceBreed ($object_id)
aff10dee 312{
ec523868
DO
313 $breed_by_swcode = array
314 (
315 251 => 'ios12',
316 252 => 'ios12',
317 254 => 'ios12',
318 963 => 'nxos4',
319 964 => 'nxos4',
3c6595fa 320 1365 => 'nxos4',
ec523868
DO
321 1352 => 'xos12',
322 1360 => 'vrp53',
323 1361 => 'vrp55',
908c2e7d 324 1369 => 'vrp55', // VRP versions 5.5 and 5.7 seem to be compatible
ec523868
DO
325 1363 => 'fdry5',
326 );
aff10dee 327 foreach (getAttrValues ($object_id) as $record)
ec523868
DO
328 if ($record['id'] == 4 and array_key_exists ($record['key'], $breed_by_swcode))
329 return $breed_by_swcode[$record['key']];
9dd73255
DO
330 return '';
331}
332
74d3f7da 333function getRunning8021QConfig ($object_id)
9dd73255 334{
db64180c 335 $ret = gwRetrieveDeviceConfig ($object_id, 'get8021q');
f740bd2f
DO
336 // Once there is no default VLAN in the parsed data, it means
337 // something else was parsed instead of config text.
338 if (!in_array (VLAN_DFL_ID, $ret['vlanlist']))
3a089a44 339 throw new RTGatewayError ('communication with device failed');
f740bd2f 340 return $ret;
dbc00990
DO
341}
342
9dd73255
DO
343function setDevice8021QConfig ($object_id, $pseudocode)
344{
2d8fd72f 345 require_once 'deviceconfig.php';
9dd73255 346 if ('' == $breed = detectDeviceBreed ($object_id))
3a089a44 347 throw new RTGatewayError ('device breed unknown');
50520b40 348 global $gwpushxlator;
f82a94da 349 // FIXME: this is a perfect place to log intended changes
5017142e 350 gwDeployDeviceConfig ($object_id, $breed, unix2dos ($gwpushxlator[$breed] ($pseudocode)));
9dd73255
DO
351}
352
793ba0cc 353function gwRetrieveDeviceConfig ($object_id, $command)
dbc00990 354{
b519e37e 355 require_once 'deviceconfig.php';
ad541266
DO
356 global $gwrxlator;
357 if (!array_key_exists ($command, $gwrxlator))
3a089a44 358 throw new RTGatewayError ('command unknown');
ad541266
DO
359 $breed = detectDeviceBreed ($object_id);
360 if (!array_key_exists ($breed, $gwrxlator[$command]))
3a089a44 361 throw new RTGatewayError ('device breed unknown');
dbc00990
DO
362 $objectInfo = spotEntity ('object', $object_id);
363 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
364 if (count ($endpoints) == 0)
3a089a44 365 throw new RTGatewayError ('no management address set');
dbc00990 366 if (count ($endpoints) > 1)
3a089a44 367 throw new RTGatewayError ('cannot pick management address');
dbc00990
DO
368 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
369 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
e11d4936
DO
370 try
371 {
372 queryGateway ('deviceconfig', array ("${command} ${endpoint} ${breed} ${tmpfilename}"));
373 $configtext = file_get_contents ($tmpfilename);
374 unlink ($tmpfilename);
375 }
376 catch (RTGatewayError $e)
377 {
378 unlink ($tmpfilename);
379 throw $e;
380 }
ec523868
DO
381 if ($configtext === FALSE)
382 throw new RTGatewayError ('failed to read temporary file');
0f86f02c 383 // Being here means it was alright.
ec523868 384 return $gwrxlator[$command][$breed] (dos2unix ($configtext));
aff10dee
DO
385}
386
9dd73255
DO
387function gwDeployDeviceConfig ($object_id, $breed, $text)
388{
f82a94da
DO
389 if ($text == '')
390 throw new InvalidArgException ('text', '', 'deploy text is empty');
9dd73255
DO
391 $objectInfo = spotEntity ('object', $object_id);
392 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
393 if (count ($endpoints) == 0)
3a089a44 394 throw new RTGatewayError ('no management address set');
9dd73255 395 if (count ($endpoints) > 1)
3a089a44 396 throw new RTGatewayError ('cannot pick management address');
9dd73255
DO
397 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
398 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
399 if (FALSE === file_put_contents ($tmpfilename, $text))
01af4445
DO
400 {
401 unlink ($tmpfilename);
3a089a44 402 throw new RTGatewayError ('failed to write to temporary file');
01af4445 403 }
e11d4936
DO
404 try
405 {
406 queryGateway ('deviceconfig', array ("deploy ${endpoint} ${breed} ${tmpfilename}"));
407 unlink ($tmpfilename);
408 }
409 catch (RTGatewayError $e)
410 {
411 unlink ($tmpfilename);
412 throw $e;
413 }
9dd73255
DO
414}
415
d33645ff 416?>