1<?php
2
3# This file is a part of RackTables, a datacenter and server room management
4# framework. See accompanying file "COPYING" for the full copyright and
5# licensing information.
6
7# The array below maps availability of particular commands for each particular
8# device breed. Array values are functions implemented in deviceconfig.php, which
9# is not normally included until any of the functions is actually called.
10$breedfunc = array
11(
12	'ios12-getcdpstatus-main'  => 'ios12ReadCDPStatus',
13	'ios12-getlldpstatus-main' => 'ios12ReadLLDPStatus',
14	'ios12-get8021q-main'      => 'ios12ReadVLANConfig',
15	'ios12-get8021q-swports'   => 'ios12ReadSwitchPortList',
16	'ios12-get8021q-top'       => 'ios12ScanTopLevel',
17	'ios12-get8021q-readport'  => 'ios12PickSwitchportCommand',
18	'ios12-get8021q-readvlan'  => 'ios12PickVLANCommand',
19	'ios12-getportstatus-main' => 'ciscoReadInterfaceStatus',
20	'ios12-getmaclist-main'    => 'ios12ReadMacList',
21	'ios12-getportmaclist-main'=> 'ios12ReadMacList',
22	'ios12-xlatepushq-main'    => 'ios12TranslatePushQueue',
23	'ios12-getallconf-main'    => 'ios12SpotConfigText',
24	'fdry5-get8021q-main'      => 'fdry5ReadVLANConfig',
25	'fdry5-get8021q-top'       => 'fdry5ScanTopLevel',
26	'fdry5-get8021q-readvlan'  => 'fdry5PickVLANSubcommand',
27	'fdry5-get8021q-readport'  => 'fdry5PickInterfaceSubcommand',
28	'fdry5-xlatepushq-main'    => 'fdry5TranslatePushQueue',
29	'fdry5-getallconf-main'    => 'fdry5SpotConfigText',
30	'fdry5-getportstatus-main' => 'foundryReadInterfaceStatus',
31	'fdry5-getmaclist-main'    => 'fdry5ReadMacList',
32	'fdry5-getportmaclist-main'=> 'fdry5ReadMacList',
33	'vrp53-getlldpstatus-main' => 'vrpReadLLDPStatus',
34	'vrp53-get8021q-main'      => 'vrp53ReadVLANConfig',
35	'vrp53-get8021q-top'       => 'vrp53ScanTopLevel',
36	'vrp53-get8021q-readport'  => 'vrp53PickInterfaceSubcommand',
37	'vrp53-getportstatus-main' => 'vrpReadInterfaceStatus',
38	'vrp53-getmaclist-main'    => 'vrp53ReadMacList',
39	'vrp53-getportmaclist-main'=> 'vrp53ReadMacList',
40	'vrp53-xlatepushq-main'    => 'vrp53TranslatePushQueue',
41	'vrp53-getallconf-main'    => 'vrpSpotConfigText',
42	'vrp55-getlldpstatus-main' => 'vrpReadLLDPStatus',
43	'vrp55-get8021q-main'      => 'vrp55Read8021QConfig',
44	'vrp55-getportstatus-main' => 'vrpReadInterfaceStatus',
45	'vrp55-getmaclist-main'    => 'vrpReadMacList',
46	'vrp55-getportmaclist-main'=> 'vrpReadMacList',
47	'vrp55-xlatepushq-main'    => 'vrp55TranslatePushQueue',
48	'vrp55-getallconf-main'    => 'vrpSpotConfigText',
49	'vrp85-getlldpstatus-main' => 'vrpReadLLDPStatus',
50	'vrp85-get8021q-main'      => 'vrp85Read8021QConfig',
51	'vrp85-getportstatus-main' => 'vrpReadInterfaceStatus',
52	'vrp85-getmaclist-main'    => 'vrpReadMacList',
53	'vrp85-getportmaclist-main'=> 'vrpReadMacList',
54	'vrp85-xlatepushq-main'    => 'vrp85TranslatePushQueue',
55	'vrp85-getallconf-main'    => 'vrpSpotConfigText',
56	'nxos4-getcdpstatus-main'  => 'ios12ReadCDPStatus',
57	'nxos4-getlldpstatus-main' => 'ios12ReadLLDPStatus',
58	'nxos4-get8021q-main'      => 'ios12ReadVLANConfig',
59	'nxos4-getportstatus-main' => 'ciscoReadInterfaceStatus',
60	'nxos4-getmaclist-main'    => 'nxos4ReadMacList',
61	'nxos4-getportmaclist-main'=> 'nxos4ReadMacList',
62	'nxos4-xlatepushq-main'    => 'nxos4TranslatePushQueue',
63	'nxos4-getallconf-main'    => 'nxos4SpotConfigText',
64	'dlink-get8021q-main'      => 'dlinkReadVLANConfig',
65	'dlink-get8021q-top'       => 'dlinkScanTopLevel',
66	'dlink-get8021q-pickvlan'  => 'dlinkPickVLANCommand',
67	'dlink-getportstatus-main' => 'dlinkReadInterfaceStatus',
68	'dlink-getmaclist-main'    => 'dlinkReadMacList',
69	'dlink-getportmaclist-main'=> 'dlinkReadMacList',
70	'dlink-xlatepushq-main'    => 'dlinkTranslatePushQueue',
71	'linux-get8021q-main'      => 'linuxReadVLANConfig',
72	'linux-getportstatus-main' => 'linuxReadInterfaceStatus',
73	'linux-getmaclist-main'    => 'linuxReadMacList',
74	'linux-getportmaclist-main'=> 'linuxReadMacList',
75	'linux-xlatepushq-main'    => 'linuxTranslatePushQueue',
76	'xos12-getlldpstatus-main' => 'xos12ReadLLDPStatus',
77	'xos12-get8021q-main'      => 'xos12Read8021QConfig',
78	'xos12-xlatepushq-main'    => 'xos12TranslatePushQueue',
79	'xos12-getallconf-main'    => 'xos12SpotConfigText',
80	'xos12-getportstatus-main' => 'xos12ReadInterfaceStatus',
81	'xos12-getmaclist-main'    => 'xos12ReadMacList',
82	'xos12-getportmaclist-main' => 'xos12ReadMacList',
83	'jun10-get8021q-main'      => 'jun10Read8021QConfig',
84	'jun10-xlatepushq-main'    => 'jun10TranslatePushQueue',
85	'jun10-getallconf-main'    => 'jun10SpotConfigText',
86	'jun10-getlldpstatus-main' => 'jun10ReadLLDPStatus',
87	'jun10-getportstatus-main' => 'jun10ReadInterfaceStatus',
88	'ftos8-xlatepushq-main'    => 'ftos8TranslatePushQueue',
89	'ftos8-getlldpstatus-main' => 'ftos8ReadLLDPStatus',
90	'ftos8-getmaclist-main'    => 'ftos8ReadMacList',
91	'ftos8-getportmaclist-main'=> 'ftos8ReadMacList',
92	'ftos8-getportstatus-main' => 'ftos8ReadInterfaceStatus',
93	'ftos8-get8021q-main'      => 'ftos8Read8021QConfig',
94	'ftos8-getallconf-main'    => 'ftos8SpotConfigText',
95	'air12-xlatepushq-main'    => 'air12TranslatePushQueue',
96	'air12-getallconf-main'    => 'ios12SpotConfigText',
97	'air12-getcdpstatus-main'  => 'ios12ReadCDPStatus',
98	'eos4-getallconf-main'     => 'eos4SpotConfigText',
99	'eos4-getmaclist-main'     => 'eos4ReadMacList',
100	'eos4-getportmaclist-main' => 'eos4ReadMacList',
101	'eos4-getportstatus-main'  => 'eos4ReadInterfaceStatus',
102	'eos4-getlldpstatus-main'  => 'eos4ReadLLDPStatus',
103	'eos4-get8021q-main'       => 'eos4Read8021QConfig',
104	'eos4-xlatepushq-main'     => 'eos4TranslatePushQueue',
105	'ros11-getallconf-main'    => 'ros11SpotConfigText',
106	'ros11-xlatepushq-main'    => 'ros11TranslatePushQueue',
107	'ros11-getlldpstatus-main' => 'ros11ReadLLDPStatus',
108	'ros11-getportstatus-main' => 'ros11ReadInterfaceStatus',
109	'ros11-getmaclist-main'    => 'ros11ReadMacList',
110	'ros11-getportmaclist-main'=> 'ros11ReadMacList',
111	'ros11-get8021q-main'      => 'ros11Read8021QConfig',
112	'ros11-get8021q-scantop'   => 'ros11Read8021QScanTop',
113	'ros11-get8021q-vlandb'    => 'ros11Read8021QVLANDatabase',
114	'ros11-get8021q-readports' => 'ros11Read8021QPorts',
115	'iosxr4-xlatepushq-main'   => 'iosxr4TranslatePushQueue',
116	'iosxr4-getallconf-main'   => 'iosxr4SpotConfigText',
117	'iosxr4-getlldpstatus-main'=> 'iosxr4ReadLLDPStatus',
118	'iosxr4-getportstatus-main'=> 'iosxr4ReadInterfaceStatus',
119	'ucs-xlatepushq-main'      => 'ucsTranslatePushQueue',
120	'ucs-getinventory-main'    => 'ucsReadInventory',
121	'hpprocurveN1178-getlldpstatus-main' => 'hpprocurveN1178ReadLLDPStatus',
122	'hpprocurveN1178-get8021q-main'      => 'hpprocurveN1178Read8021QConfig',
123	'hpprocurveN1178-getportstatus-main' => 'hpprocurveN1178ReadInterfaceStatus',
124	'hpprocurveN1178-getmaclist-main'    => 'hpprocurveN1178ReadMacList',
125	'hpprocurveN1178-getportmaclist-main'=> 'hpprocurveN1178ReadMacList',
126	'hpprocurveN1178-xlatepushq-main'    => 'hpprocurveN1178TranslatePushQueue',
127	'hpprocurveN1178-getallconf-main'    => 'hpprocurveN1178SpotConfigText',
128	'ios15-getcdpstatus-main'  => 'ios12ReadCDPStatus',
129	'ios15-getlldpstatus-main' => 'ios15ReadLLDPStatus',
130	'ios15-get8021q-main'      => 'ios12ReadVLANConfig',
131	'ios15-get8021q-swports'   => 'ios12ReadSwitchPortList',
132	'ios15-get8021q-top'       => 'ios12ScanTopLevel',
133	'ios15-get8021q-readport'  => 'ios12PickSwitchportCommand',
134	'ios15-get8021q-readvlan'  => 'ios12PickVLANCommand',
135	'ios15-getportstatus-main' => 'ciscoReadInterfaceStatus',
136	'ios15-getmaclist-main'    => 'ios12ReadMacList',
137	'ios15-getportmaclist-main'=> 'ios12ReadMacList',
138	'ios15-xlatepushq-main'    => 'ios15TranslatePushQueue',
139	'ios15-getallconf-main'    => 'ios12SpotConfigText',
140);
141
142define ('MAX_GW_LOGSIZE', 1024*1024); // do not store more than 1 MB of log data
143
144$breed_by_swcode = array
145(
146	244  => 'ios12', // IOS 12.0
147	251  => 'ios12', // IOS 12.1
148	252  => 'ios12', // IOS 12.2
149	254  => 'ios12', // IOS 12.0 (router OS)
150	255  => 'ios12', // IOS 12.1 (router OS)
151	256  => 'ios12', // IOS 12.2 (router OS)
152	257  => 'ios12', // IOS 12.3 (router OS)
153	258  => 'ios12', // IOS 12.4 (router OS)
154	1901 => 'ios15', // IOS 15.0 (switch)
155	2082 => 'ios15', // IOS 15.1 (switch)
156	2142 => 'ios15', // IOS 15.2 (switch)
157	2667 => 'ios15', // IOS 15.0 (router OS)
158	1963 => 'ios15', // IOS 15.1 (router OS)
159	2668 => 'ios15', // IOS 15.2 (router OS)
160	2669 => 'ios15', // IOS 15.3 (router OS)
161	2670 => 'ios15', // IOS 15.4 (router OS)
162	2671 => 'ios15', // IOS 15.5 (router OS)
163	963  => 'nxos4', // NX-OS 4.0
164	964  => 'nxos4', // NX-OS 4.1
165	1365 => 'nxos4', // NX-OS 4.2
166	1410 => 'nxos4', // NX-OS 5.0
167	1411 => 'nxos4', // NX-OS 5.1
168	1809 => 'nxos4', // NX-OS 5.2
169	1643 => 'nxos4', // NX-OS 6.0
170	2028 => 'nxos4', // NX-OS 6.1
171	1352 => 'xos12', // Extreme XOS 12
172	1360 => 'vrp53', // Huawei VRP 5.3
173	1361 => 'vrp55', // Huawei VRP 5.5
174	1369 => 'vrp55', // Huawei VRP 5.7
175	2080 => 'vrp55', // Huawei VRP 5.11
176	2081 => 'vrp55', // Huawei VRP 5.12
177	2027 => 'vrp85', // Huawei VRP 8.5
178	1363 => 'fdry5', // IronWare 5
179	1364 => 'fdry5', // Brocade FastIron LS648
180	1367 => 'jun10', // JunOS 10, switch
181	2151 => 'jun10', // JunOS 11, switch
182	2152 => 'jun10', // JunOS 12, switch
183	2397 => 'jun10', // JunOS 13, switch
184	2398 => 'jun10', // JunOS 14, switch
185	2399 => 'jun10', // JunOS 15, switch
186	1597 => 'jun10', // JunOS 10
187	1598 => 'jun10', // JunOS 11
188	1599 => 'jun10', // JunOS 12
189	2400 => 'jun10', // JunOS 13
190	2401 => 'jun10', // JunOS 14
191	2402 => 'jun10', // JunOS 15
192	1594 => 'ftos8', // Force10 FTOS 8
193	1673 => 'air12', // AIR IOS 12.3
194	1674 => 'air12', // AIR IOS 12.4
195	1675 => 'eos4',  // Arista EOS 4
196	1759 => 'iosxr4', // Cisco IOS XR 4.2
197	1786 => 'ros11', // Marvell ROS 1.1
198	3720 => 'hpprocurveN1178', // HP Procurve 11
199	//... linux items added by the loop below
200);
201
202$shorten_by_breed = array (
203	'ios12' => 'ios12ShortenIfName_real',
204	'ios15' => 'ios15ShortenIfName_real',
205	'nxos4' => 'nxos4ShortenIfName',
206	'vrp53' => 'vrp5xShortenIfName',
207	'vrp55' => 'vrp5xShortenIfName',
208	'vrp85' => 'vrp85ShortenIfName',
209	'iosxr4' => 'iosxr4ShortenIfName',
210);
211
212$breed_by_hwcode = array (
213	1362 => 'fdry5', // Brocade FastIron CX648
214	//... dlink items added by the loop below
215);
216
217$breed_by_mgmtcode = array (
218	1788 => 'ucs',
219);
220
221// add 'linux' items into $breed_by_swcode
222$linux_sw_ranges = array (
223	225,235,
224	418,436,
225	1331,1334,
226	1395,1396,
227	1417,1422,
228	1704,1709,
229);
230for ($i = 0; $i + 1 < count ($linux_sw_ranges); $i += 2)
231	for ($j = $linux_sw_ranges[$i]; $j <= $linux_sw_ranges[$i + 1]; $j++)
232		$breed_by_swcode[$j] = 'linux';
233
234// add 'dlink' items into $breed_by_hwcode
235for ($i = 589; $i <= 637; $i++)
236	$breed_by_hwcode[$i] = 'dlink';
237
238function detectDeviceBreed ($object_id)
239{
240	global $breed_by_swcode, $breed_by_hwcode, $breed_by_mgmtcode;
241	foreach (getAttrValues ($object_id) as $record)
242		if ($record['id'] == 4 && array_key_exists ($record['key'], $breed_by_swcode))
243			return $breed_by_swcode[$record['key']];
244		elseif ($record['id'] == 2 && array_key_exists ($record['key'], $breed_by_hwcode))
245			return $breed_by_hwcode[$record['key']];
246		elseif ($record['id'] == 30 && array_key_exists ($record['key'], $breed_by_mgmtcode))
247			return $breed_by_mgmtcode[$record['key']];
248	return '';
249}
250
251function assertDeviceBreed ($object_id)
252{
253	if ('' == $breed = detectDeviceBreed ($object_id))
254		throw new RTGatewayError ('Cannot determine device breed');
255	return $breed;
256}
257
258function validBreedFunction ($breed, $command)
259{
260	global $breedfunc;
261	return array_key_exists ("${breed}-${command}-main", $breedfunc);
262}
263
264function assertBreedFunction ($breed, $command)
265{
266	global $breedfunc;
267	if (! validBreedFunction ($breed, $command))
268		throw new RTGatewayError ("unsupported command '${command}' for breed '${breed}'");
269	return $breedfunc["${breed}-${command}-main"];
270}
271
272function queryDevice ($object_id, $command, $args = array())
273{
274	$ret = NULL;
275	$request = array ('opcode' => $command);
276	if (is_array ($args) && count ($args))
277	{
278		$i = 1;
279		foreach ($args as $arg)
280		{
281			$request["arg$i"] = $arg;
282			$i++;
283		}
284	}
285	$query = translateDeviceCommands ($object_id, array ($request));
286	if ($command == 'xlatepushq')
287		return $query;
288	$breed = assertDeviceBreed ($object_id);
289	$funcname = assertBreedFunction ($breed, $command);
290	require_once 'deviceconfig.php';
291	if (! is_callable ($funcname))
292		throw new RTGatewayError ("undeclared function '${funcname}'");
293
294	global $current_query_breed;
295	$current_query_breed = $breed; // this global is used to auto-detect breed in shortenIfName
296	for ($i = 0; $i < 3; $i++)
297		try
298		{
299			$ret = $funcname (queryTerminal ($object_id, $query, FALSE));
300			break;
301		}
302		catch (ERetryNeeded $e)
303		{
304			// some devices (e.g. Cisco IOS) refuse to print running configuration
305			// while they are busy. The best way of treating this is retry a few times
306			// before failing the request
307			sleep (3);
308			continue;
309		}
310		catch (Exception $e)
311		{
312			$current_query_breed = NULL;
313			throw $e;
314		}
315	$current_query_breed = NULL;
316
317	if (NULL !== ($subst = callHook ('alterDeviceQueryResult', $ret, $object_id, $command)))
318		$ret = $subst;
319	if (! isset ($ret))
320		throw new RTGatewayError ("No result from $command");
321	return $ret;
322}
323
324function translateDeviceCommands ($object_id, $crq, $vlan_names = NULL)
325{
326	$breed = assertDeviceBreed ($object_id);
327	$funcname = assertBreedFunction ($breed, 'xlatepushq');
328	require_once 'deviceconfig.php';
329	if (! is_callable ($funcname))
330		throw new RTGatewayError ("undeclared function '${funcname}'");
331	global $current_query_breed;
332	$current_query_breed = $breed; // this global is used to auto-detect breed in shortenIfName
333	try
334	{
335		$ret = $funcname ($object_id, $crq, $vlan_names);
336	}
337	catch (Exception $e)
338	{
339		$current_query_breed = NULL;
340		throw $e;
341	}
342	$current_query_breed = NULL;
343	return $ret;
344}
345
346// takes settings struct (declared in queryTerminal) and CLI commands (plain text) as input by reference
347// returns an array of command-line parameters to $ref_settings[0]['protocol']
348// this function is called by callHook, so you can override/chain it
349// to customize command-line options to particular gateways.
350function makeGatewayParams ($object_id, /*array(&)*/$ref_settings, /*array(&)*/$ref_commands)
351{
352	$ret = array();
353	$settings = &$ref_settings[0];
354	$commands = &$ref_commands[0];
355
356	$prepend_credentials = FALSE;
357	switch ($settings['protocol'])
358	{
359		case 'telnet':
360			$prepend_credentials = TRUE;
361			$params_from_settings['port'] = 'port';
362			$params_from_settings['prompt'] = 'prompt';
363			$params_from_settings['connect-timeout'] = 'connect_timeout';
364			$params_from_settings['timeout'] = 'timeout';
365			$params_from_settings['prompt-delay'] = 'prompt_delay';
366			if (isset ($settings['proto']))
367				switch ($settings['proto'])
368				{
369					case 4:
370						$params_from_settings[] = '-4';
371						break;
372					case 6:
373						$params_from_settings[] = '-6';
374						break;
375					default:
376						throw new RTGatewayError ("Proto '${settings['proto']}' is invalid. Valid protocols are: '4', '6'");
377				}
378			$params_from_settings[] = $settings['hostname'];
379			break;
380		case 'netcat':
381			$prepend_credentials = TRUE;
382			$params_from_settings['p'] = 'port';
383			$params_from_settings['w'] = 'timeout';
384			$params_from_settings['b'] = 'ncbin';
385			$params_from_settings[] = $settings['hostname'];
386			break;
387		case 'sshnokey':
388			$prepend_credentials = TRUE;
389			$params_from_settings['proto'] = 'proto';
390			$params_from_settings['prompt'] = 'prompt';
391			$params_from_settings['prompt-delay'] = 'prompt_delay';
392			$params_from_settings['username'] = 'username';
393			$params_from_settings['password'] = 'password';
394			$params_from_settings[] = $settings['hostname'];
395			break;
396		case 'ssh':
397			$params_from_settings['sudo-user'] = 'sudo_user';
398			$params_from_settings[] = '--';
399			$params_from_settings['p'] = 'port';
400			$params_from_settings['l'] = 'username';
401			$params_from_settings['i'] = 'identity_file';
402			if (isset ($settings['proto']))
403				switch ($settings['proto'])
404				{
405					case 4:
406						$params_from_settings[] = '-4';
407						break;
408					case 6:
409						$params_from_settings[] = '-6';
410						break;
411					default:
412						throw new RTGatewayError ("Proto '${settings['proto']}' is invalid. Valid protocols are: '4', '6'");
413				}
414			if (isset ($settings['connect_timeout']))
415				$params_from_settings[] = '-oConnectTimeout=' . $settings['connect_timeout'];
416			$params_from_settings[] = '-T';
417			$params_from_settings[] = '-oStrictHostKeyChecking=no';
418			$params_from_settings[] = '-oBatchMode=yes';
419			$params_from_settings[] = '-oCheckHostIP=no';
420			$params_from_settings[] = '-oLogLevel=ERROR';
421			$params_from_settings[] = $settings['hostname'];
422			break;
423		case 'ucssdk': # remote XML through a Python backend
424			# UCS in its current implementation besides the terminal_settings() provides
425			# an additional username/password feed through the HTML form. Whenever the
426			# user provides the credentials through the form, use these instead of the
427			# credentials [supposedly] set by terminal_settings().
428			global $script_mode;
429			if ($script_mode != TRUE && ! isCheckSet ('use_terminal_settings'))
430			{
431				$settings['username'] = assertStringArg ('ucs_login');
432				$settings['password'] = assertStringArg ('ucs_password');
433			}
434			foreach (array ('hostname', 'username', 'password') as $item)
435				if (empty ($settings[$item]))
436					throw new RTGatewayError ("${item} not available, check terminal_settings()");
437			$commands = "login ${settings['hostname']} ${settings['username']} ${settings['password']}\n" . $commands;
438			break;
439		default:
440			throw new RTGatewayError ("Invalid terminal protocol '${settings['protocol']}' specified");
441	}
442
443	// prepend commands by credentials
444	if ($prepend_credentials)
445	{
446		if (isset ($settings['password']))
447			$commands = $settings['password'] . "\n" . $commands;
448		if (isset ($settings['username']))
449			$commands = $settings['username'] . "\n" . $commands;
450	}
451
452	foreach ($params_from_settings as $param_name => $setting_name)
453		if (is_int ($param_name))
454			$ret[] = $setting_name;
455		elseif (isset ($settings[$setting_name]))
456			$ret[$param_name] = $settings[$setting_name];
457
458	return $ret;
459}
460
461// This function returns a text output received from the device
462// You can override connection settings by implement a callback named 'terminal_settings'.
463// Errors are thrown as exceptions if not $tolerate_remote_errors, and shown as warnings otherwise.
464function queryTerminal ($object_id, $commands, $tolerate_remote_errors = TRUE)
465{
466	$objectInfo = spotEntity ('object', $object_id);
467	$endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
468	if (count ($endpoints) == 0)
469		throw new RTGatewayError ('no management address set');
470	if (count ($endpoints) > 1)
471		throw new RTGatewayError ('cannot pick management address');
472
473	$hide_warnings = FALSE;
474
475	// telnet prompt and mode specification
476	switch ($breed = detectDeviceBreed ($object_id))
477	{
478		case 'ios12':
479		case 'air12':
480		case 'ftos8':
481			$protocol = 'netcat'; // default is netcat mode
482			if ($breed == 'air12')
483				$protocol = 'telnet'; # Aironet IOS is broken
484			$prompt = '^(Login|[Uu]sername|Password): $|^\S+[>#]$|\[[^][]*\]\? $|\?\s+\[[^][]*\]\s*$|\[confirm yes\/no\]: $'; // set the prompt in case user would like to specify telnet protocol
485			$commands = "terminal length 0\nterminal no monitor\n" . $commands;
486			break;
487		case 'fdry5':
488			$protocol = 'netcat'; // default is netcat mode
489			$prompt = '^(Login|Username|Password|Please Enter Password): $|^\S+[>#]$'; // set the prompt in case user would like to specify telnet protocol
490			$commands = "skip-page-display\n" . $commands;
491			# Using ssh and sshnokey will always result in 'Connection to $ip closed by remote host.' upon exit.
492			# let's hide the warnings
493			$tolerate_remote_errors = TRUE;
494			$hide_warnings = TRUE;
495			break;
496		case 'vrp55':
497			$commands = "terminal echo-mode line\n" . $commands;
498			/* fall-through */
499		case 'vrp85':
500			$commands = "screen-length 0 temporary\n" . $commands;
501			/* fall-through */
502		case 'vrp53':
503			$protocol = 'telnet';
504			$prompt = '^\[[^[\]]+\]$|^<[^<>]+>$|^(Username|Password):$|\[[Yy][^\[\]]*\]\s*:?\s*$';
505			break;
506		case 'nxos4':
507			$protocol = 'telnet';
508			$prompt = '(^([Ll]ogin|[Pp]assword):|[>#]) $';
509			$commands = "terminal length 0\nterminal no monitor\n" . $commands;
510			break;
511		case 'xos12':
512			$protocol = 'telnet';
513			$prompt = ': $|\.\d+ # $|\?\s*\([Yy]\/[Nn]\)\s*$';
514			$commands = "disable clipaging\n" . $commands;
515			break;
516		case 'jun10':
517			$protocol = 'telnet';
518			$prompt = '^login: $|^Password:$|^\S+@\S+[>#] $';
519			$commands = "set cli screen-length 0\n" . $commands;
520			break;
521		case 'eos4':
522			$protocol = 'telnet'; # strict RFC854 implementation, netcat won't work
523			$prompt = '^\xf2?(login|Username|Password): $|^\S+[>#]$';
524			$commands = "enable\nno terminal monitor\nterminal length 0\n" . $commands;
525			break;
526		case 'ros11':
527			$protocol = 'netcat'; # see ftos8 case
528			$prompt = '^(User Name|\rPassword):$|^\r?\S+# $';
529			$commands = "terminal datadump\n" . $commands;
530			$commands .= "\n\n"; # temporary workaround for telnet server
531			break;
532		case 'iosxr4':
533			$protocol = 'telnet';
534			$prompt = '^\r?(Login|Username|Password): $|^\r?\S+[>#]$';
535			$commands = "terminal length 0\nterminal monitor disable\n" . $commands;
536			break;
537		case 'ucs':
538			$protocol = 'ucssdk';
539			break;
540		case 'dlink':
541			$protocol = 'netcat';
542			$commands = "disable clipaging\n" . $commands;
543			break;
544		case 'hpprocurveN1178':
545			$protocol = 'sshnokey';
546			$prompt = '(Login|[Uu]sername|[Pp]assword): $|Press any key to continue(\e\[\??\d+(;\d+)*[A-Za-z])*$|[#>].*$';
547			# Console emulator should be set to 'none' to avoid special characters to be sent by stupid default VT100!!!
548			$commands = "configure\nconsole local-terminal none\nexit\nno page\n" . $commands . "configure\nconsole local-terminal vt100\nend\nexit\nexit\ny\n";
549			break;
550		case 'ios15':
551			$commands = "terminal length 0\nterminal no monitor\n" . $commands;
552			break;
553	}
554	if (! isset ($protocol))
555		$protocol = 'netcat';
556	if (! isset ($prompt))
557		$prompt = NULL;
558
559	// set the default settings before calling user-defined callback
560	$settings = array
561	(
562		'hostname' => $endpoints[0],
563		'protocol' => $protocol,
564		'port' => NULL,
565		'prompt' => $prompt,
566		'username' => NULL,
567		'password' => NULL,
568		'timeout' => 15,
569		'connect_timeout' => 2,
570		'prompt_delay' => 0.001, # 1ms
571		'sudo_user' => NULL,
572		'identity_file' => NULL,
573		'tolerate_remote_errors' => $tolerate_remote_errors,
574	);
575
576	// override default settings
577	if (is_callable ('terminal_settings'))
578		call_user_func ('terminal_settings', $objectInfo, array (&$settings));
579	// make gateway-specific CLI params out of settings
580	$params = callHook ('makeGatewayParams', $object_id, array (&$settings), array (&$commands));
581	// call gateway
582	$ret_code = callScript ($settings['protocol'], $params, $commands, $out, $errors);
583
584	if (substr($settings['protocol'],0,3) != 'ssh' || ! $settings['tolerate_remote_errors'])
585	{
586		if ($errors != '')
587			throw new RTGatewayError ("${settings['protocol']} error: " . rtrim ($errors));
588		elseif ($ret_code !== 0)
589			throw new RTGatewayError ("${settings['protocol']} error: result code $ret_code");
590	}
591	elseif ($errors != '') // ssh and tolerate and non-empty $errors
592		foreach (explode ("\n", $errors) as $line)
593			if ($line != '' && ! $hide_warnings)
594				showWarning ("${settings['protocol']} ${settings['hostname']}: $line");
595	return strtr($out, array("\r" => "")); // cut ^M symbols
596}
597
598function callScript ($gwname, $params, $in, &$out, &$errors)
599{
600	global $racktables_gwdir, $local_gwdir, $gateway_log, $script_child_res;
601	if (isset ($gateway_log))
602		$gateway_log = '';
603
604	$cwd = NULL;
605	if ('/' === substr ($gwname, 0, 1))
606		// absolute path to executable
607		$binary = $gwname;
608	else
609	{
610		// path relative to one of RackTables' gwdirs
611		if (isset ($local_gwdir) && file_exists ("$local_gwdir/$gwname"))
612			$cwd = $local_gwdir;
613		elseif (isset ($racktables_gwdir) && file_exists ("$racktables_gwdir/$gwname"))
614			$cwd = $racktables_gwdir;
615		if (! isset ($cwd))
616			throw new RTGatewayError ("Could not find the gateway file called '$gwname'");
617		$binary = "./$gwname";
618	}
619
620	$cmd_line = $binary;
621	foreach ($params as $key => $value)
622	{
623		if (! isset ($value))
624			continue;
625		if (preg_match ('/^\d+$/', $key))
626			$cmd_line .= " " . escapeshellarg ($value);
627		else
628		{
629			if (strlen ($key) == 1)
630				$cmd_line .= " " . escapeshellarg ("-$key") . " " . escapeshellarg ($value);
631			else
632				$cmd_line .= " " . escapeshellarg("--$key=$value");
633		}
634	}
635
636	$pipes = array();
637	$child = proc_open
638	(
639		$cmd_line,
640		array (
641			0 => array ('pipe', 'r'),
642			1 => array ('pipe', 'w'),
643			2 => array ('pipe', 'w'),
644		),
645		$pipes,
646		$cwd
647	);
648	if (! is_resource ($child))
649		throw new RTGatewayError ("failed to execute $binary");
650	$script_child_res = $child;
651
652	$buff_size = 4096;
653	$write_left = array ($pipes[0]);
654	$read_left = array ($pipes[1], $pipes[2]);
655	$write_fd = $write_left;
656	$read_fd = $read_left;
657	$except_fd = array();
658	$out = '';
659	$errors = '';
660	$write_cursor = 0;
661	while ((! empty ($read_fd) || ! empty ($write_fd)) && stream_select ($read_fd, $write_fd, $except_fd, NULL))
662	{
663		foreach ($write_fd as $fd)
664		{
665			if (0 != $written = fwrite ($fd, substr ($in, $write_cursor, $buff_size), $buff_size))
666			{
667				// log all communication data into global var
668				if (isset ($gateway_log))
669					$gateway_log .= preg_replace ('/^/m', '> ', substr ($in, $write_cursor, $written));
670
671				$write_cursor += $written;
672			}
673			else
674				$write_cursor = strlen ($in);
675
676			if ($write_cursor >= strlen ($in))
677			{
678				// close input fd
679				$write_left = array_diff ($write_left, array ($fd));
680				fclose ($fd);
681			}
682		}
683		foreach ($read_fd as $fd)
684			if ('' == $str = fread ($fd, $buff_size))
685			{
686				// close output fd
687				$read_left = array_diff ($read_left, array ($fd));
688				fclose ($fd);
689			}
690			else
691			{
692				// log all communication data into global var
693				if (isset ($gateway_log))
694					$gateway_log .= $str;
695
696				if ($fd == $pipes[1])
697					$out .= $str;
698				elseif ($fd == $pipes[2])
699					$errors .= $str;
700			}
701
702		$write_fd = $write_left;
703		$read_fd = $read_left;
704
705		// limit the size of gateway_log
706		if (isset ($gateway_log) && strlen ($gateway_log) > MAX_GW_LOGSIZE * 1.1)
707			$gateway_log = substr ($gateway_log, -MAX_GW_LOGSIZE);
708
709	}
710	// The global link to the resource needs to be destroyed here.
711	// PHP's proc_close implementation does nothing itself: it only returns
712	// the value saved by the resource destructor. If the resource was not
713	// destroyed (refcnt > 0), the return value is incorrect.
714	$script_child_res = NULL;
715
716	return proc_close ($child);
717}
718
719// returns the empty structure to be returned from getRunning8021QConfig
720function constructRunning8021QConfig()
721{
722	return array
723	(
724		'vlanlist' => array(),   // unindexed list of integer VIDs
725		'portdata' => array(),   // portconf structures indexed by portname
726		'portconfig' => array(), // config text lines indexed by portname.
727		'vlannames' => array(),  // vlan names indexed by VID
728	);
729}
730
731function getRunning8021QConfig ($object_id)
732{
733	$ret = queryDevice ($object_id, 'get8021q');
734	// Once there is no default VLAN in the parsed data, it means
735	// something else was parsed instead of config text.
736	if (!in_array (VLAN_DFL_ID, $ret['vlanlist']) || empty ($ret['portdata']))
737		throw new RTGatewayError ('communication with device failed');
738	return $ret;
739}
740
741function setDevice8021QConfig ($object_id, $pseudocode, $vlan_names)
742{
743	// FIXME: this is a perfect place to log intended changes
744	// $object_id argument isn't used by default translating functions, but
745	// may come in handy for overloaded versions of these.
746	$commands = translateDeviceCommands ($object_id, $pseudocode, $vlan_names);
747	$breed = detectDeviceBreed ($object_id);
748	$output = queryTerminal ($object_id, $commands, FALSE);
749
750	// throw an exception if Juniper did not allow to enter config mode or to commit changes
751	if ($breed == 'jun10')
752	{
753		if (preg_match ('/>\s*configure exclusive\s*$[^#>]*?^error:/sm', $output))
754			throw new RTGatewayError ("Configuration is locked by other user");
755		elseif (preg_match ('/#\s*commit\s*$([^#]*?^error: .*?)$/sm', $output, $m))
756			throw new RTGatewayError ("Commit failed: ${m[1]}");
757	}
758}
759
760// if both $breed and $object_id are omitted, the breed could be auto-detected
761// in case shortenIfName is called from within queryDevice
762// (i.e. some function in deviceconfig.php)
763function shortenIfName ($if_name, $breed = NULL, $object_id = NULL)
764{
765	// This is a port name invented in snmp.php, do not translate it.
766	if (preg_match ('/^AC-in(-[12])?$/', $if_name))
767		return $if_name;
768
769	global $current_query_breed;
770	global $shorten_by_breed;
771	if (! isset ($breed))
772	{
773		if (isset ($object_id))
774			$breed = detectDeviceBreed ($object_id);
775		elseif (isset ($current_query_breed))
776			$breed = $current_query_breed;
777	}
778
779	if (isset ($breed) and isset ($shorten_by_breed[$breed]))
780		return call_user_func ($shorten_by_breed[$breed], $if_name);
781	else
782		return ios12ShortenIfName ($if_name);
783}
784
785function ios12ShortenIfName_real ($ifname)
786{
787	$ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
788	$ifname = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $ifname);
789	$ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
790	$ifname = preg_replace ('@^port-channel(.+)$@i', 'po\\1', $ifname);
791	$ifname = strtolower ($ifname);
792	$ifname = preg_replace ('/^(fa|gi|te|po)\s+(\d.*)/', '$1$2', $ifname);
793	return $ifname;
794}
795
796function nxos4ShortenIfName ($ifname)
797{
798	$ifname = preg_replace ('@^(Ethernet|Eth)(.+)$@', 'e\\2', $ifname);
799	$ifname = preg_replace ('@^port-channel(.+)$@i', 'po\\1', $ifname);
800	$ifname = preg_replace ('@^mgmt(.+)$@i', 'mgmt\\1', $ifname);
801	$ifname = strtolower ($ifname);
802	$ifname = preg_replace ('/^(e|po|mgmt)\s+(\d.*)/', '$1$2', $ifname);
803	return $ifname;
804}
805
806function vrp5xShortenIfName ($ifname)
807{
808	if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
809		return "Eth-Trunk${m[1]}";
810	$ifname = preg_replace ('@^MEth(.+)$@', 'me\\1', $ifname);
811	$ifname = preg_replace ('@^(?:Ethernet|Eth)(.+)$@', 'ether\\1', $ifname);
812	$ifname = preg_replace ('@^(?:GigabitEthernet|GE)(.+)$@', 'gi\\1', $ifname);
813	$ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
814	$ifname = strtolower ($ifname);
815	return $ifname;
816}
817
818function vrp85ShortenIfName ($ifname)
819{
820	if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
821		return "Eth-Trunk${m[1]}";
822	// VRP 8.5 has already shortened ifNames
823	$ifname = preg_replace ('@^MEth(.+)$@', 'me\\1', $ifname);
824	$ifname = strtolower ($ifname);
825	return $ifname;
826}
827
828function iosxr4ShortenIfName ($ifname)
829{
830	$ifname = preg_replace ('@^Mg(?:mtEth)?\s*(.*)$@', 'mg\\1', $ifname);
831	$ifname = preg_replace ('@^FastEthernet\s*(.+)$@', 'fa\\1', $ifname);
832	$ifname = preg_replace ('@^GigabitEthernet\s*(.+)$@', 'gi\\1', $ifname);
833	$ifname = preg_replace ('@^TenGigE\s*(.*)$@', 'te\\1', $ifname);
834	$ifname = preg_replace ('@^BE\s*(\d+)$@', 'bundle-ether\\1', $ifname);
835	$ifname = preg_replace ('@^HundredGigE\s*(.+)$@i', 'hu\\1', $ifname);
836	$ifname = strtolower ($ifname);
837	return $ifname;
838}
839
840// this function should be kept as-is for compatibility.
841// It is trying hard to complement every known breed.
842function ios12ShortenIfName ($ifname)
843{
844	if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
845		return "Eth-Trunk${m[1]}";
846	$ifname = preg_replace ('@^(?:[Ee]thernet|Eth)(.+)$@', 'e\\1', $ifname);
847	$ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
848	$ifname = preg_replace ('@^(?:GigabitEthernet|GE)\s*(.+)$@', 'gi\\1', $ifname);
849	$ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
850	$ifname = preg_replace ('@^port-channel(.+)$@i', 'po\\1', $ifname);
851	$ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
852	$ifname = preg_replace ('@^LongReachEthernet(.+)$@', 'lo\\1', $ifname);
853	$ifname = preg_replace ('@^Management(?:Ethernet)\s(.+)$@', 'ma\\1', $ifname);
854	$ifname = preg_replace ('@^Et(\d.*)$@', 'e\\1', $ifname);
855	$ifname = preg_replace ('@^TenGigE(.*)$@', 'te\\1', $ifname); // IOS XR4
856	$ifname = preg_replace ('@^Mg(?:mtEth)?(.*)$@', 'mg\\1', $ifname); // IOS XR4
857	$ifname = preg_replace ('@^BE(\d+)$@', 'bundle-ether\\1', $ifname); // IOS XR4
858	$ifname = strtolower ($ifname);
859	$ifname = preg_replace ('/^(e|fa|gi|te|po|xg|lo|ma)\s+(\d.*)/', '$1$2', $ifname);
860	return $ifname;
861}
862