XML-RPC: Implement XML-RPC handling for remote administration
authorMichal Novotny <minovotn@redhat.com>
Wed, 3 Oct 2012 21:44:31 +0000 (23:44 +0200)
committerMichal Novotny <minovotn@redhat.com>
Wed, 3 Oct 2012 21:58:27 +0000 (23:58 +0200)
This will be later used for the Android application to access
the php-virt-control instances directly from your phone.

The architecture is following:
1. The scope/namespace is defined in the XmlRPC.php and is
   recognized to a real class
2. The method called has the "rpc_" prefix to identify it's
   for the XML-RPC. The method syntax is having ID of the user,
   libvirt instance object and also the input data array, i.e.

     function rpc_foo($idUser, $lv, $data) {...}

   The data array is having all the data from XMLRPC data params
   element set.

   The necessary information consist of API key and connection
   information for the libvirt instance (libvirt connection URI
   and possible username and password, e.g. for SASL auth).
3. The output of the rpc_*() function is being either array or
   a simple string which is formatted to the valid XMLRPC reply
   data elements.

For XMLRPC testing please see the xmlrpc-test.py python script.

Signed-off-by: Michal Novotny <minovotn@redhat.com>

15 files changed:
classes/XmlRPC.php [new file with mode: 0644]
classes/database-mysql.php
classes/database.php
classes/language.php
classes/libvirt-domain.php
classes/libvirt-info.php
classes/libvirt-network.php
classes/loggerBase.php
dialog-login.php
init.php
lang/cs.php
lang/en.php
pages/users.php
xmlrpc-test.py [new file with mode: 0755]
xmlrpc.php [new file with mode: 0644]

