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