9764fbaf8dc6c07d26e91d69bf5ebb160d27482c
[plcapi.git] / php / plc_api.php
1 <?php
2 //
3 // PlanetLab Central Slice API (PLCAPI) PHP interface
4 //
5 // DO NOT EDIT. This file was automatically generated at
6 // @DATE@.
7 //
8 // Mark Huang <mlhuang@cs.princeton.edu>
9 // Copyright (C) 2005-2006 The Trustees of Princeton University
10 //
11
12 //ini_set('error_reporting', 1);
13
14 /*
15  * May 2017 - Ciro Scognamiglio <c.scognamiglio@cslash.net>
16  *
17  * xmlrpc php module is not compatible anymore with the PLCAPI class,
18  * if the package phpxmlrpc is installed in the same dir it will be used instead
19  *
20  * https://github.com/gggeek/phpxmlrpc
21  *
22  * If the package is not found the php module XML-RPC is used if available
23  *
24  */
25 if (file_exists(__DIR__ . '/phpxmlrpc/src/Autoloader.php')) {
26     include_once __DIR__ . '/phpxmlrpc/src/Autoloader.php';
27     PhpXmlRpc\Autoloader::register();
28 }
29
30 require_once 'plc_config.php';
31
32 class PLCAPI
33 {
34   var $auth;
35   var $server;
36   var $port;
37   var $path;
38   var $errors;
39   var $trace;
40   var $calls;
41   var $multicall;
42
43   function PLCAPI($auth = NULL,
44                   $server = PLC_API_HOST,
45                   $port = PLC_API_PORT,
46                   $path = PLC_API_PATH,
47                   $cainfo = NULL)
48   {
49     $this->auth = $auth;
50     $this->server = $server;
51     $this->port = $port;
52     $this->path = $path;
53     $this->cainfo = $cainfo;
54     $this->errors = array();
55     $this->trace = array();
56     $this->calls = array();
57     $this->multicall = false;
58   }
59
60   function rec_join ($arg) {
61     if ( is_array($arg) ) {
62         $ret = "";
63         foreach ( $arg as $i ) {
64             $l = $this->rec_join($i);
65             # ignore html code.
66             if ( $l[0] != "<" ) { $ret .= $l . ", "; }
67         }
68         return $ret;
69     } else {
70         settype($arg, "string");
71         return $arg;
72     }
73   }
74
75   function backtrace_php () {
76     $backtrace = debug_backtrace();
77     $msg = "";
78     $len = count($backtrace);
79     $cnt = 1;
80     foreach( array_reverse($backtrace) as $line ) {
81         $msg .= "File '". $line['file'] . "' line " . $line['line'] . "\n";
82         $msg .= "    " . $line['function'] . "( "  . $this->rec_join($line['args']) . ")\n";
83         $cnt += 1;
84         if ( $cnt == $len ) { break; }
85     }
86     return $msg;
87   }
88
89   function error_log($error_msg, $backtrace_level = 1)
90   {
91     $backtrace = debug_backtrace();
92     $file = $backtrace[$backtrace_level]['file'];
93     $line = $backtrace[$backtrace_level]['line'];
94
95     $error_line='PLCAPI error:  ' . $error_msg ;
96     if ($file) $error_line .= ' in file ' . $file;
97     if ($line) $error_line .= ' on line ' . $line;
98     $this->errors[] = $error_line;
99     # TODO: setup a config variable for more detailed stack traces, for API errors.
100     if ( TRUE ){
101       error_log($error_line);
102     } else {
103        error_log($this->backtrace_php());
104     }
105   }
106
107   function error()
108   {
109     if (empty($this->trace)) {
110       return NULL;
111     } else {
112       $last_trace = end($this->trace);
113       return implode("\\n", $last_trace['errors']);
114     }
115   }
116
117   function trace()
118   {
119     return $this->trace;
120   }
121
122   function microtime_float()
123   {
124     list($usec, $sec) = explode(" ", microtime());
125     return ((float) $usec + (float) $sec);
126   }
127
128   function call($method, $args = NULL)
129   {
130     if ($this->multicall) {
131       $this->calls[] = array ('methodName' => $method,
132                                 'params' => $args);
133       return NULL;
134     } else {
135       return $this->internal_call($method, $args, 3);
136     }
137   }
138
139   /*
140    * Use PhpXmlRpc\Value before encoding the request
141    */
142   function xmlrpcValue($value) {
143       switch(gettype($value)) {
144           case 'array':
145               $members = array();
146               foreach($value as $vk => $vv) {
147                   $members[$vk] = $this->xmlrpcValue($vv);
148               }
149
150               if (array_key_exists(0, $value)) {
151                   return new PhpXmlRpc\Value(
152                       $members,
153                       'array'
154                   );
155               } else {
156                   return new PhpXmlRpc\Value(
157                       $members,
158                       'struct'
159                   );
160               }
161
162               break;
163           case 'null':
164               return new PhpXmlRpc\Value(null, 'null');
165               break;
166           case 'integer':
167               return new PhpXmlRpc\Value($value, 'int');
168               break;
169           default:
170             if (empty($value)) {
171                 return new PhpXmlRpc\Value(null, 'null');
172             } else {
173                 return new PhpXmlRpc\Value($value);
174             }
175
176               break;
177       }
178   }
179
180     function internal_call($method, $args = NULL, $backtrace_level = 2)
181     {
182         if (class_exists('PhpXmlRpc\\PhpXmlRpc')) {
183             return $this->internal_call_phpxmlrpc($method, $args, $backtrace_level);
184         } else {
185             return $this->internal_call_xmlrpc($method, $args, $backtrace_level);
186         }
187     }
188
189   /*
190    * the new internal call, will use PhpXmlRpc
191    */
192   function internal_call_phpxmlrpc($method, $args = NULL, $backtrace_level = 2)
193   {
194
195
196       PhpXmlRpc\PhpXmlRpc::$xmlrpc_null_extension = true;
197
198       if ($this->port == 443) {
199           $url = 'https://';
200       } else {
201           $url = 'http://';
202       }
203
204       // Set the URL for the request
205       $url .= $this->server . ':' . $this->port . '/' . $this->path;
206
207       $client = new PhpXmlRpc\Client($url);
208       $client->setSSLVerifyPeer(false);
209       /*
210        * 1 -> not verify CN
211        * 2 -> verify CN (default)
212        */
213       $client->setSSLVerifyHost(1);
214
215       $values = $this->xmlrpcValue($args);
216
217       $response = $client->send(new PhpXmlRpc\Request($method, $values));
218
219
220       if (!$response->faultCode()) {
221           $encoder = new PhpXmlRpc\Encoder();
222           $v = $encoder->decode($response->value());
223
224           return $v;
225       } else {
226           $this->error_log("An error occurred [" . $response->faultCode() . "] ".
227               $response->faultString());
228           return NULL;
229       }
230   }
231
232   /*
233    * The original internal call that uses php XML-RPC
234    */
235   function internal_call_xmlrpc($method, $args = NULL, $backtrace_level = 2)
236   {
237     $curl = curl_init();
238
239     // Verify peer certificate if talking over SSL
240     if ($this->port == 443) {
241       curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
242       if (!empty($this->cainfo)) {
243         curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
244       } elseif (defined('PLC_API_CA_SSL_CRT')) {
245         curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
246       }
247       $url = 'https://';
248     } else {
249       $url = 'http://';
250     }
251
252     // Set the URL for the request
253     $url .= $this->server . ':' . $this->port . '/' . $this->path;
254     curl_setopt($curl, CURLOPT_URL, $url);
255
256     // Marshal the XML-RPC request as a POST variable. <nil/> is an
257     // extension to the XML-RPC spec that is supported in our custom
258     // version of xmlrpc.so via the 'allow_null' output_encoding key.
259     $request = xmlrpc_encode_request($method, $args, array('null_extension'));
260     curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
261
262     // Construct the HTTP header
263     $header[] = 'Content-type: text/xml';
264     $header[] = 'Content-length: ' . strlen($request);
265     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
266
267     // Set some miscellaneous options
268     curl_setopt($curl, CURLOPT_TIMEOUT, 180);
269
270     // Get the output of the request
271     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
272     $t0 = $this->microtime_float();
273     $output = curl_exec($curl);
274     $t1 = $this->microtime_float();
275
276     if (curl_errno($curl)) {
277       $this->error_log('curl: ' . curl_error($curl), true);
278       $ret = NULL;
279     } else {
280       $ret = xmlrpc_decode($output);
281       if (is_array($ret) && xmlrpc_is_fault($ret)) {
282         $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
283                          $ret['faultString'], $backtrace_level, true);
284         $ret = NULL;
285       }
286     }
287
288     curl_close($curl);
289
290     $this->trace[] = array('method' => $method,
291                            'args' => $args,
292                            'runtime' => $t1 - $t0,
293                            'return' => $ret,
294                            'errors' => $this->errors);
295     $this->errors = array();
296
297     return $ret;
298   }
299
300   function begin()
301   {
302     if (!empty($this->calls)) {
303       $this->error_log ('Warning: multicall already in progress');
304     }
305
306     $this->multicall = true;
307   }
308
309   function xmlrpc_is_fault($arr)
310     {
311         // check if xmlrpc_is_fault exists
312         return is_array($arr) && array_key_exists('faultCode', $arr) && array_key_exists('faultString', $arr);
313     }
314   function commit()
315   {
316     if (!empty ($this->calls)) {
317       $ret = array();
318       $results = $this->internal_call('system.multicall', array ($this->calls));
319       foreach ($results as $result) {
320         if (is_array($result)) {
321           if ($this->xmlrpc_is_fault($result)) {
322             $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
323                              $result['faultString'], 1, true);
324             $ret[] = NULL;
325             // Thierry - march 30 2007 
326             // using $adm->error() is broken with begin/commit style 
327             // this is because error() uses last item in trace and checks for ['errors']
328             // when using begin/commit we do run internal_call BUT internal_call checks for 
329             // multicall's result globally, not individual results, so ['errors'] comes empty
330             // I considered hacking internal_call 
331             // to *NOT* maintain this->trace at all when invoked with multicall
332             // but it is too complex to get all values right
333             // so let's go for the hacky way, and just record individual errors at the right place
334             $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
335           } else {
336             $ret[] = $result[0];
337           }
338         } else {
339           $ret[] = $result;
340         }
341       }
342     } else {
343       $ret = NULL;
344     }
345
346     $this->calls = array();
347     $this->multicall = false;
348
349     return $ret;
350   }
351
352   //
353   // PLCAPI Methods
354   //
355
356   function __call($name, $args)
357   {
358      array_unshift($args, $this->auth);
359      return $this->call($name, $args);
360   }
361 }
362
363 global $adm;
364
365 $adm = new PLCAPI(array('AuthMethod' => "capability",
366                         'Username' => PLC_API_MAINTENANCE_USER,
367                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
368
369 ?>