diff --git a/classes/XmlRPC.php b/classes/XmlRPC.php
new file mode 100644 (file)
index 0000000..98e7c24
--- /dev/null
@@ -0,0 +1,495 @@
+<?php
+       class XmlRPC extends LoggerBase {
+               private $log_override = false;
+               private $is_xmlrpc = false;
+               private $data = false;
+               private $db = false;
+
+               function XmlRPC($db, $input = false) {
+                       $this->db = $db;
+
+                       if (!$input) {
+                               $input = file_get_contents('php://input');
+                               $ret = $this->process_xml_rpc_from_xml($input);
+                       }
+                       else {
+                               if ($isFile)
+                                       $ret = $this->process_xml_rpc_from_file($input);
+                               else
+                                       $ret = $this->process_xml_rpc_from_xml($input);
+                       }
+
+                       $this->data = $this->format_rpc_reply($ret, true);
+               }
+
+               function getData() {
+                       return $this->data;
+               }
+
+               function parse_xmlrpc_struct($x, $first = true) {
+                       $r = (array)$x['member'];
+
+                       $ret = array();
+                       if ($first)
+                               $ret['data'] = array();
+                       if (array_key_exists('name', $r)) {
+                               $key = $r['name'];
+
+                               $r = (array)$r['value'];
+                               if (array_key_exists('string', $r))
+                                       $value = (string)$r['string'];
+                               elseif (array_key_exists('int', $r))
+                                       $value = (int)$r['int'];
+                               elseif (array_key_exists('i4', $r))
+                                       $value = (int)$r['int'];
+                               elseif (array_key_exists('boolean', $r))
+                                       $value = (($r['boolean'] == '1') || ($r['boolean'] == 'true')) ? 1 : 0;
+                               elseif (array_key_exists('double', $r))
+                                       $value = (double)$r['double'];
+                               elseif (array_key_exists('base64', $r))
+                                       $value = (string)$r['base64'];
+                               elseif (array_key_exists('dateTime.iso8601', $r))
+                                       $value = $this->format_datetime((string)$r['dateTime.iso8601']);
+                               elseif (array_key_exists('struct', $r))
+                                       $value = $this->parse_xmlrpc_struct((array)$r['struct'], false);
+
+                               if ($first) {
+                                       if ($key == 'apikey')
+                                               $ret['apikey'] = $value;
+                                       else
+                                               $ret['data'][$key] = $value;
+                               }
+                               else
+                                       $ret[$key] = $value;
+                       }
+                       else {
+                               for ($i = 0; $i < sizeof($r); $i++) {
+                                       $tmp = (array)$r[$i];
+                                       $key = $tmp['name'];
+
+                                       $tmp = (array)$tmp['value'];
+
+                                       if (array_key_exists('string', $tmp))
+                                               $value = (string)$tmp['string'];
+                                       elseif (array_key_exists('int', $tmp))
+                                               $value = (int)$tmp['int'];
+                                       elseif (array_key_exists('i4', $tmp))
+                                               $value = (int)$tmp['int'];
+                                       elseif (array_key_exists('boolean', $tmp))
+                                               $value = (($tmp['boolean'] == '1') || ($tmp['boolean'] == 'true')) ? 1 : 0;
+                                       elseif (array_key_exists('double', $tmp))
+                                               $value = (double)$tmp['double'];
+                                       elseif (array_key_exists('dateTime.iso8601', $tmp))
+                                               $value = $this->format_datetime((string)$tmp['dateTime.iso8601']);
+                                       elseif (array_key_exists('base64', $tmp))
+                                               $value = (string)$tmp['base64'];
+                                       elseif (array_key_exists('struct', $tmp))
+                                               $value = $this->parse_xmlrpc_struct((array)$tmp['struct'], false);
+
+                                       if ($first) {
+                                               if ($key == 'apikey')
+                                                       $ret['apikey'] = $value;
+                                               else
+                                                       $ret['data'][$key] = $value;
+                                       }
+                                       else
+                                               $ret[$key] = $value;
+                               }
+                       }
+
+                       return $ret;
+               }
+
+               function process_xml_rpc_from_file($filename) {
+                       if (!function_exists('simplexml_load_file'))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'XML Error', 'Function simplexml_load_file() not present');
+
+                       $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'XML Processing', 'Processing file '.$filename);
+
+                       $xml = simplexml_load_file($filename);
+                       return $this->process_xml_rpc($xml);
+               }
+
+               function process_xml_rpc_from_xml($xml) {
+                       if (!function_exists('simplexml_load_string'))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'XML Error', 'Function simplexml_load_string() not present');
+
+                       $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'XML Processing', 'Processing XML input');
+
+                       $tmp = simplexml_load_string($xml);
+                       return $this->process_xml_rpc($tmp);
+               }
+
+               function map_scope_to_class($scope) {
+                       switch ($scope) {
+                               case 'Domain':  return 'LibvirtDomain';
+                                               break;
+                               case 'Network': return 'LibvirtNetwork';
+                                               break;
+                               case 'Information':     return 'LibvirtInfo';
+                                                       break;
+                       }
+
+                       return false;
+               }
+
+               function get_by_key($apikey) {
+                       if (!$apikey)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Input data are missing');
+
+                       if (!$this->db)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Database disconnected', 'Database class is not loaded');
+
+                       return $this->db->get_by_apikey($apikey);
+               }
+
+               function process_xml_rpc($xml) {
+                       if (!$xml)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'XML Processing', 'SimpleXMLObject not present');
+
+                       if ($xml->getName() != 'request') {
+                               if ($xml->getName() != 'methodCall')
+                                       return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'XML Error', 'Invalid root tag');
+
+                               $r = array( $xml );
+                               $x = (array)$r[0];
+                               $method = (string)$x['methodName'];
+                               $r = (array)$x['params'];
+                               if (array_key_exists('param', $r)) {
+                                       $x = (array)$r['param'];
+                                       $r = (array)$x['value'];
+                                       $x = (array)$r['struct'];
+
+                                       $ret = $this->parse_xmlrpc_struct($x);
+                                       $ret['method'] = $method;
+                               }
+                               else
+                                       $ret = array('method' => $method);
+
+                               $this->is_xmlrpc = true;
+                       }
+                       else {
+                               $ret = array();
+                               foreach($xml->children() as $child) {
+                                       $name = (string)$child->getName();
+                                       $arr = (array)$child;
+                                       if (!empty($arr)) {
+                                               $ix = 0;
+                                               foreach($child as $child2) {
+                                                       $child2 = (array)$child2;
+
+                                                       $ret[$name] = $child2;
+                                                       if (sizeof($child2) > 0)
+                                                               $ix++;
+                                               }
+
+                                               if ($ix == 0)
+                                                       $ret[$name] = $arr;
+                                       }
+                                       else {
+                                               $value = (string)$child;
+
+                                               $ret[$name] = $value;
+                                       }
+                               }
+
+                               $this->is_xmlrpc = false;
+                       }
+
+                       $tmp = explode('.', $ret['method']);
+                       if (sizeof($tmp) != 2)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Invalid method', 'Invalid method has been requested');
+
+                       if (!array_key_exists('apikey', $ret))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'API key missing', 'API key is missing in the request');
+
+                       $scope = $tmp[0];
+                       $method = $tmp[1];
+                       unset($tmp);
+
+                       $class = $this->map_scope_to_class($scope);
+                       if (!$class)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Requested scope not found', 'Requested scope ('.$scope.') not supported');
+
+                       if (!class_exists($class))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Requested class not found', 'Requested class ('.$class.') doesn\'t exist');
+
+                       $idUser = $this->get_by_key($ret['apikey']);
+                       if (!$idUser)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Invalid API key', 'Your API key is invalid');
+
+                       if (!array_key_exists('data', $ret))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Missing params element', 'Necessary input data are missing');
+
+                       if (!array_key_exists('connection', $ret['data']))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Missing connection parameters', 'Connection parameters are missing');
+
+                       $uri = $ret['data']['connection']['uri'];
+                       if (!CONNECT_WITH_NULL_STRING && $uri == 'null')
+                               $uri = false;
+
+                       if ((array_key_exists('user', $ret['data']['connection'])) && (array_key_exists('password', $ret['data']['connection'])))
+                               $lv = new Libvirt($uri, $ret['data']['connection']['user'], $ret['data']['connection']['password'], false, 'en');
+                       else
+                               $lv = new Libvirt($uri, null, null, false, 'en');
+
+                       $lib = new $class($lv, 'en');
+                       $method_name = 'rpc_'.$method;
+                       if (!method_exists($lib, $method_name))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Requested method not exist', 'Requested method '.$method_name.'() doesn\'t exist in class '.$scope);
+
+                       $this->log_override = false;
+
+                       eval('$ret = $lib->'.$method_name.'($idUser, $lv, $ret);');
+                       unset($lv);
+
+                       if ($lib->has_error())
+                               $this->log_override = $lib->get_log();
+
+                       unset($lib);
+                       return $ret;
+               }
+
+               function format_xmlrpc_reply($data, $encapsulate, $level) {
+                       $ret = '';
+                       if ((empty($data)) || ($data == false)) {
+                               $errmsg = false;
+                               $log = ($this->log_override) ? $this->log_override : $this->get_log();
+                               for ($i = sizeof($log) - 1; $i > 0; $i--) {
+                                       if ($log[$i]['type'] == TYPE_ERROR) {
+                                               $errmsg = $log[$i]['msg'];
+                                               break;
+                                       }
+                               }
+
+                               if (!$errmsg)
+                                       $errmsg = 'unknown';
+
+                               $ret = '<'."?xml version=\"1.0\" encoding=\"utf-8\"?".">\n";
+                               $ret .= "<methodResponse>\n";
+                               $ret .= "\t<fault>\n";
+                               $ret .= "\t\t<value>\n";
+                               $ret .= "\t\t\t<struct>\n";
+                               $ret .= "\t\t\t\t<member>\n";
+                               $ret .= "\t\t\t\t\t<name>faultCode</name>\n";
+                               $ret .= "\t\t\t\t\t<value><int>1</int></value>\n";
+                               $ret .= "\t\t\t\t</member>\n";
+                               $ret .= "\t\t\t\t<member>\n";
+                               $ret .= "\t\t\t\t\t<name>faultString</name>\n";
+                               $ret .= "\t\t\t\t\t<value><string>$errmsg</string></value>\n";
+                               $ret .= "\t\t\t\t</member>\n";
+                               $ret .= "\t\t\t</struct>\n";
+                               $ret .= "\t\t</value>\n";
+                               $ret .= "\t</fault>\n";
+                               $ret .= "</methodResponse>\n";
+
+                               return $ret;
+                       }
+
+                       if ($encapsulate) {
+                               for ($i = 0; $i < $level; $i++)
+                                       $ret .= "\t";
+                               $ret .= '<'."?xml version=\"1.0\" encoding=\"utf-8\"?".">\n";
+                               $ret .= "<methodResponse>\n";
+                               $ret .= "\t<params>\n";
+                               $ret .= "\t\t<param>\n";
+                               $ret .= "\t\t\t<value>\n";
+                               $ret .= "\t\t\t\t<struct>\n";
+
+                               $level += 5;
+                               $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'XML Formatting', 'Formatting XML reply data');
+                       }
+
+                       if (!is_array($data))
+                               $ret .= '<member><name>msg</name><value><string>'.htmlentities($data).'</string></value></member>';
+                       else {
+                               foreach ($data as $key => $item) {
+                                       if (!strpos($key, '-datatype')) {
+                                               for ($i = 0; $i < $level + 3; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "<member>\n";
+
+                                               for ($i = 0; $i < $level + 4; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "<name>$key</name>\n";
+
+                                               for ($i = 0; $i < $level + 4; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "<value>\n";
+
+                                               if (is_array($item)) {
+                                                       for ($i = 0; $i < $level + 5; $i++)
+                                                               $ret .= "\t";
+
+                                                       $ret .= "<struct>\n";
+
+                                                       $ret .= $this->format_xmlrpc_reply($item, false, $level + 3);
+
+                                                       for ($i = 0; $i < $level + 5; $i++)
+                                                               $ret .= "\t";
+
+                                                       $ret .= "</struct>\n";
+                                               }
+                                               else {
+                                                       for ($i = 0; $i < $level + 5; $i++)
+                                                               $ret .= "\t";
+
+                                                       if (array_key_exists($key.'-datatype', $data))
+                                                               $type = $data[$key.'-datatype'];
+                                                       else {
+                                                               if (is_string($item))
+                                                                       $type = 'string';
+                                                               elseif (is_numeric($item)) {
+                                                                       if (strpos($item, '.'))
+                                                                               $type = 'double';
+                                                                       else
+                                                                               $type = 'int';
+                                                               }
+                                                               elseif (is_bool($item)) {
+                                                                       $type = 'boolean';
+
+                                                                       $item = $item ? 1 : 0;
+                                                               }
+                                                       }
+
+                                                       $ret .= "<$type>$item</$type>\n";
+                                               }
+
+                                               for ($i = 0; $i < $level + 4; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "</value>\n";
+
+                                               for ($i = 0; $i < $level + 3; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "</member>\n";
+                                       }
+                               }
+                       }
+
+                       if ($encapsulate) {
+                               $ret .= "</struct>\n";
+
+                               for ($i = 0; $i < $level - 2; $i++)
+                                       $ret .= "\t";
+
+                               $ret .= "</value>\n";
+
+                               for ($i = 0; $i < $level - 3; $i++)
+                                       $ret .= "\t";
+
+                               $ret .= "</param>\n";
+
+                               for ($i = 0; $i < $level - 4; $i++)
+                                       $ret .= "\t";
+
+                               $ret .= "</params>\n";
+
+                               $ret .= "</methodResponse>";
+                       }
+
+                       return $ret;
+               }
+
+               function format_rpc_reply($data, $encapsulate=false, $level = 0) {
+                       if ($this->is_xmlrpc)
+                               return $this->format_xmlrpc_reply($data, $encapsulate, $level);
+
+                       $ret = '';
+                       if (empty($data)) {
+                               $errmsg = false;
+                               $log = ($this->log_override) ? $this->log_override : $this->get_log();
+                               for ($i = sizeof($log) - 1; $i > 0; $i--) {
+                                       if ($log[$i]['type'] == TYPE_ERROR) {
+                                               $errmsg = $log[$i]['msg'];
+                                               break;
+                                       }
+                               }
+
+                               if (!$errmsg)
+                                       $errmsg = 'unknown';
+
+                               $ret = '<'."?xml version=\"1.0\" encoding=\"utf-8\"?".">\n";
+                               $ret .= "<reply>\n";
+                               $ret .= "\t<method>{$this->_scope}.{$this->_method}</method>\n";
+                               $ret .= "\t<apikey>{$this->_apikey}</apikey>\n";
+                               $ret .= "\t<data>\n";
+                               $ret .= "\t\t<error>$errmsg</error>\n";
+                               $ret .= "\t</data>\n";
+                               $ret .= "</reply>\n";
+
+                               return $ret;
+                       }
+
+                       if ($encapsulate) {
+                               for ($i = 0; $i < $level; $i++)
+                                       $ret .= "\t";
+                               $ret .= '<'."?xml version=\"1.0\" encoding=\"utf-8\"?".">\n";
+                               $ret .= "<reply>\n";
+                               $ret .= "\t<method>{$this->_scope}.{$this->_method}</method>\n";
+                               $ret .= "\t<apikey>{$this->_apikey}</apikey>\n";
+                               $ret .= "\t<data>\n";
+
+                               $level += 2;
+                               $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'XML Formatting', 'Formatting XML reply data');
+                       }
+
+                       foreach ($data as $key => $item) {
+                               if (!strpos($key, '-datatype')) {
+                                       for ($i = 0; $i < $level; $i++)
+                                               $ret .= "\t";
+
+                                       $ret .= "<item>\n";
+
+                                       for ($i = 0; $i < $level + 1; $i++)
+                                               $ret .= "\t";
+
+                                       $ret .= "<name>$key</name>\n";
+
+                                       if (is_array($item)) {
+                                               for ($i = 0; $i < $level + 1; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "<array>\n";
+
+                                               $ret .= $this->format_rpc_reply($item, false, $level + 2);
+
+                                               for ($i = 0; $i < $level + 1; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "</array>\n";
+                                       }
+                                       else {
+                                               for ($i = 0; $i < $level + 1; $i++)
+                                                       $ret .= "\t";
+
+                                               $ret .= "<value>$item</value>\n";
+                                       }
+
+                                       for ($i = 0; $i < $level; $i++)
+                                               $ret .= "\t";
+
+                                       $ret .= "</item>\n";
+                               }
+                       }
+
+                       if ($encapsulate) {
+                               for ($i = 0; $i < $level - 1; $i++)
+                                       $ret .= "\t";
+
+                               $ret .= "</data>\n";
+
+                               for ($i = 0; $i < $level - 2; $i++)
+                                       $ret .= "\t";
+
+                               $ret .= "</reply>";
+                       }
+
+                       return $ret;
+               }
+       }
+?>
index 21d4b69..5055e6f 100644 (file)
@@ -9,6 +9,7 @@
                private $default_password = 'admin';
                private $tab_connections = 'connections';
                private $tab_users = 'users';
+               private $tab_apikeys = 'apikeys';
                private $connections = array();
                private $db;
 
                        if (!mysql_query($qry))
                                return false;
 
+                       $qry = 'CREATE TABLE IF NOT EXISTS '.$this->prefix.$this->tab_apikeys.' ('.
+                                       'id int(11) NOT NULL AUTO_INCREMENT,'.
+                                       'idUser int NOT NULL,'.
+                                       'apikey varchar(128) NOT NULL,'.
+                                       'PRIMARY KEY (id)'.
+                               ') ENGINE=MyISAM DEFAULT CHARSET=utf8';
+
+                       if (!mysql_query($qry))
+                               return false;
+
                        /* Create a user with full permissions */
                        global $user_permissions;
                        $perms = 0;
 
                        $qry = 'INSERT INTO '.$this->prefix.$this->tab_users.'(username, password, permissions) VALUES("'.$user.'", "'.
                                $password.'", '.$perms.')';
+                       if (!mysql_query($qry))
+                               return false;
+
+                       $id = mysql_insert_id();
+                       $key = $this->_generate_unique_apikey();
+                       $qry = 'INSERT INTO '.$this->prefix.$this->tab_apikeys.'(idUser, apikey) VALUES('.$id.', "'.$key.'")';
 
                        return (mysql_query($qry) ? true : false);
                }
 
+               function user_renew_apikey($id) {
+                       $apikey = $this->_generate_unique_apikey();
+
+                       $qry = 'UPDATE '.$this->prefix.$this->tab_apikeys.' SET apikey = "'.$this->_generate_unique_apikey().'" '.
+                               'WHERE idUser = '.$id;
+
+                       return mysql_query($qry) ? $apikey : false;
+               }
+
                function user_edit($id, $user, $password, $perms) {
                        $user = mysql_real_escape_string($user);
                        $password = (strlen($password) > 0) ? hash('sha512', $password) : false;
                        if (mysql_num_rows($res) == 0)
                                return false;
 
+                       $qry = 'DELETE FROM '.$this->prefix.$this->tab_apikeys.' WHERE idUser = '.$id;
+                       mysql_query($qry);
+
                        $qry = 'DELETE FROM '.$this->prefix.$this->tab_users.' WHERE username = "'.$user.'" AND id = '.$id;
                        return (mysql_query($qry) ? true : false);
                }
 
                        $ret = array();
                        while ($rec = mysql_fetch_assoc($res)) {
+                               $res2 = mysql_query('SELECT apikey FROM '.$this->prefix.$this->tab_apikeys.' WHERE idUser = '.$rec['id']);
+                               $rec2 = mysql_fetch_assoc($res2);
+
                                $ret[] = array(
                                                'id'   => $rec['id'],
                                                'name' => $rec['username'],
-                                               'permissions' => $rec['permissions']
+                                               'permissions' => $rec['permissions'],
+                                               'apikey' => $rec2['apikey']
                                                );
                        }
 
                        return true;
                }
 
+               function _generate_unique_apikey() {
+                       $apikey = $this->generate_random_chars(128);
+                       $res = mysql_query('SELECT id FROM '.$this->prefix.$this->tab_apikeys.' WHERE apikey = "'.$apikey.'"');
+                       while (mysql_num_rows($res) > 0)
+                               $apikey = $this->generate_random_chars(128);
+
+                       return $apikey;
+               }
+
                /* Listing functions */
                function list_connections($refresh=false) {
                        if ($refresh)
 
                        return mysql_query($qry) ? true : false;
                }
+
+               /* Used for XmlRPC */
+               function get_by_apikey($apikey) {
+                       if (!$apikey)
+                               return false;
+
+                       $qry = 'SELECT idUser FROM '.$this->prefix.$this->tab_apikeys.' WHERE apikey = "'.$apikey.'"';
+                       $res = mysql_query($qry);
+                       if (mysql_num_rows($res) == 0)
+                               return false;
+
+                       $rec = mysql_fetch_assoc($res);
+                       return $rec['idUser'];
+               }
        }
 ?>
index 90dfd63..0d14dcd 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-       class Database {
+       class Database extends LoggerBase {
                var $unimpl = 'Function is not implemented';
                var $log = array();
                private $fatal = false;
@@ -12,7 +12,7 @@
                        $this->fatal = true;
                        $this->err('connect', $func);
 
-                       return false;
+                       return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Connect error', $this->unimpl);
                }
 
                function init() {
                        return $this->err('user_add', $this->unimpl);
                }
 
+               function user_renew_apikey($id) {
+                       return $this->err('user_renew_apikey', $this->unimpl);
+               }
+
                function user_edit($id, $user, $password, $perms) {
                        return $this->err('user_edit', $this->unimpl);
                }
                        return $this->err('close', $this->unimpl);
                }
 
+               function refresh() {
+               }
+
+               function generate_random_chars($len = 6) {
+                       $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+                       $ret = '';
+                       for ($i = 0; $i < $len; $i++)
+                               $ret = $ret.$chars[rand() % strlen($chars)];
+
+                       return $ret;
+               }
+
                /* Listing functions */
                function list_connections() {
                        return $this->err('list_connections', $this->unimpl);
                function remove_connection($id) {
                        return $this->err('remove_connection', $this->unimpl);
                }
+
+               /* Used for XmlRPC */
+               function get_by_apikey($apikey) {
+                       return false;
+               }
        }
 ?>
index bc73425..ff75c7a 100644 (file)
 
                                        'mem-alloc-changed' => 'Memory allocation for next run has been altered successfully',
                                        'mem-alloc-change-error' => 'Cannot change memory allocation',
+                                       'apikey-renew' => 'Renew API key for XMLRPC',
                                        );
 
                        $this->trans = $trans;
