0c2187e1901394c7371854433c630abe13344a30
[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 'double':
164               return new PhpXmlRpc\Value($value, 'double');
165               break;
166           case 'boolean':
167               return new PhpXmlRpc\Value($value, 'boolean');
168               break;
169           case 'NULL':
170           case 'null':
171               return new PhpXmlRpc\Value(null, 'null');
172               break;
173           case 'integer':
174               return new PhpXmlRpc\Value($value, 'int');
175               break;
176           default:
177             if (empty($value)) {
178                 return new PhpXmlRpc\Value(null, 'null');
179             } else {
180                 return new PhpXmlRpc\Value($value);
181             }
182
183               break;
184       }
185   }
186
187     function internal_call($method, $args = NULL, $backtrace_level = 2)
188     {
189         if (class_exists('PhpXmlRpc\\PhpXmlRpc')) {
190             return $this->internal_call_phpxmlrpc($method, $args, $backtrace_level);
191         } else {
192             return $this->internal_call_xmlrpc($method, $args, $backtrace_level);
193         }
194     }
195
196   /*
197    * the new internal call, will use PhpXmlRpc
198    */
199   function internal_call_phpxmlrpc($method, $args = NULL, $backtrace_level = 2)
200   {
201
202
203       PhpXmlRpc\PhpXmlRpc::$xmlrpc_null_extension = true;
204
205       if ($this->port == 443) {
206           $url = 'https://';
207       } else {
208           $url = 'http://';
209       }
210
211       // Set the URL for the request
212       $url .= $this->server . ':' . $this->port . '/' . $this->path;
213
214       $client = new PhpXmlRpc\Client($url);
215       $client->setSSLVerifyPeer(false);
216       /*
217        * 1 -> not verify CN
218        * 2 -> verify CN (default)
219        */
220       $client->setSSLVerifyHost(1);
221
222       $values = $this->xmlrpcValue($args);
223
224       $response = $client->send(new PhpXmlRpc\Request($method, $values));
225
226
227       if (!$response->faultCode()) {
228           $encoder = new PhpXmlRpc\Encoder();
229           $v = $encoder->decode($response->value());
230
231           return $v;
232       } else {
233           $this->error_log("An error occurred [" . $response->faultCode() . "] ".
234               $response->faultString());
235           return NULL;
236       }
237   }
238
239   /*
240    * The original internal call that uses php XML-RPC
241    */
242   function internal_call_xmlrpc($method, $args = NULL, $backtrace_level = 2)
243   {
244     $curl = curl_init();
245
246     // Verify peer certificate if talking over SSL
247     if ($this->port == 443) {
248       curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
249       if (!empty($this->cainfo)) {
250         curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
251       } elseif (defined('PLC_API_CA_SSL_CRT')) {
252         curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
253       }
254       $url = 'https://';
255     } else {
256       $url = 'http://';
257     }
258
259     // Set the URL for the request
260     $url .= $this->server . ':' . $this->port . '/' . $this->path;
261     curl_setopt($curl, CURLOPT_URL, $url);
262
263     // Marshal the XML-RPC request as a POST variable. <nil/> is an
264     // extension to the XML-RPC spec that is supported in our custom
265     // version of xmlrpc.so via the 'allow_null' output_encoding key.
266     $request = xmlrpc_encode_request($method, $args, array('null_extension'));
267     curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
268
269     // Construct the HTTP header
270     $header[] = 'Content-type: text/xml';
271     $header[] = 'Content-length: ' . strlen($request);
272     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
273
274     // Set some miscellaneous options
275     curl_setopt($curl, CURLOPT_TIMEOUT, 180);
276
277     // Get the output of the request
278     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
279     $t0 = $this->microtime_float();
280     $output = curl_exec($curl);
281     $t1 = $this->microtime_float();
282
283     if (curl_errno($curl)) {
284       $this->error_log('curl: ' . curl_error($curl), true);
285       $ret = NULL;
286     } else {
287       $ret = xmlrpc_decode($output);
288       if (is_array($ret) && xmlrpc_is_fault($ret)) {
289         $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
290                          $ret['faultString'], $backtrace_level, true);
291         $ret = NULL;
292       }
293     }
294
295     curl_close($curl);
296
297     $this->trace[] = array('method' => $method,
298                            'args' => $args,
299                            'runtime' => $t1 - $t0,
300                            'return' => $ret,
301                            'errors' => $this->errors);
302     $this->errors = array();
303
304     return $ret;
305   }
306
307   function begin()
308   {
309     if (!empty($this->calls)) {
310       $this->error_log ('Warning: multicall already in progress');
311     }
312
313     $this->multicall = true;
314   }
315
316   function xmlrpc_is_fault($arr)
317     {
318         // check if xmlrpc_is_fault exists
319         return is_array($arr) && array_key_exists('faultCode', $arr) && array_key_exists('faultString', $arr);
320     }
321   function commit()
322   {
323     if (!empty ($this->calls)) {
324       $ret = array();
325       $results = $this->internal_call('system.multicall', array ($this->calls));
326       foreach ($results as $result) {
327         if (is_array($result)) {
328           if ($this->xmlrpc_is_fault($result)) {
329             $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
330                              $result['faultString'], 1, true);
331             $ret[] = NULL;
332             // Thierry - march 30 2007 
333             // using $adm->error() is broken with begin/commit style 
334             // this is because error() uses last item in trace and checks for ['errors']
335             // when using begin/commit we do run internal_call BUT internal_call checks for 
336             // multicall's result globally, not individual results, so ['errors'] comes empty
337             // I considered hacking internal_call 
338             // to *NOT* maintain this->trace at all when invoked with multicall
339             // but it is too complex to get all values right
340             // so let's go for the hacky way, and just record individual errors at the right place
341             $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
342           } else {
343             $ret[] = $result[0];
344           }
345         } else {
346           $ret[] = $result;
347         }
348       }
349     } else {
350       $ret = NULL;
351     }
352
353     $this->calls = array();
354     $this->multicall = false;
355
356     return $ret;
357   }
358
359   //
360   // PLCAPI Methods
361   //
362
363   function __call($name, $args)
364   {
365      array_unshift($args, $this->auth);
366      return $this->call($name, $args);
367   }
368 }
369
370 global $adm;
371
372 $adm = new PLCAPI(array('AuthMethod' => "capability",
373                         'Username' => PLC_API_MAINTENANCE_USER,
374                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
375
376 ?>