1 // ----------------------------------------------------------------------------
2 //
3 // flxmlrpc Copyright (c) 2015 by W1HKJ, Dave Freese <iam_w1hkj@w1hkj.com>
4 //
5 // XmlRpc++ Copyright (c) 2002-2008 by Chris Morley
6 //
7 // This file is part of fldigi
8 //
9 // flxmlrpc is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU Lesser General Public License as published by
11 // the Free Software Foundation; either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 // ----------------------------------------------------------------------------
17 
18 #include <config.h>
19 
20 #include "XmlRpcServer.h"
21 #include "XmlRpcServerConnection.h"
22 #include "XmlRpcServerMethod.h"
23 #include "XmlRpcSocket.h"
24 #include "XmlRpcUtil.h"
25 #include "XmlRpcException.h"
26 #include "XmlRpc.h"
27 
28 #include <stdio.h>
29 
30 using namespace XmlRpc;
31 
32 std::string XmlRpc::request_str = "";
33 std::string XmlRpc::client_id = "";
34 
35 // Static data
36 const char XmlRpcServer::METHODNAME_TAG[] = "methodName";
37 const char XmlRpcServer::PARAMS_TAG[] = "params";
38 const char XmlRpcServer::PARAM_TAG[] = "param";
39 
40 const std::string XmlRpcServer::METHODNAME = "methodName";
41 const std::string XmlRpcServer::PARAMS = "params";
42 
43 const std::string XmlRpcServer::FAULTCODE = "faultCode";
44 const std::string XmlRpcServer::FAULTSTRING = "faultString";
45 
46 
47 
XmlRpcServer()48 XmlRpcServer::XmlRpcServer()
49 {
50   _introspectionEnabled = false;
51   _listMethods = 0;
52   _methodHelp = 0;
53 }
54 
55 
~XmlRpcServer()56 XmlRpcServer::~XmlRpcServer()
57 {
58   this->shutdown();
59   _methods.clear();
60   delete _listMethods;
61   delete _methodHelp;
62 }
63 
64 
65 // Add a command to the RPC server
66 void
addMethod(XmlRpcServerMethod * method)67 XmlRpcServer::addMethod(XmlRpcServerMethod* method)
68 {
69   _methods[method->name()] = method;
70 }
71 
72 // Remove a command from the RPC server
73 void
removeMethod(XmlRpcServerMethod * method)74 XmlRpcServer::removeMethod(XmlRpcServerMethod* method)
75 {
76   MethodMap::iterator i = _methods.find(method->name());
77   if (i != _methods.end())
78     _methods.erase(i);
79 }
80 
81 // Remove a command from the RPC server by name
82 void
removeMethod(const std::string & methodName)83 XmlRpcServer::removeMethod(const std::string& methodName)
84 {
85   MethodMap::iterator i = _methods.find(methodName);
86   if (i != _methods.end())
87     _methods.erase(i);
88 }
89 
90 
91 // Look up a method by name
92 XmlRpcServerMethod*
findMethod(const std::string & name) const93 XmlRpcServer::findMethod(const std::string& name) const
94 {
95   MethodMap::const_iterator i = _methods.find(name);
96   if (i == _methods.end())
97     return 0;
98   return i->second;
99 }
100 
101 
102 // Create a socket, bind to the specified port, and
103 // set it in listen mode to make it available for clients.
104 bool
bindAndListen(int port,int backlog)105 XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/)
106 {
107   XmlRpcSocket::Socket fd = XmlRpcSocket::socket();
108   if (XmlRpcSocket::Invalid == fd)
109   {
110     XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
111     return false;
112   }
113 
114   this->setfd(fd);
115 
116   // Don't block on reads/writes
117   if ( ! XmlRpcSocket::setNonBlocking(fd))
118   {
119     this->close();
120     XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
121     return false;
122   }
123 
124   // Allow this port to be re-bound immediately so server re-starts are not delayed
125   if ( ! XmlRpcSocket::setReuseAddr(fd))
126   {
127     this->close();
128     XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set SO_REUSEADDR socket option (%s).", XmlRpcSocket::getErrorMsg().c_str());
129     return false;
130   }
131 
132   // Bind to the specified port on the default interface
133   if ( ! XmlRpcSocket::bind(fd, port))
134   {
135     this->close();
136     XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not bind to specified port (%s).", XmlRpcSocket::getErrorMsg().c_str());
137     return false;
138   }
139 
140   // Set in listening mode
141   if ( ! XmlRpcSocket::listen(fd, backlog))
142   {
143     this->close();
144     XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket in listening mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
145     return false;
146   }
147 
148   XmlRpcUtil::log(2, "XmlRpcServer::bindAndListen: server listening on port %d fd %d", port, fd);
149 
150   // Notify the dispatcher to listen on this source when we are in work()
151   _disp.addSource(this, XmlRpcDispatch::ReadableEvent);
152 
153   return true;
154 }
155 
156 
157 // Get port number that this server is listening on
158 int
getPort(void) const159 XmlRpcServer::getPort(void) const
160 {
161   return XmlRpcSocket::getPort(getfd());
162 }
163 
164 
165 
166 // Process client requests for the specified time (in seconds)
167 void
work(double timeSeconds)168 XmlRpcServer::work(double timeSeconds)
169 {
170   XmlRpcUtil::log(2, "XmlRpcServer::work: waiting for a connection");
171   _disp.work(timeSeconds);
172 }
173 
174 
175 
176 // Handle input on the server socket by accepting the connection
177 // and reading the rpc request.
178 unsigned
handleEvent(unsigned mask)179 XmlRpcServer::handleEvent(unsigned mask)
180 {
181   acceptConnection();
182   return XmlRpcDispatch::ReadableEvent;		// Continue to monitor this fd
183 }
184 
185 
186 // Accept a client connection request and create a connection to
187 // handle method calls from the client.
188 void
acceptConnection()189 XmlRpcServer::acceptConnection()
190 {
191   XmlRpcSocket::Socket s = XmlRpcSocket::accept(this->getfd());
192   XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: socket %d", s);
193   if (XmlRpcSocket::Invalid == s)
194   {
195     //this->close();
196     XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not accept connection (%s).", XmlRpcSocket::getErrorMsg().c_str());
197   }
198   else if ( ! XmlRpcSocket::setNonBlocking(s))
199   {
200     XmlRpcSocket::close(s);
201     XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
202   }
203   else  // Notify the dispatcher to listen for input on this source when we are in work()
204   {
205     XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: creating a connection");
206     XmlRpcServerConnection* c = this->createConnection(s);
207     if (c) this->dispatchConnection(c);
208   }
209 }
210 
211 
212 // Create a new connection object for processing requests from a specific client.
213 XmlRpcServerConnection*
createConnection(XmlRpcSocket::Socket s)214 XmlRpcServer::createConnection(XmlRpcSocket::Socket s)
215 {
216   // Specify that the connection object be deleted when it is closed
217   return new XmlRpcServerConnection(s, this, true);
218 }
219 
220 
221 // Hand off a new connection to a dispatcher
222 void
dispatchConnection(XmlRpcServerConnection * sc)223 XmlRpcServer::dispatchConnection(XmlRpcServerConnection* sc)
224 {
225   _disp.addSource(sc, XmlRpcDispatch::ReadableEvent);
226 }
227 
228 
229 // Remove a connection. Called by the connection when it closes down.
230 void
removeConnection(XmlRpcServerConnection * sc)231 XmlRpcServer::removeConnection(XmlRpcServerConnection* sc)
232 {
233   _disp.removeSource(sc);
234 }
235 
236 
237 // Stop processing client requests
238 void
exit()239 XmlRpcServer::exit()
240 {
241   _disp.exit();
242 }
243 
244 
245 // Close the server socket file descriptor and stop monitoring connections
246 void
shutdown()247 XmlRpcServer::shutdown()
248 {
249   // This closes and destroys all connections as well as closing this socket
250   _disp.clear();
251 }
252 
253 
254 // Introspection support
255 static const std::string LIST_METHODS("system.listMethods");
256 static const std::string METHOD_HELP("system.methodHelp");
257 static const std::string MULTICALL("system.multicall");
258 
259 
260 // List all methods available on a server
261 class ListMethods : public XmlRpcServerMethod
262 {
263 public:
ListMethods(XmlRpcServer * s)264   ListMethods(XmlRpcServer* s) : XmlRpcServerMethod(LIST_METHODS, s) {}
265 
execute(XmlRpcValue & params,XmlRpcValue & result)266   void execute(XmlRpcValue& params, XmlRpcValue& result)
267   {
268     _server->listMethods(result);
269   }
270 
help()271   std::string help() { return std::string("List all methods available on a server as an array of strings"); }
272 };
273 
274 
275 // Retrieve the help string for a named method
276 class MethodHelp : public XmlRpcServerMethod
277 {
278 public:
MethodHelp(XmlRpcServer * s)279   MethodHelp(XmlRpcServer* s) : XmlRpcServerMethod(METHOD_HELP, s) {}
280 
execute(XmlRpcValue & params,XmlRpcValue & result)281   void execute(XmlRpcValue& params, XmlRpcValue& result)
282   {
283     if (params[0].getType() != XmlRpcValue::TypeString)
284       throw XmlRpcException(METHOD_HELP + ": Invalid argument type");
285 
286     XmlRpcServerMethod* m = _server->findMethod(params[0]);
287     if ( ! m)
288       throw XmlRpcException(METHOD_HELP + ": Unknown method name");
289 
290     result = m->help();
291   }
292 
help()293   std::string help() { return std::string("Retrieve the help string for a named method"); }
294 };
295 
296 
297 // Specify whether introspection is enabled or not. Default is enabled.
298 void
enableIntrospection(bool enabled)299 XmlRpcServer::enableIntrospection(bool enabled)
300 {
301   if (_introspectionEnabled == enabled)
302     return;
303 
304   _introspectionEnabled = enabled;
305 
306   if (enabled)
307   {
308     if ( ! _listMethods)
309     {
310       _listMethods = new ListMethods(this);
311       _methodHelp = new MethodHelp(this);
312     } else {
313       addMethod(_listMethods);
314       addMethod(_methodHelp);
315     }
316   }
317   else
318   {
319     removeMethod(LIST_METHODS);
320     removeMethod(METHOD_HELP);
321   }
322 }
323 
324 
325 void
listMethods(XmlRpcValue & result)326 XmlRpcServer::listMethods(XmlRpcValue& result)
327 {
328   int i = 0;
329   result.setSize(int(_methods.size())+1);
330   for (MethodMap::iterator it=_methods.begin(); it != _methods.end(); ++it)
331     result[i++] = it->first;
332 
333   // Multicall support is built into XmlRpcServer::executeRequest
334   result[i] = MULTICALL;
335 }
336 
337 
338 
339 // Parse the request, run the method, generate a response string.
340 std::string
executeRequest(std::string const & request)341 XmlRpcServer::executeRequest(std::string const& request)
342 {
343   XmlRpcValue params, resultValue;
344   std::string methodName = parseRequest(request, params);
345   XmlRpcUtil::log(2, "XmlRpcServer::executeRequest: server calling method '%s'",
346                     methodName.c_str());
347 
348   std::string response;
349   try {
350 
351     if ( ! executeMethod(methodName, params, resultValue) &&
352          ! executeMulticall(methodName, params, resultValue))
353       response = generateFaultResponse(methodName + ": unknown method name");
354     else
355       response = generateResponse(resultValue.toXml());
356 
357   } catch (const XmlRpcException& fault) {
358     XmlRpcUtil::log(2, "XmlRpcServer::executeRequest: fault %s.",
359                     fault.getMessage().c_str());
360     response = generateFaultResponse(fault.getMessage(), fault.getCode());
361   }
362 
363   return response;
364 }
365 
366 // Parse the method name and the argument values from the request.
367 
368 static std::string id_str = "<?clientid=";
369 
370 std::string
parseRequest(std::string const & request,XmlRpcValue & params)371 XmlRpcServer::parseRequest(std::string const& request, XmlRpcValue& params)
372 {
373   std::string methodName;
374 
375   XmlRpc::request_str = request;
376 
377   XmlRpc::client_id = "UNKNOWN";
378   size_t id_ptr = XmlRpc::request_str.find(id_str);
379   if (id_ptr != std::string::npos) {
380     id_ptr += id_str.length();
381     size_t end_id_ptr = XmlRpc::request_str.find("?", id_ptr);
382     if (end_id_ptr != std::string::npos) {
383       XmlRpc::client_id = XmlRpc::request_str.substr(id_ptr, end_id_ptr - id_ptr);
384       if (XmlRpc::client_id[0] == '"') XmlRpc::client_id.erase(0,1);
385       if (XmlRpc::client_id[XmlRpc::client_id.length() -1] == '"')
386         XmlRpc::client_id.erase(XmlRpc::client_id.length() - 1);
387     }
388   }
389 
390   int offset = 0;   // Number of chars parsed from the request
391   bool emptyTag;
392 
393   if (XmlRpcUtil::parseTag(METHODNAME_TAG, request, &offset, methodName) &&
394       XmlRpcUtil::findTag(PARAMS_TAG, request, &offset, &emptyTag) &&
395       ! emptyTag)
396   {
397     int nArgs = 0;
398     while (XmlRpcUtil::nextTagIs(PARAM_TAG, request, &offset, &emptyTag))
399     {
400       if (emptyTag)
401       {
402         params[nArgs++] = XmlRpcValue("");
403       }
404       else
405       {
406         params[nArgs++] = XmlRpcValue(request, &offset);
407         (void) XmlRpcUtil::nextTagIsEnd(PARAM_TAG, request, &offset);
408       }
409     }
410 
411     (void) XmlRpcUtil::nextTagIsEnd(PARAMS_TAG, request, &offset);
412   }
413 
414   return methodName;
415 }
416 
417 // Execute a named method with the specified params.
418 bool
executeMethod(const std::string & methodName,XmlRpcValue & params,XmlRpcValue & result)419 XmlRpcServer::executeMethod(const std::string& methodName,
420                             XmlRpcValue& params,
421                             XmlRpcValue& result)
422 {
423   XmlRpcServerMethod* method = findMethod(methodName);
424 
425   if ( ! method) return false;
426 
427   method->execute(params, result);
428 
429   // Ensure a valid result value
430   if ( ! result.valid())
431       result = std::string();
432 
433   return true;
434 }
435 
436 // Execute multiple calls and return the results in an array.
437 bool
executeMulticall(const std::string & methodName,XmlRpcValue & params,XmlRpcValue & result)438 XmlRpcServer::executeMulticall(const std::string& methodName,
439                                XmlRpcValue& params,
440                                XmlRpcValue& result)
441 {
442   if (methodName != MULTICALL) return false;
443 
444   // There ought to be 1 parameter, an array of structs
445   if (params.size() != 1 || params[0].getType() != XmlRpcValue::TypeArray)
446     throw XmlRpcException(MULTICALL + ": Invalid argument (expected an array)");
447 
448   int nc = params[0].size();
449   result.setSize(nc);
450 
451   for (int i=0; i<nc; ++i) {
452 
453     if ( ! params[0][i].hasMember(METHODNAME) ||
454          ! params[0][i].hasMember(PARAMS)) {
455       result[i][FAULTCODE] = -1;
456       result[i][FAULTSTRING] = MULTICALL +
457               ": Invalid argument (expected a struct with members methodName and params)";
458       continue;
459     }
460 
461     const std::string& methodName = params[0][i][METHODNAME];
462     XmlRpcValue& methodParams = params[0][i][PARAMS];
463 
464     XmlRpcValue resultValue;
465     resultValue.setSize(1);
466     try {
467       if ( ! executeMethod(methodName, methodParams, resultValue[0]) &&
468            ! executeMulticall(methodName, params, resultValue[0]))
469       {
470         result[i][FAULTCODE] = -1;
471         result[i][FAULTSTRING] = methodName + ": unknown method name";
472       }
473       else
474         result[i] = resultValue;
475 
476     } catch (const XmlRpcException& fault) {
477         result[i][FAULTCODE] = fault.getCode();
478         result[i][FAULTSTRING] = fault.getMessage();
479     }
480   }
481 
482   return true;
483 }
484 
485 
486 // Create a response from results xml
487 std::string
generateResponse(std::string const & resultXml)488 XmlRpcServer::generateResponse(std::string const& resultXml)
489 {
490   const char RESPONSE_1[] =
491     "<?xml version=\"1.0\"?>\r\n"
492     "<methodResponse><params><param>\r\n\t";
493   const char RESPONSE_2[] =
494     "\r\n</param></params></methodResponse>\r\n";
495 
496   std::string body = RESPONSE_1 + resultXml + RESPONSE_2;
497   std::string header = generateHeader(body);
498   std::string response = header + body;
499 
500   XmlRpcUtil::log(5, "XmlRpcServer::generateResponse:\n%s\n", response.c_str());
501   return response;
502 }
503 
504 
505 // Prepend http headers
506 std::string
generateHeader(std::string const & body)507 XmlRpcServer::generateHeader(std::string const& body)
508 {
509   std::string header =
510     "HTTP/1.1 200 OK\r\n"
511     "Server: ";
512   header += XMLRPC_VERSION;
513   header += "\r\n"
514     "Content-Type: text/xml\r\n"
515     "Content-length: ";
516 
517   char buffLen[40];
518   sprintf(buffLen,"%d\r\n\r\n", static_cast<int>(body.size()));
519 
520   return header + buffLen;
521 }
522 
523 
524 std::string
generateFaultResponse(std::string const & errorMsg,int errorCode)525 XmlRpcServer::generateFaultResponse(std::string const& errorMsg, int errorCode)
526 {
527   const char RESPONSE_1[] =
528     "<?xml version=\"1.0\"?>\r\n"
529     "<methodResponse><fault>\r\n\t";
530   const char RESPONSE_2[] =
531     "\r\n</fault></methodResponse>\r\n";
532 
533   XmlRpcValue faultStruct;
534   faultStruct[FAULTCODE] = errorCode;
535   faultStruct[FAULTSTRING] = errorMsg;
536   std::string body = RESPONSE_1 + faultStruct.toXml() + RESPONSE_2;
537   std::string header = generateHeader(body);
538 
539   return header + body;
540 }
541 
542