index 1ac0724..a856425 100644 (file)
@@ -13,6 +13,9 @@
                }
 
                function getData() {
+                       if (!$this->data)
+                               return $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'No data', 'No data set yet');
+
                        return $this->data;
                }
 
                }
 
                function createNewVM($input) {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Domain', 'Libvirt class is not set');
+
                        $lv = $this->lv;
                        $lang = $this->lang;
 
                                                'maxvcpu' => $maxvcpu
                                        );
                }
+
+               function rpc_list($idUser, $lv, $ret) {
+                       return $lv->get_domains();
+               }
+
+               function rpc_start($idUser, $lv, $ret) {
+                       if ((!array_key_exists('data', $ret)) || (!array_key_exists('data', $ret['data'])) || (!array_key_exists('name', $ret['data']['data'])))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Name is missing', 'Domain name is missing');
+
+                       $name = $ret['data']['data']['name'];
+                       return ($lv->domain_start($name)) ? 'Domain started successfully' : 'Cannot start domain';
+               }
+
+               function rpc_stop($idUser, $lv, $ret) {
+                       if ((!array_key_exists('data', $ret)) || (!array_key_exists('data', $ret['data'])) || (!array_key_exists('name', $ret['data']['data'])))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Name is missing', 'Domain name is missing');
+
+                       $name = $ret['data']['data']['name'];
+                       return ($lv->domain_destroy($name)) ? 'Domain stopped successfully' : 'Cannot stop domain';
+               }
+
+               function rpc_reboot($idUser, $lv, $ret) {
+                       if ((!array_key_exists('data', $ret)) || (!array_key_exists('data', $ret['data'])) || (!array_key_exists('name', $ret['data']['data'])))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Name is missing', 'Domain name is missing');
+
+                       $name = $ret['data']['data']['name'];
+                       return ($lv->domain_reboot($name)) ? 'Domain reboot triggered successfully' : 'Cannot trigger reboot command';
+               }
+
+               function rpc_info($idUser, $lv, $ret) {
+                       if ((!array_key_exists('data', $ret)) || (!array_key_exists('data', $ret['data'])) || (!array_key_exists('name', $ret['data']['data'])))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Name is missing', 'Domain name is missing');
+
+                       $name = $ret['data']['data']['name'];
+                       $tmp = $lv->domain_get_info_call($name);
+                       $ret = $tmp[$name];
+
+                       if ($lv->domain_is_running($name))
+                               $ret['vnc_port'] = $lv->domain_get_vnc_port($name);
+
+                       $ret['arch'] = $lv->domain_get_arch($name);
+                       $ret['boot_devices'] = $lv->domain_get_boot_devices($name);
+                       $ret['multimedia'] = array(
+                                                       'console' => $lv->domain_get_multimedia_device($name, 'console'),
+                                                       'input' => $lv->domain_get_multimedia_device($name, 'input'),
+                                                       'graphics' => $lv->domain_get_multimedia_device($name, 'graphics'),
+                                                       'video' => $lv->domain_get_multimedia_device($name, 'video')
+                                               );
+
+                       $ret['devices'] = $lv->domain_get_host_devices($name);
+                       if (!$ret['devices']['pci'])
+                               $ret['devices']['pci'] = 'none';
+                       if (!$ret['devices']['usb'])
+                               $ret['devices']['usb'] = 'none';
+                       $ret['clock-offset'] = $lv->domain_get_clock_offset($name);
+
+                       $features = array('apic', 'acpi', 'pae', 'hap');
+                       $feat = array();
+                       for ($i = 0; $i < sizeof($features); $i++) {
+                               if ($lv->domain_get_feature($name, $features[$i]))
+                                       $feat[] = $features[$i];
+                       }
+
+                       $ret['features'] = join(',', $feat);
+
+                       $ret['state'] = $lv->domain_state_translate($ret['state']);
+                       return $ret;
+               }
        }
 ?>
