blob: 2609170c6c5ab55337f1ce2efbe631239ea6874c [file] [log] [blame]
Tomas Cejkaac49c6e2012-07-30 17:10:25 +02001<?php
2/*!
3 * \file phpmynetconf.php
4 * \brief NETCONF PHP gateway for Apache module of Netopeer
5 * \author Tomas Cejka <cejkat@cesnet.cz>
6 * \date 2012
7 */
8/*
9 * Copyright (C) 2011-2012 CESNET
10 *
11 * LICENSE TERMS
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the
21 * distribution.
22 * 3. Neither the name of the Company nor the names of its contributors
23 * may be used to endorse or promote products derived from this
24 * software without specific prior written permission.
25 *
26 * ALTERNATIVELY, provided that this notice is retained in full, this
27 * product may be distributed under the terms of the GNU General Public
28 * License (GPL) version 2 or later, in which case the provisions
29 * of the GPL apply INSTEAD OF those given above.
30 *
31 * This software is provided ``as is'', and any express or implied
32 * warranties, including, but not limited to, the implied warranties of
33 * merchantability and fitness for a particular purpose are disclaimed.
34 * In no event shall the company or contributors be liable for any
35 * direct, indirect, incidental, special, exemplary, or consequential
36 * damages (including, but not limited to, procurement of substitute
37 * goods or services; loss of use, data, or profits; or business
38 * interruption) however caused and on any theory of liability, whether
39 * in contract, strict liability, or tort (including negligence or
40 * otherwise) arising in any way out of the use of this software, even
41 * if advised of the possibility of such damage.
42 *
43 */
44
45/* Enumeration of Message type (taken from mod_netconf.c) */
46class MsgType {
47 const REPLY_OK = 0;
48 const REPLY_DATA = 1;
49 const REPLY_ERROR = 2;
50 const REPLY_INFO = 3;
51 const MSG_CONNECT = 4;
52 const MSG_DISCONNECT = 5;
53 const MSG_GET = 6;
54 const MSG_GETCONFIG = 7;
55 const MSG_EDITCONFIG = 8;
56 const MSG_COPYCONFIG = 9;
57 const MSG_DELETECONFIG = 10;
58 const MSG_LOCK = 11;
59 const MSG_UNLOCK = 12;
60 const MSG_KILL = 13;
61 const MSG_INFO = 14;
62 const MSG_GENERIC = 15;
63};
64
Tomas Cejka4bcfaf12012-09-29 20:52:24 +020065function unwrap_rfc6242($message)
66{
67 $response = "";
68 if ($message == "") {
69 return $response;
70 }
71 $chunks = explode("\n#", $message);
72 $numchunks = sizeof($chunks);
73 $i = 0;
74 if ($numchunks > 0) {
75 do {
76 if ($i == 0 && $chunks[$i++] != "") {
77 /* something is wrong, message should start by '\n#'
78 */
79 echo "Wrong message format, it is not according to RFC6242 (starting with \\n#).";
80 echo var_export($message, true);
81 throw new \ErrorException("Wrong message format, it is not according to RFC6242 (starting with \\n#).");
82 }
83 if ($i >= $numchunks) {
84 echo "Malformed message (RFC6242) - Bad amount of parts.";
85 echo var_export($message, true);
86 /* echo "chunk length<br>\n"; */
87 throw new \ErrorException("Malformed message (RFC6242) - Bad amount of parts.");
88 }
89 $len = 0;
90 sscanf($chunks[$i], "%i", $len);
91
92 /* echo "chunk data<br>\n"; */
93 $nl = strpos($chunks[$i], "\n");
94 if ($nl === false) {
95 echo "Malformed message (RFC6242) - There is no \\n after chunk-data size.";
96 echo var_export($message, true);
97 throw new \ErrorException("Malformed message (RFC6242) - There is no \\n after chunk-data size.");
98 }
99 $data = substr($chunks[$i], $nl + 1);
100 $realsize = strlen($data);
101 if ($realsize != $len) {
102 echo "Chunk $i has the length $realsize instead of $len.";
103 echo var_export($message, true);
104 throw new \ErrorException("Chunk $i has the length $realsize instead of $len.");
105 }
106 $response .= $data;
107 $i++;
108 if ($chunks[$i][0] == '#') {
109 /* ending part */
110 break;
111 }
112 } while ($i<$numchunks);
113 }
114
115 return $response;
116}
117
118function write2socket(&$sock, $message)
119{
120 $final_message = sprintf("\n#%d\n%s\n##\n", strlen($message), $message);
121 fwrite($sock, $final_message);
122}
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200123/**
124 \brief Read response from socket
125 \param[in,out] $sock socket descriptor
126 \return trimmed string that was read
127 */
Tomas Cejka4939fc32012-12-10 00:17:56 +0100128function readnetconf2(&$sock)
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200129{
Tomas Cejka4939fc32012-12-10 00:17:56 +0100130 $start = microtime(true);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200131 $response = "";
132 do {
133 $tmp = "";
134 $tmp = fread($sock, 4096);
135 if ($tmp != "") {
136 $response .= $tmp;
137 }
138 if (strlen($tmp) < 4096) {
139 break;
140 }
141 } while ($tmp != "");
Tomas Cejka4939fc32012-12-10 00:17:56 +0100142 $res = "";
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200143 try {
Tomas Cejka4939fc32012-12-10 00:17:56 +0100144 $res = unwrap_rfc6242($response);
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200145 } catch (\Exception $e) {
146 echo $e;
147 return "";
148 }
Tomas Cejka4939fc32012-12-10 00:17:56 +0100149 echo "readnetconf elapsed time: ".(microtime(true) - $start);
150 return $res;
151}
152function readnetconf(&$sock) {
153 stream_set_blocking($sock, 1);
154 //stream_set_timeout($sock, 1, 100);
155//$start = microtime(true);
156 $response = "";
157 $tmp = "";
158 $tmp = fread($sock, 1024);
159 if ($tmp === false) {
160 $this->container->get('request')->getSession()->setFlash($this->flashState .' error', "Reading failure.");
161 }
162
163 $response = $tmp;
164 // message is wrapped in "\n#strlen($m)\n$m\n##\n"
165 // get size:
166 $lines = explode("\n", $tmp);
167 if (sizeof($lines >= 2)) {
168 $size = strlen($lines[0]) + 1 + strlen($lines[1]) + 1;
169 $size += intval(substr($lines[1], 1)) + 5;
170 }
171
172 while (strlen($response) < $size) {
173 $tmp = "";
174 $tmp = fread($sock, $size - strlen($response));
175 if ($tmp === false) {
176 #$this->container->get('request')->getSession()->setFlash($this->flashState .' error', "Reading failure.");
177 echo "reading failure";
178 die();
179 }
180 $response .= $tmp;
181 echo strlen($response) ."/". $size ."\n";
182 }
183 $status = stream_get_meta_data($sock);
184 if (!$response && $status["timed_out"] == true) {
185 #$this->container->get('request')->getSession()->setFlash($this->flashState .' error', "Reached timeout for reading response.");
186 echo "Reached timeout for reading response.";
187 }
188 /* "unchunk" frames (RFC6242) */
189 try {
190 $response = unwrap_rfc6242($response);
191 } catch (\ErrorException $e) {
192 #$this->container->get('request')->getSession()->setFlash($this->flashState .' error', "Could not read NetConf. Error: ".$e->getMessage());
193 echo "unwrap exception";
194 return 1;
195 }
196//echo "readnetconf time consumed: ". (microtime(true) - $start);
197
198 return trim($response);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200199}
200
201function printJsonError() {
202 switch (json_last_error()) {
203 case JSON_ERROR_NONE:
204 echo 'No errors';
205 break;
206 case JSON_ERROR_DEPTH:
207 echo 'Maximum stack depth exceeded';
208 break;
209 case JSON_ERROR_STATE_MISMATCH:
210 echo 'Underflow or the modes mismatch';
211 break;
212 case JSON_ERROR_CTRL_CHAR:
213 echo 'Unexpected control character found';
214 break;
215 case JSON_ERROR_SYNTAX:
216 echo 'Syntax error, malformed JSON';
217 break;
218 case JSON_ERROR_UTF8:
219 echo 'Malformed UTF-8 characters, possibly incorrectly encoded';
220 break;
221 default:
222 echo 'Unknown error';
223 break;
224 }
225}
226
227/**
228 \brief Prints formatted XML
229 */
230function printxml($string)
231{
232 $xmlObj = simplexml_load_string("<rootnode>".str_replace('<?xml version="1.0" encoding="UTF-8"?>', "", $string)."</rootnode>");
233 echo("<pre>".htmlspecialchars($xmlObj->asXML())."</pre>");
234}
235
236/**
237 \param[in,out] $sock socket descriptor
238 \return 0 on success
239*/
240function handle_connect(&$sock)
241{
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200242 $capabilities = explode("\n", trim(str_replace("\r", "", $_REQUEST["capab"])));
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200243 $connect = json_encode(array("type" => MsgType::MSG_CONNECT,
244 "host" => $_REQUEST["host"],
245 "port" => 22,
246 "user" => $_REQUEST["user"],
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200247 "pass" => $_REQUEST["pass"],
248 "capabilities" => $capabilities,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200249 ));
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200250 write2socket($sock, $connect);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200251 $response = readnetconf($sock);
252 $decoded = json_decode($response, true);
253 echo "<h2>CONNECT</h2>";
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200254 if ($decoded && ($decoded["type"] == MsgType::REPLY_OK)) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200255 $sessionkey = $decoded["session"];
256 if (!isset($_SESSION["keys"])) {
257 $_SESSION["keys"] = array("$sessionkey");
258 } else {
259 $_SESSION["keys"][] = $sessionkey;
260 }
261 if (!isset($_SESSION["hosts"])) {
262 $_SESSION["hosts"] = array($_REQUEST["host"]);
263 } else {
264 $_SESSION["hosts"][] = $_REQUEST["host"];
265 }
266 echo "Successfully connected.";
267 return 0;
268 } else {
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200269 echo "Could not connect.<br>";
270 echo "Result: ". var_export($decoded, true);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200271 return 1;
272 }
273}
274
275/**
276 \return 0 on success
277 */
278function check_logged_keys()
279{
280 if (!isset($_SESSION["keys"])) {
281 echo "Not logged in.";
282 return 1;
283 }
284 if (!isset($_REQUEST["key"])) {
285 echo "No Index of key.";
286 return 1;
287 }
288 if (!isset($_SESSION["keys"][$_REQUEST["key"]])) {
289 echo "Bad Index of key.";
290 return 1;
291 }
292 return 0;
293}
294
295/**
296 \param[in,out] $sock socket descriptor
297 \param[in] $params array of values for mod_netconf (type, params...)
298 \return array - response from mod_netconf
299*/
300function execute_operation(&$sock, $params)
301{
302 $operation = json_encode($params);
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200303 write2socket($sock, $operation);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200304 $response = readnetconf($sock);
305 return json_decode($response, true);
306}
307
308/**
309 \param[in,out] $sock socket descriptor
310 \return 0 on success
311*/
312function handle_get(&$sock)
313{
314 if (check_logged_keys() != 0) {
315 return 1;
316 }
317 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
318
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200319 $params = array("type" => MsgType::MSG_GET,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200320 "session" => $sessionkey,
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200321 "source" => "running");
322 if (isset($_REQUEST["filter"]) && $_REQUEST["filter"] != "") {
323 $params["filter"] = $_REQUEST["filter"];
324 }
325 $decoded = execute_operation($sock, $params);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200326
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200327 echo "<h2>GET</h2>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200328 printxml($decoded["data"]);
329}
330
331/**
332 \param[in,out] $sock socket descriptor
333 \return 0 on success
334*/
335function handle_getconfig(&$sock)
336{
337 if (check_logged_keys() != 0) {
338 return 1;
339 }
340 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200341 $params = array("type" => MsgType::MSG_GETCONFIG,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200342 "session" => $sessionkey,
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200343 "source" => (isset($_REQUEST["source"])?$_REQUEST["source"]:"running"));
344 if (isset($_REQUEST["filter"]) && $_REQUEST["filter"] != "") {
345 $params["filter"] = $_REQUEST["filter"];
346 }
347 $decoded = execute_operation($sock, $params);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200348
349 echo "<h2>GET-CONFIG</h2>";
350 printxml($decoded["data"]);
351 return 0;
352}
353
354/**
355 \param[in,out] $sock socket descriptor
356 \return 0 on success
357*/
Tomas Cejka1e954632012-08-23 12:57:49 +0200358function handle_editconfig(&$sock)
359{
360 if (check_logged_keys() != 0) {
361 return 1;
362 }
363 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
364 /* execute get-config */
365 $decoded = execute_operation($sock,
366 array("type" => MsgType::MSG_GETCONFIG,
367 "session" => $sessionkey,
368 "source" => "running"));
369
370 /* apply changes */
371 $oldtree = $decoded["data"];
372 var_dump($oldtree);
373 echo "<br><br><br>";
374 $newtree = simplexml_load_string("<rootnode>".str_replace('<?xml version="1.0" encoding="UTF-8"?>', "", $oldtree)."</rootnode>");
375 echo "<br><br><br>";
376 var_dump($newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled);
377 //return 0;
378 $newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled = "false";
379 var_dump($newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled);
380 $config = "";
381 foreach ($newtree as $ch) {
382 $config .= $ch->asXML();
383 }
384 /* copy-config to store new values */
385 $params = array("type" => MsgType::MSG_EDITCONFIG,
386 "session" => $sessionkey,
387 "target" => "running",
388 "config" => $config);
389 print_r($params);
390 $decoded = execute_operation($sock, $params);
391
392 echo "<h2>EDIT-CONFIG</h2>";
393 var_dump($decoded);
394 return 0;
395}
396
397function handle_copyconfig(&$sock)
398{
399 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
400 if (isset($_REQUEST["config"]) && $_REQUEST["config"] != '') {
401 $config = $_REQUEST["config"];
402 var_dump($config);
403 $params = array("type" => MsgType::MSG_COPYCONFIG,
404 "session" => $sessionkey,
405 "target" => "running",
406 "config" => $config);
407 $decoded = execute_operation($sock, $params);
408 var_dump($decoded);
409 } else {
410 echo "No config was sent";
411 }
412}
413
414/**
415 \param[in,out] $sock socket descriptor
416 \return 0 on success
417*/
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200418function handle_disconnect(&$sock)
419{
420 if (check_logged_keys() != 0) {
421 return 1;
422 }
423 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
424 $decoded = execute_operation($sock,
425 array( "type" => MsgType::MSG_DISCONNECT,
426 "session" => $sessionkey));
427 echo "<h2>Disconnect</h2>";
428 if ($decoded["type"] == MsgType::REPLY_OK) {
429 echo "Successfully disconnected.";
430 } else {
431 echo "Error occured.";
432 var_dump($decoded);
433 }
434 unset($_SESSION["keys"][$_REQUEST["key"]]);
435 unset($_SESSION["hosts"][$_REQUEST["key"]]);
436 $_SESSION["keys"] = array_values($_SESSION["keys"]);
437 $_SESSION["hosts"] = array_values($_SESSION["hosts"]);
438}
439
440/* main part of script */
441session_start();
442
Tomas Cejka1e954632012-08-23 12:57:49 +0200443/* create connection with socket */
444if (isset($_REQUEST["getconfig"]) || (isset($_REQUEST["command"]))) {
445 $errno = 0;
446 $errstr = "";
447 $sock = fsockopen('unix:///tmp/mod_netconf.sock', NULL, $errno, $errstr);
448 if ($errno != 0) {
449 echo "Could not connect to socket.";
450 echo "$errstr";
451 return 1;
452 }
453 stream_set_timeout($sock, 1, 100);
454}
455
456/* pseudo-AJAX response of get-config running */
457if (isset($_REQUEST["getconfig"])) {
458 if (check_logged_keys() != 0) {
459 return 1;
460 }
461 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
462 $params = array("type" => MsgType::MSG_GETCONFIG,
463 "session" => $sessionkey,
464 "source" => "running");
465 $decoded = execute_operation($sock, $params);
466 echo $decoded["data"];
467 /* end script return only data */
468 exit(0);
469}
470
471
472/* print mainpage */
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200473echo "<html><head><title>phpMyNetconf</title><body>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200474if (!isset($_REQUEST["command"])) {
475 echo "<h2>Connect to new NETCONF server</h2>
476 <form action='?' method='POST'>
477 <input type='hidden' name='command' value='connect'>
478 <label for='host'>Hostname:</label><input type='text' name='host'><br>
479 <label for='user'>Username:</label><input type='text' name='user'><br>
480 <label for='pass'>Password:</label><input type='password' name='pass'><br>
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200481 <label for='capab'>Capabilities:</label><br><textarea name='capab' rows=10 cols=100>
482urn:ietf:params:netconf:base:1.0
483urn:ietf:params:netconf:base:1.1
484urn:ietf:params:netconf:capability:startup:1.0
485urn:ietf:params:netconf:capability:writable-running:1.0
486urn:ietf:params:netconf:capability:candidate:1.0
487urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&amp;also-supported=report-all,report-all-tagged,trim,explicit
488urn:cesnet:tmc:comet:1.0
489urn:cesnet:tmc:combo:1.0
490urn:cesnet:tmc:hanicprobe:1.0
491</textarea><br>
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200492 <input type='submit' value='Login'>
493 </form>";
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200494 if (isset($_SESSION["keys"]) && sizeof($_SESSION["keys"])) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200495 echo "<h2>Already connected nodes</h2>";
496 $keys = $_SESSION["keys"];
497 $i = 0;
498 foreach ($keys as $k) {
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200499 echo "$i ".$_SESSION["hosts"][$i]."
500<form action='?' method='GET'>
501<input type='hidden' name='command' value='get'>
502<input type='hidden' name='key' value='$i'>
503<label for='get-filter'>Filter:</label><input type='text' name='filter'>
504<input type='submit' value='Execute Get'></form>
505<form action='?' method='GET'>
506<input type='hidden' name='command' value='getconfig'>
507<input type='hidden' name='key' value='$i'>
508<label for='get-filter'>Filter:</label><input type='text' name='filter'>
509<select name='source'><option value='running'>Running</option>
510<option value='startup'>Start-up</option>
511<option value='candidate'>Candidate</option></select>
512<input type='submit' value='Execute Get-config'></form>
Tomas Cejka1e954632012-08-23 12:57:49 +0200513<!--<form action='?' method='GET'>
514<input type='hidden' name='command' value='editconfig'>
515<input type='hidden' name='key' value='$i'>
516<label for='edit-element'>Element name:</label><input type='text' name='element'>
517<label for='edit-value'>Value:</label><input type='text' name='newval'>
518<input type='submit' value='Execute Edit-config'>
519</form>-->
520<form action='?' method='POST'>
521<input type='hidden' name='command' value='copyconfig'>
522<input type='hidden' name='key' value='$i'>
523<textarea name='config' id='configdata$i' cols=40 rows=10></textarea>
524<input type='submit' value='Rewrite running config'> (copy-config)
525</form>
526<a href='?command=disconnect&amp;key=$i'><button>disconnect</button></a><br>
527<script type='text/javascript'>
528xmlHttp = new XMLHttpRequest();
529xmlHttp.open( 'GET', '?key=$i&getconfig', false );
530xmlHttp.send( null );
531document.getElementById('configdata$i').value=xmlHttp.responseText;
532</script>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200533 $i++;
534 }
535 }
536 exit(0);
537}
538
Tomas Cejka1e954632012-08-23 12:57:49 +0200539/* handle commands */
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200540if (isset($_REQUEST["command"])) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200541 echo "<a href='?'>Back</a>";
542
543 if ($_REQUEST["command"] === "connect") {
544 handle_connect($sock);
Tomas Cejka1e954632012-08-23 12:57:49 +0200545 } else if ($_REQUEST["command"] === "copyconfig") {
546 handle_copyconfig($sock);
547 } else if ($_REQUEST["command"] === "editconfig") {
548 handle_editconfig($sock);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200549 } else if ($_REQUEST["command"] === "get") {
550 handle_get($sock);
551 } else if ($_REQUEST["command"] === "getconfig") {
552 handle_getconfig($sock);
553 } else if ($_REQUEST["command"] === "disconnect") {
554 handle_disconnect($sock);
555 } else {
556 printf("Not implemented yet. (%s)", $_REQUEST["command"]);
557 }
558 fclose($sock);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200559}
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200560echo "</body></html>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200561