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
17 // This function launches specified gateway with specified
18 // command-line arguments and feeds it with the commands stored
19 // in the second arg as array.
20 // The answers are stored in another array, which is returned
21 // by this function. In the case when a gateway cannot be found,
22 // finishes prematurely or exits with non-zero return code,
23 // a single-item array is returned with the only "ERR" record,
24 // which explains the reason.
25 function queryGateway ($gwname, $questions)
27 $execpath = "./gateways/{$gwname}/main";
30 0 => array ("pipe", "r"),
31 1 => array ("pipe", "w"),
32 2 => array ("file", "/dev/null", "a")
35 $gateway = proc_open ($execpath, $dspec, $pipes);
36 if (!is_resource ($gateway))
37 return array ('ERR proc_open() failed in ' . __FUNCTION__
);
39 // Dialogue starts. Send all questions.
40 foreach ($questions as $q)
41 fwrite ($pipes[0], "$q\n");
46 while (!feof($pipes[1]))
48 $a = fgets ($pipes[1]);
51 // Somehow I got a space appended at the end. Kick it.
52 $answers[] = trim ($a);
56 $retval = proc_close ($gateway);
58 return array ("ERR gateway '${gwname}' returned ${retval}");
62 // This functions returns an array for VLAN list, and an array for port list (both
63 // form another array themselves) and another one with MAC address list.
64 // The ports in the latter array are marked with either VLAN ID or 'trunk'.
65 // We don't sort the port list, as the gateway is believed to have done this already
66 // (or at least the underlying switch software ought to). This is important, as the
67 // port info is transferred to/from form not by names, but by numbers.
68 function getSwitchVLANs ($object_id = 0)
70 global $remote_username;
72 throw new InvalidArgException('$object_id', $object_id);
73 $objectInfo = spotEntity ('object', $object_id);
74 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
75 if (count ($endpoints) == 0)
76 throw new RuntimeException('Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.');
77 if (count ($endpoints) > 1)
78 throw new RuntimeException('More than one IP address is assigned to this object, please configure FQDN attribute.');
79 $hwtype = $swtype = 'unknown';
80 foreach (getAttrValues ($object_id) as $record)
82 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
83 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
84 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
85 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
87 $endpoint = str_replace (' ', '+', $endpoints[0]);
90 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
95 $data = queryGateway ('switchvlans', $commands);
97 throw new RuntimeException('Failed to get any response from queryGateway() or the gateway died');
98 if (strpos ($data[0], 'OK!') !== 0)
99 throw new RuntimeException("Gateway failure: ${data[0]}.");
100 if (count ($data) != count ($commands))
101 throw new RuntimeException("Gateway failure: malformed reply.");
102 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
103 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
104 if (count ($tmp) == 0)
105 throw new RuntimeException("Gateway succeeded, but returned no VLAN records.");
107 foreach ($tmp as $record)
109 list ($vlanid, $vlandescr) = explode ('=', $record);
110 $vlanlist[$vlanid] = $vlandescr;
113 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
115 list ($portname, $pair2) = explode ('=', $pair);
116 list ($status, $vlanid) = explode (',', $pair2);
117 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
119 if (count ($portlist) == 0)
120 throw new RuntimeException("Gateway succeeded, but returned no port records.");
122 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
124 list ($macaddr, $pair2) = explode ('=', $pair);
125 if (!strlen ($pair2))
127 list ($vlanid, $ifname) = explode ('@', $pair2);
128 $maclist[$ifname][$vlanid][] = $macaddr;
130 return array ($vlanlist, $portlist, $maclist);
133 function setSwitchVLANs ($object_id = 0, $setcmd)
135 global $remote_username;
137 return oneLiner (160); // invalid arguments
138 $objectInfo = spotEntity ('object', $object_id);
139 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
140 if (count ($endpoints) == 0)
141 return oneLiner (161); // endpoint not found
142 if (count ($endpoints) > 1)
143 return oneLiner (162); // can't pick an address
144 $hwtype = $swtype = 'unknown';
145 foreach (getAttrValues ($object_id) as $record)
147 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
148 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
149 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
150 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
152 $endpoint = str_replace (' ', '+', $endpoints[0]);
156 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
159 return oneLiner (163); // unknown gateway failure
160 if (strpos ($data[0], 'OK!') !== 0)
161 return oneLiner (164, array ($data[0])); // gateway failure
162 if (count ($data) != 2)
163 return oneLiner (165); // protocol violation
164 // Finally we can parse the response into message array.
166 foreach (split (';', substr ($data[1], strlen ('OK!'))) as $text)
168 if (strpos ($text, 'C!') === 0)
170 $tmp = split ('!', $text);
172 $code = array_shift ($tmp);
173 $log_m[] = count ($tmp) ?
array ('c' => $code, 'a' => $tmp) : array ('c' => $code); // gateway-encoded message
175 elseif (strpos ($text, 'I!') === 0)
176 $log_m[] = array ('c' => 62, 'a' => array (substr ($text, 2))); // generic gateway success
177 elseif (strpos ($text, 'W!') === 0)
178 $log_m[] = array ('c' => 202, 'a' => array (substr ($text, 2))); // generic gateway warning
179 else // All improperly formatted messages must be treated as error conditions.
180 $log_m[] = array ('c' => 166, 'a' => array (substr ($text, 2))); // generic gateway error
185 // Drop a file off RackTables platform. The gateway will catch the file and pass it to the given
187 function gwSendFile ($endpoint, $handlername, $filetext = array())
189 global $remote_username;
191 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
192 $command = "submit ${remote_username} ${endpoint} ${handlername}";
193 foreach ($filetext as $text)
195 $name = tempnam ('', 'RackTables-sendfile-');
197 $tmpfile = fopen ($name, 'wb');
198 $failed = FALSE === fwrite ($tmpfile, $text);
199 $failed = FALSE === fclose ($tmpfile) or $fail;
202 foreach ($tmpnames as $name)
204 return oneLiner (164, array ('file write error')); // gateway failure
206 $command .= " ${name}";
208 $outputlines = queryGateway
213 foreach ($tmpnames as $name)
215 if ($outputlines == NULL)
216 return oneLiner (163); // unknown gateway failure
217 if (count ($outputlines) != 1)
218 return oneLiner (165); // protocol violation
219 if (strpos ($outputlines[0], 'OK!') !== 0)
220 return oneLiner (164, array ($outputlines[0])); // gateway failure
221 // Being here means having 'OK!' in the response.
222 return oneLiner (66, array ($handlername)); // ignore provided "Ok" text
225 // Query something through a gateway and get some text in return. Return that text.
226 function gwRecvFile ($endpoint, $handlername, &$output)
228 global $remote_username;
229 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
230 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
231 $outputlines = queryGateway
234 array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}")
236 $output = file_get_contents ($tmpfilename);
237 unlink ($tmpfilename);
238 if ($outputlines == NULL)
239 return oneLiner (163); // unknown gateway failure
240 if (count ($outputlines) != 1)
241 return oneLiner (165); // protocol violation
242 if (strpos ($outputlines[0], 'OK!') !== 0)
243 return oneLiner (164, array ($outputlines[0])); // gateway failure
244 // Being here means having 'OK!' in the response.
245 return oneLiner (66, array ($handlername)); // ignore provided "Ok" text
248 function gwSendFileToObject ($object_id = 0, $handlername, $filetext = '')
250 global $remote_username;
251 if ($object_id <= 0 or !strlen ($handlername))
252 return oneLiner (160); // invalid arguments
253 $objectInfo = spotEntity ('object', $object_id);
254 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
255 if (count ($endpoints) == 0)
256 return oneLiner (161); // endpoint not found
257 if (count ($endpoints) > 1)
258 return oneLiner (162); // can't pick an address
259 $endpoint = str_replace (' ', '+', $endpoints[0]);
260 return gwSendFile ($endpoint, $handlername, array ($filetext));
263 function gwRecvFileFromObject ($object_id = 0, $handlername, &$output)
265 global $remote_username;
266 if ($object_id <= 0 or !strlen ($handlername))
267 return oneLiner (160); // invalid arguments
268 $objectInfo = spotEntity ('object', $object_id);
269 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
270 if (count ($endpoints) == 0)
271 return oneLiner (161); // endpoint not found
272 if (count ($endpoints) > 1)
273 return oneLiner (162); // can't pick an address
274 $endpoint = str_replace (' ', '+', $endpoints[0]);
275 return gwRecvFile ($endpoint, $handlername, $output);
278 function getDevice8021QConfig ($object_id)
281 foreach (getAttrValues ($object_id) as $record)
285 $record['name'] == 'SW type' &&
286 strlen ($record['o_value']) &&
287 preg_match ('/^Cisco IOS 12\./', execGMarker ($record['o_value']))
295 $record['name'] == 'HW type' &&
296 strlen ($record['o_value']) &&
297 preg_match ('/^Foundry FastIron GS /', execGMarker ($record['o_value']))
305 throw new RuntimeException ('cannot pick handler for this device');
308 'ios12' => 'iosReadVLANConfig',
309 'fdry5' => 'fdry5ReadVLANConfig',
311 return $reader[$breed] (dos2unix (gwRetrieveDeviceConfig ($object_id, $breed)));
314 function gwRetrieveDeviceConfig ($object_id, $breed)
316 $objectInfo = spotEntity ('object', $object_id);
317 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
318 if (count ($endpoints) == 0)
319 throw new RuntimeException ('endpoint not found');
320 if (count ($endpoints) > 1)
321 throw new RuntimeException ('cannot pick endpoint');
322 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
323 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
324 $outputlines = queryGateway
327 array ("retrieve ${endpoint} ${breed} ${tmpfilename}")
329 $configtext = file_get_contents ($tmpfilename);
330 unlink ($tmpfilename);
331 if ($outputlines == NULL)
332 throw new RuntimeException ('unknown gateway failure');
333 if (count ($outputlines) != 1)
334 throw new RuntimeException ('gateway protocol violation');
335 if (strpos ($outputlines[0], 'OK!') !== 0)
336 throw new RuntimeException ("gateway failure: ${outputlines[0]}");
337 // Being here means it was alright.