index 22cfa3e..eaa61f7 100644 (file)
@@ -2,11 +2,17 @@
        class LibvirtInfo extends LoggerBase {
                private $lv = false;
 
-               function LibvirtInfo($libvirtInstance) {
+               function LibvirtInfo($libvirtInstance = false) {
+                       if (!$libvirtInstance)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        $this->lv = $libvirtInstance;
                }
 
                function getModuleInfo() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        ob_start();
                        PHPInfo();
                        $c = ob_get_contents();
                }
 
                function getConnectInfo() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->get_connect_information();
                }
 
                function getNodeInfo() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->host_get_node_info();
                }
 
                function getCpuStats() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->node_get_cpu_stats();
                }
 
                function getCpuStatsEachCPU() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->node_get_cpu_stats_each_cpu(2);
                }
 
                function getMemoryStats() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->node_get_mem_stats();
                }
 
                function getSystemInfo() {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Information', 'Libvirt class is not set');
+
                        return $this->lv->connect_get_sysinfo();
                }
+
+               function rpc_get($idUser, $lv, $ret) {
+                       if (!array_key_exists('data', $ret))
+                               return false;
+                       if (!array_key_exists('data', $ret['data']))
+                               return false;
+                       if (!array_key_exists('type', $ret['data']['data']))
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Type is missing. Type can be one of following:'
+                                               .' connection, node, cpustats, eachcpustats, memstats, system');
+
+                       switch ($ret['data']['data']['type']) {
+                               case 'connection':      return $lv->get_connect_information();
+                                                       break;
+                               case 'node':            return $lv->host_get_node_info();
+                                                       break;
+                               case 'cpustats':        return $lv->node_get_cpu_stats();
+                                                       break;
+                               case 'eachcpustats':    return $lv->node_get_cpu_stats_each_cpu(2);
+                                                       break;
+                               case 'memstats':        return $lv->node_get_mem_stats();
+                                                       break;
+                               case 'system':          return $lv->connect_get_sysinfo();
+                                                       break;
+                       }
+
+                       return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Type not supported. Type can be one of following: '
+                                               .' connection, node, cpustats, eachcpustats, memstats, system');
+               }
        }
 ?>
