blob: 0456298d14b76892e2f6fc4a6782cdc0550d5a95 [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 */
128function readnetconf(&$sock)
129{
130 $response = "";
131 do {
132 $tmp = "";
133 $tmp = fread($sock, 4096);
134 if ($tmp != "") {
135 $response .= $tmp;
136 }
137 if (strlen($tmp) < 4096) {
138 break;
139 }
140 } while ($tmp != "");
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200141 try {
142 return unwrap_rfc6242($response);
143 } catch (\Exception $e) {
144 echo $e;
145 return "";
146 }
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200147}
148
149function printJsonError() {
150 switch (json_last_error()) {
151 case JSON_ERROR_NONE:
152 echo 'No errors';
153 break;
154 case JSON_ERROR_DEPTH:
155 echo 'Maximum stack depth exceeded';
156 break;
157 case JSON_ERROR_STATE_MISMATCH:
158 echo 'Underflow or the modes mismatch';
159 break;
160 case JSON_ERROR_CTRL_CHAR:
161 echo 'Unexpected control character found';
162 break;
163 case JSON_ERROR_SYNTAX:
164 echo 'Syntax error, malformed JSON';
165 break;
166 case JSON_ERROR_UTF8:
167 echo 'Malformed UTF-8 characters, possibly incorrectly encoded';
168 break;
169 default:
170 echo 'Unknown error';
171 break;
172 }
173}
174
175/**
176 \brief Prints formatted XML
177 */
178function printxml($string)
179{
180 $xmlObj = simplexml_load_string("<rootnode>".str_replace('<?xml version="1.0" encoding="UTF-8"?>', "", $string)."</rootnode>");
181 echo("<pre>".htmlspecialchars($xmlObj->asXML())."</pre>");
182}
183
184/**
185 \param[in,out] $sock socket descriptor
186 \return 0 on success
187*/
188function handle_connect(&$sock)
189{
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200190 $capabilities = explode("\n", trim(str_replace("\r", "", $_REQUEST["capab"])));
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200191 $connect = json_encode(array("type" => MsgType::MSG_CONNECT,
192 "host" => $_REQUEST["host"],
193 "port" => 22,
194 "user" => $_REQUEST["user"],
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200195 "pass" => $_REQUEST["pass"],
196 "capabilities" => $capabilities,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200197 ));
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200198 write2socket($sock, $connect);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200199 $response = readnetconf($sock);
200 $decoded = json_decode($response, true);
201 echo "<h2>CONNECT</h2>";
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200202 if ($decoded && ($decoded["type"] == MsgType::REPLY_OK)) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200203 $sessionkey = $decoded["session"];
204 if (!isset($_SESSION["keys"])) {
205 $_SESSION["keys"] = array("$sessionkey");
206 } else {
207 $_SESSION["keys"][] = $sessionkey;
208 }
209 if (!isset($_SESSION["hosts"])) {
210 $_SESSION["hosts"] = array($_REQUEST["host"]);
211 } else {
212 $_SESSION["hosts"][] = $_REQUEST["host"];
213 }
214 echo "Successfully connected.";
215 return 0;
216 } else {
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200217 echo "Could not connect.<br>";
218 echo "Result: ". var_export($decoded, true);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200219 return 1;
220 }
221}
222
223/**
224 \return 0 on success
225 */
226function check_logged_keys()
227{
228 if (!isset($_SESSION["keys"])) {
229 echo "Not logged in.";
230 return 1;
231 }
232 if (!isset($_REQUEST["key"])) {
233 echo "No Index of key.";
234 return 1;
235 }
236 if (!isset($_SESSION["keys"][$_REQUEST["key"]])) {
237 echo "Bad Index of key.";
238 return 1;
239 }
240 return 0;
241}
242
243/**
244 \param[in,out] $sock socket descriptor
245 \param[in] $params array of values for mod_netconf (type, params...)
246 \return array - response from mod_netconf
247*/
248function execute_operation(&$sock, $params)
249{
250 $operation = json_encode($params);
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200251 write2socket($sock, $operation);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200252 $response = readnetconf($sock);
253 return json_decode($response, true);
254}
255
256/**
257 \param[in,out] $sock socket descriptor
258 \return 0 on success
259*/
260function handle_get(&$sock)
261{
262 if (check_logged_keys() != 0) {
263 return 1;
264 }
265 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
266
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200267 $params = array("type" => MsgType::MSG_GET,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200268 "session" => $sessionkey,
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200269 "source" => "running");
270 if (isset($_REQUEST["filter"]) && $_REQUEST["filter"] != "") {
271 $params["filter"] = $_REQUEST["filter"];
272 }
273 $decoded = execute_operation($sock, $params);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200274
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200275 echo "<h2>GET</h2>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200276 printxml($decoded["data"]);
277}
278
279/**
280 \param[in,out] $sock socket descriptor
281 \return 0 on success
282*/
283function handle_getconfig(&$sock)
284{
285 if (check_logged_keys() != 0) {
286 return 1;
287 }
288 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200289 $params = array("type" => MsgType::MSG_GETCONFIG,
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200290 "session" => $sessionkey,
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200291 "source" => (isset($_REQUEST["source"])?$_REQUEST["source"]:"running"));
292 if (isset($_REQUEST["filter"]) && $_REQUEST["filter"] != "") {
293 $params["filter"] = $_REQUEST["filter"];
294 }
295 $decoded = execute_operation($sock, $params);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200296
297 echo "<h2>GET-CONFIG</h2>";
298 printxml($decoded["data"]);
299 return 0;
300}
301
302/**
303 \param[in,out] $sock socket descriptor
304 \return 0 on success
305*/
Tomas Cejka1e954632012-08-23 12:57:49 +0200306function handle_editconfig(&$sock)
307{
308 if (check_logged_keys() != 0) {
309 return 1;
310 }
311 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
312 /* execute get-config */
313 $decoded = execute_operation($sock,
314 array("type" => MsgType::MSG_GETCONFIG,
315 "session" => $sessionkey,
316 "source" => "running"));
317
318 /* apply changes */
319 $oldtree = $decoded["data"];
320 var_dump($oldtree);
321 echo "<br><br><br>";
322 $newtree = simplexml_load_string("<rootnode>".str_replace('<?xml version="1.0" encoding="UTF-8"?>', "", $oldtree)."</rootnode>");
323 echo "<br><br><br>";
324 var_dump($newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled);
325 //return 0;
326 $newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled = "false";
327 var_dump($newtree->{'comet-testers'}->{'comet-tester'}->statistics->enabled);
328 $config = "";
329 foreach ($newtree as $ch) {
330 $config .= $ch->asXML();
331 }
332 /* copy-config to store new values */
333 $params = array("type" => MsgType::MSG_EDITCONFIG,
334 "session" => $sessionkey,
335 "target" => "running",
336 "config" => $config);
337 print_r($params);
338 $decoded = execute_operation($sock, $params);
339
340 echo "<h2>EDIT-CONFIG</h2>";
341 var_dump($decoded);
342 return 0;
343}
344
345function handle_copyconfig(&$sock)
346{
347 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
348 if (isset($_REQUEST["config"]) && $_REQUEST["config"] != '') {
349 $config = $_REQUEST["config"];
350 var_dump($config);
351 $params = array("type" => MsgType::MSG_COPYCONFIG,
352 "session" => $sessionkey,
353 "target" => "running",
354 "config" => $config);
355 $decoded = execute_operation($sock, $params);
356 var_dump($decoded);
357 } else {
358 echo "No config was sent";
359 }
360}
361
362/**
363 \param[in,out] $sock socket descriptor
364 \return 0 on success
365*/
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200366function handle_disconnect(&$sock)
367{
368 if (check_logged_keys() != 0) {
369 return 1;
370 }
371 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
372 $decoded = execute_operation($sock,
373 array( "type" => MsgType::MSG_DISCONNECT,
374 "session" => $sessionkey));
375 echo "<h2>Disconnect</h2>";
376 if ($decoded["type"] == MsgType::REPLY_OK) {
377 echo "Successfully disconnected.";
378 } else {
379 echo "Error occured.";
380 var_dump($decoded);
381 }
382 unset($_SESSION["keys"][$_REQUEST["key"]]);
383 unset($_SESSION["hosts"][$_REQUEST["key"]]);
384 $_SESSION["keys"] = array_values($_SESSION["keys"]);
385 $_SESSION["hosts"] = array_values($_SESSION["hosts"]);
386}
387
388/* main part of script */
389session_start();
390
Tomas Cejka1e954632012-08-23 12:57:49 +0200391/* create connection with socket */
392if (isset($_REQUEST["getconfig"]) || (isset($_REQUEST["command"]))) {
393 $errno = 0;
394 $errstr = "";
395 $sock = fsockopen('unix:///tmp/mod_netconf.sock', NULL, $errno, $errstr);
396 if ($errno != 0) {
397 echo "Could not connect to socket.";
398 echo "$errstr";
399 return 1;
400 }
401 stream_set_timeout($sock, 1, 100);
402}
403
404/* pseudo-AJAX response of get-config running */
405if (isset($_REQUEST["getconfig"])) {
406 if (check_logged_keys() != 0) {
407 return 1;
408 }
409 $sessionkey = $_SESSION["keys"][$_REQUEST["key"]];
410 $params = array("type" => MsgType::MSG_GETCONFIG,
411 "session" => $sessionkey,
412 "source" => "running");
413 $decoded = execute_operation($sock, $params);
414 echo $decoded["data"];
415 /* end script return only data */
416 exit(0);
417}
418
419
420/* print mainpage */
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200421echo "<html><head><title>phpMyNetconf</title><body>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200422if (!isset($_REQUEST["command"])) {
423 echo "<h2>Connect to new NETCONF server</h2>
424 <form action='?' method='POST'>
425 <input type='hidden' name='command' value='connect'>
426 <label for='host'>Hostname:</label><input type='text' name='host'><br>
427 <label for='user'>Username:</label><input type='text' name='user'><br>
428 <label for='pass'>Password:</label><input type='password' name='pass'><br>
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200429 <label for='capab'>Capabilities:</label><br><textarea name='capab' rows=10 cols=100>
430urn:ietf:params:netconf:base:1.0
431urn:ietf:params:netconf:base:1.1
432urn:ietf:params:netconf:capability:startup:1.0
433urn:ietf:params:netconf:capability:writable-running:1.0
434urn:ietf:params:netconf:capability:candidate:1.0
435urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&amp;also-supported=report-all,report-all-tagged,trim,explicit
436urn:cesnet:tmc:comet:1.0
437urn:cesnet:tmc:combo:1.0
438urn:cesnet:tmc:hanicprobe:1.0
439</textarea><br>
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200440 <input type='submit' value='Login'>
441 </form>";
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200442 if (isset($_SESSION["keys"]) && sizeof($_SESSION["keys"])) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200443 echo "<h2>Already connected nodes</h2>";
444 $keys = $_SESSION["keys"];
445 $i = 0;
446 foreach ($keys as $k) {
Tomas Cejkacb2765e2012-08-03 20:40:48 +0200447 echo "$i ".$_SESSION["hosts"][$i]."
448<form action='?' method='GET'>
449<input type='hidden' name='command' value='get'>
450<input type='hidden' name='key' value='$i'>
451<label for='get-filter'>Filter:</label><input type='text' name='filter'>
452<input type='submit' value='Execute Get'></form>
453<form action='?' method='GET'>
454<input type='hidden' name='command' value='getconfig'>
455<input type='hidden' name='key' value='$i'>
456<label for='get-filter'>Filter:</label><input type='text' name='filter'>
457<select name='source'><option value='running'>Running</option>
458<option value='startup'>Start-up</option>
459<option value='candidate'>Candidate</option></select>
460<input type='submit' value='Execute Get-config'></form>
Tomas Cejka1e954632012-08-23 12:57:49 +0200461<!--<form action='?' method='GET'>
462<input type='hidden' name='command' value='editconfig'>
463<input type='hidden' name='key' value='$i'>
464<label for='edit-element'>Element name:</label><input type='text' name='element'>
465<label for='edit-value'>Value:</label><input type='text' name='newval'>
466<input type='submit' value='Execute Edit-config'>
467</form>-->
468<form action='?' method='POST'>
469<input type='hidden' name='command' value='copyconfig'>
470<input type='hidden' name='key' value='$i'>
471<textarea name='config' id='configdata$i' cols=40 rows=10></textarea>
472<input type='submit' value='Rewrite running config'> (copy-config)
473</form>
474<a href='?command=disconnect&amp;key=$i'><button>disconnect</button></a><br>
475<script type='text/javascript'>
476xmlHttp = new XMLHttpRequest();
477xmlHttp.open( 'GET', '?key=$i&getconfig', false );
478xmlHttp.send( null );
479document.getElementById('configdata$i').value=xmlHttp.responseText;
480</script>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200481 $i++;
482 }
483 }
484 exit(0);
485}
486
Tomas Cejka1e954632012-08-23 12:57:49 +0200487/* handle commands */
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200488if (isset($_REQUEST["command"])) {
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200489 echo "<a href='?'>Back</a>";
490
491 if ($_REQUEST["command"] === "connect") {
492 handle_connect($sock);
Tomas Cejka1e954632012-08-23 12:57:49 +0200493 } else if ($_REQUEST["command"] === "copyconfig") {
494 handle_copyconfig($sock);
495 } else if ($_REQUEST["command"] === "editconfig") {
496 handle_editconfig($sock);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200497 } else if ($_REQUEST["command"] === "get") {
498 handle_get($sock);
499 } else if ($_REQUEST["command"] === "getconfig") {
500 handle_getconfig($sock);
501 } else if ($_REQUEST["command"] === "disconnect") {
502 handle_disconnect($sock);
503 } else {
504 printf("Not implemented yet. (%s)", $_REQUEST["command"]);
505 }
506 fclose($sock);
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200507}
Tomas Cejka4bcfaf12012-09-29 20:52:24 +0200508echo "</body></html>";
Tomas Cejkaac49c6e2012-07-30 17:10:25 +0200509