index 3766d93..e342ed9 100644 (file)
@@ -13,6 +13,9 @@
                }
 
                function getData() {
+                       if (!$this->data)
+                               return $this->log(TYPE_INFO, __CLASS__.'::'.__FUNCTION__, 'No data', 'No data set yet');
+
                        return $this->data;
                }
 
                }
 
                function createNewNetwork($input) {
+                       if (!$this->lv)
+                               return $this->log(TYPE_ERROR, __CLASS__.'::'.__FUNCTION__, 'Libvirt Network', 'Libvirt class is not set');
+
                        $lv = $this->lv;
                        $lang = $this->lang;
 
index e906016..00ccaed 100644 (file)
@@ -10,6 +10,7 @@
 
        class LoggerBase {
                private $log = array();
+               private $debug = true;
 
                function log($type, $lib, $msg, $data='') {
                        if (!$this->debug) return;
                function get_log() {
                        return $this->log;
                }
+
+               function type_fmt($type) {
+                       if ($type == TYPE_INFO)
+                               return 'Information';
+                       else
+                       if ($type == TYPE_ERROR)
+                               return 'Error';
+                       else
+                       if ($type == TYPE_WARN)
+                               return 'Warning';
+                       else
+                       if ($type == TYPE_FATAL)
+                               return 'Fatal error';
+
+                       return false;
+               }
+
+               function log_print($data) {
+                       $type = $this->type_fmt($data['type']);
+
+                       if ($type == false)
+                               return false;
+
+                       switch ($data['type']) {
+                               case TYPE_INFO: $col = 'lightgreen';
+                                               break;
+                               case TYPE_ERROR: $col = '#b22222';
+                                               break;
+                               case TYPE_FATAL: $col = '#b22522';
+                                               break;
+                               case TYPE_WARN: $col = 'gray';
+                                               break;
+                       }
+
+                       return "<tr align=\"center\" style=\"background-color:$col; font-weight: bold;\"><td>$type</td><td>{$data['lib']}</td>"
+                               ."<td>{$data['msg']}</td><td>{$data['data']}</td></tr>";
+               }
+
+               function log_dump($showFooter = false) {
+                       if (!$this->debug) return;
+
+                       $entries = 0;
+
+                       echo "<table border=0 cellspacing=0 width=\"95%\" align=\"center\"><tr><td>&nbsp;</td></tr>";
+                       echo "<tr><th>Type</th><th>Source</th><th>Message</th><th>Additional data</th></tr>";
+                       echo "<tr><td>&nbsp;</td></tr>";
+                       for ($i = 0; $i < sizeof($this->log); $i++) {
+                               $tmp = $this->log_print($this->log[$i]);
+
+                               if (is_string($tmp)) {
+                                       echo $tmp;
+                                       $entries++;
+                               }
+                       }
+
+                       if ($entries == 0)
+                               echo '<tr align="center" style="background-color: lightgreen"><td colspan="4">No items in the log</td></tr>';
+                       echo '</table>';
+
+                       if ($showFooter)
+                               echo '<br /><center>Number of displayed log entries: '.$entries.'</center>';
+               }
+
+               function has_error() {
+                       for ($i = 0; $i < sizeof($this->log); $i++) {
+                               if ($this->log[$i]['type'] == TYPE_ERROR)
+                                       return true;
+                       }
+
+                       return false;
+                }
+
+               function has_error_fatal() {
+                       for ($i = 0; $i < sizeof($this->log); $i++) {
+                               if ($this->log[$i]['type'] == TYPE_FATAL)
+                                       return true;
+                       }
+
+                       return false;
+               }
        }
 ?>
index 40a9edf..408961d 100644 (file)
@@ -1,8 +1,9 @@
 
 <html> 
 <head> 
- <title>php-virt-control - <?php echo $lang->get('title-vmc') ?></title
- <link rel="STYLESHEET" type="text/css" href="manager.css"> 
+ <title>php-virt-control - <?php echo $lang->get('title-vmc') ?></title>
+ <link rel="STYLESHEET" type="text/css" href="manager.css" />
+ <link rel="STYLESHEET" type="text/css" href="html/main.css" />
 </head> 
 <body> 
   <div id="header"> 
index 626be17..2750628 100644 (file)
--- a/init.php
+++ b/init.php
                exit;
        }
 
-       if (!array_key_exists('language', $_SESSION)) {
-               $tmp = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
-               $tmp = explode('-', $tmp[0]);
-               $lang_str = $tmp[0];
-               unset($tmp);
+       if (!isset($is_xmlrpc)) {
+               if (!array_key_exists('language', $_SESSION)) {
+                       $tmp = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+                       $tmp = explode('-', $tmp[0]);
+                       $lang_str = $tmp[0];
+                       unset($tmp);
+               }
+               else
+                       $lang_str = $_SESSION['language'];
        }
-       else
-               $lang_str = $_SESSION['language'];
+
+       /* Fallback to default language */
+       if (!isset($lang_str))
+               $lang_str = 'en';
 
        if (!File_Exists(LOGDIR)) {
                if (!mkdir(LOGDIR, 0777))
@@ -70,6 +76,7 @@
        require('classes/database.php');
        require('classes/database-file.php');
        require('classes/database-mysql.php');
+       require('classes/XmlRPC.php');
 
        $lang = new Language($lang_str);
 
                unset($_SESSION['connection_logging']);
        }
 
-       if (!verify_user($db)) {
+       if ((!verify_user($db)) && (!isset($is_xmlrpc))) {
                include('dialog-login.php');
                exit;
        }
index aba6dcc..569d920 100644 (file)
 
                                        'mem-alloc-changed' => 'Paměť přidělená doméněu byla změněna',
                                        'mem-alloc-change-error' => 'Nelze změnit přidělenou paměť',
+                                       'apikey-renew' => 'Obnovit API klíč pro XMLRPC',
                                        );
 ?>
index 51dbe71..5896891 100644 (file)
 
                                        'mem-alloc-changed' => 'Memory allocation for next run has been altered successfully',
                                        'mem-alloc-change-error' => 'Cannot change memory allocation',
+                                       'apikey-renew' => 'Renew API key for XMLRPC',
                                        );
 ?>
index d5c6cb1..ab8bfce 100644 (file)
@@ -16,6 +16,9 @@
 
        $uid = array_key_exists('user_id', $_GET) ? $_GET['user_id'] : false;
 
+       if (isset($_GET['renew']))
+               $msg = ($db->user_renew_apikey($_GET['user_id'])) ? 'API Key renewed successfully' : 'API Key renew failed';
+
        if ((($action == 'new') && (verify_user($db, USER_PERMISSION_USER_CREATE)))
                || ((($action == 'edit') && (verify_user($db, USER_PERMISSION_USER_EDIT))) || ($uid == $myId))):
                $skip = true;
@@ -64,6 +67,7 @@
                                        if ($tmp[$i]['id'] == $_GET['user_id']) {
                                                $p_user = $tmp[$i]['name'];
                                                $perms = $tmp[$i]['permissions'];
+                                               $apikey = $tmp[$i]['apikey'];
 
                                                $p_perms = array();
                                                while (list($key, $val) = each($user_permissions))
 ?>
 <div id="content">
 
+<?php
+       if (isset($msg))
+               echo "<div id=\"msg\"><b>{$lang->get('msg')}: </b>$msg</div>";
+?>
+
 <div class="section"><?php echo $lang->get($ident) ?></div>
 
 <form method="POST">
        }
 ?>
        </div>
-        <div class="nl">
+        <div class="nl" />
+</div>
+
+<div class="item">
+       <div class="label">API Key: </div>
+       <div class="value">
+               <textarea rows="3" cols="50" readonly="readonly"><?php echo $apikey ?></textarea> &nbsp; <input type="button" value=" <?php echo $lang->get('apikey-renew') ?> " onclick="javascript:location.href='<?php echo $_SERVER['REQUEST_URI'] ?>&amp;renew=1'"/>
+       </div>
+       <div class="nl" />
 </div>
 
 <div class="item">
diff --git a/xmlrpc-test.py b/xmlrpc-test.py
new file mode 100755 (executable)
index 0000000..2e70281
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/python3
+
+import sys
+import xmlrpc.client
+
+apikey = "8YBjNqa8zFyTs3lZqqqNnkC8KT3KQsxOfWWQaUNAq9jI2vxgJLRUWkSu1M2H2ok5a6MbQpB4oICe1YjAz0lj83E0DwvwKFrJz1Ige7asFBvxnEVKs6UrYmfpGyLf41Mr"
+address = "http://localhost/php-virt-control/xmlrpc.php"
+selections = ['Information', 'Domain', 'Network']
+info_types = ['connection', 'node', 'cpustats', 'eachcpustats', 'memstats', 'system']
+domactions = ['Start', 'Stop', 'Reboot', 'Dump', 'Migrate', 'Get screenshot']
+request = {'apikey': apikey,
+           'connection': {'uri': 'qemu:///system'}
+         }
+
+request_info = request
+request_info['data'] = {'type': 'unknown'}
+
+request_domain = request
+request_domain['data'] = {'name': 'x'}
+
+def choose(prompt, chooser, types):
+    print("\n%s types:\n" % chooser)
+    num = 0
+    for onetype in types:
+        print("\t%s) %s" % (num + 1, types[num]))
+        num += 1
+    print("\n")
+    line = input(prompt)
+    try:
+        return int(line) - 1
+    except:
+        return -1
+
+try:
+    print("XML RPC Proxy is set to %s" % address)
+    if input("Is that OK? (Y/n) ") == "n":
+        address = input("Enter new address: ")
+
+    proxy = xmlrpc.client.ServerProxy(address)
+    num = choose("Enter type: ", "Type", selections)
+    if num == 0:
+        num = choose("Enter your choice: ", "Information", info_types)
+        if num > -1:
+            request['data']['type'] = info_types[num]
+
+            print("Result is: %s" % proxy.Information.get(request_info))
+    elif num == 1:
+        l = proxy.Domain.list(request)
+        print("Domains:\n")
+        for d in l:
+            print("%s) %s" % (int(d) + 1, l[d]))
+        print("\n")
+        line = input("Choose domain index: ")
+        try:
+            idx = int(line) - 1
+        except:
+            sys.exit(1)
+
+        # Assign the name to request_domain dictionary
+        name = l[str(idx)]
+        request_domain['data']['name'] = name
+
+        l = proxy.Domain.info(request_domain)
+        print("\nDomain information:\n\nName: %s\nvCPUs: %s\nState: %s\nMemory: %s MiB (max %s MiB)\nCPUUsed: %s" %
+            (name, l['nrVirtCpu'], l['state'], l['memory'] / 1024, l['maxMem'] / 1024, l['cpuUsed']))
+        print("\nFeatures: %s\nMultimedia:\n\tInput: %s\n\tVideo: %s\n\tConsole: %s\n\tGraphics: %s\nHost devices: %s\nBoot devices: %s\n" %
+            (l['features'], l['multimedia']['input'], l['multimedia']['video'], l['multimedia']['console'],
+             l['multimedia']['graphics'], l['devices'], l['boot_devices']))
+        num = choose("Enter your choice: ", "Domain actions", domactions)
+        if num == -1:
+            sys.exit(1)
+
+        # Process actions
+        if num == 0:
+            print("Starting up domain %s" % name)
+            print("Method returned: %s" % proxy.Domain.start(request_domain))
+        elif num == 1:
+            print("Stopping domain %s" % name)
+            print("Method returned: %s" % proxy.Domain.stop(request_domain))
+        elif num == 2:
+            print("Rebooting %s" % name)
+            print("Method returned: %s" % proxy.Domain.reboot(request_domain))
+        elif num == 3:
+            print("Dumping %s" % name)
+        elif num == 4:
+            print("Migrating %s" % name)
+        elif num == 5:
+            print("Getting screenshot of %s" % name)
+
+except xmlrpc.client.ProtocolError as err:
+    print("A protocol error occurred")
+    print("URL: %s" % err.url)
+    print("HTTP/HTTPS headers: %s" % err.headers)
+    print("Error code: %d" % err.errcode)
+    print("Error message: %s" % err.errmsg)
diff --git a/xmlrpc.php b/xmlrpc.php
new file mode 100644 (file)
index 0000000..2795f0d
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+       error_reporting(0);
+
+       $is_xmlrpc = true;
+       require('init.php');
+
+       $rpc = new XmlRPC($db);
+       echo $rpc->getData();
+       unset($rpc);
+?>