#include "XmlRpc.h" #include "XmlRpcSysMethods.h" #define EMETHOD 100 #define EBADREQUEST 400 #define ENOTFOUND 404 #define ETIMEOUT 408 #define EINTERNAL 500 #define EIMPLEMENTED 501 /** * constructs a new Xmlrpc server. it constructs a new XmlRpc server, clears the list of Methods (including SysMethods). */ XmlRpcServer::XmlRpcServer() { sysMethodsLoaded=sysMethods=false; Clear(); //sslcontext.UseCertificate(String certificate¸ String private_key¸ bool cert_asn1 = false) } /** * clears the method list. this method clears the list of methods provided by the server and reloads the SysMethods, * if they were loaded before. */ void XmlRpcServer::Clear() { sslcontext.Clear(); loadSysMethods(false); methods.Clear(); loadSysMethods(sysMethods); } /** * loads the SysMethods. this method loads or unloads the special system methods provided by the server. * @param load if set the methods will be loaded else they will be unloaded */ void XmlRpcServer::loadSysMethods(bool load) { if(load) { if(sysMethodsLoaded) return; methods.Add() = new XmlRpcSysListMethods("listMethods",&methods); methods.Add() = new XmlRpcSysMethodHelp("methodHelp",&methods); methods.Add() = new XmlRpcSysMethodSignature("methodSignature",&methods); sysMethodsLoaded=true; }else { if(!sysMethodsLoaded) return; for(int i=0;iGetName().Left(7) == "system.") delete methods[i]; methods.Remove(i); i--; } sysMethodsLoaded=false; } } /** * set wether SysMethods should be provided. if called with \p on = true the Sysmethods will be * loaded and even reloaded after clearing the non system messages with Clear(). The systme messages that * are provided at the moment are: system.listMethods, system.methodHelp, system.methodSignature. * @param on if set SystemMethods will be provided by the server */ void XmlRpcServer::SysMethods(bool on) { sysMethods=on; loadSysMethods(on); } /** * add a usermethod to the server. one can add own methods to the server by calling this method. * The provided Method will be called if the name and one of the signatures of an incoming request * match. * Only methods with non empty names that don't start with "system." are allowed! * @param m a pointer to a XmlRpcMethod derived object * @returns reference to the current server * @see XmlRpcMethod */ XmlRpcServer& XmlRpcServer::Add(XmlRpcMethod* m) { if(m->GetName().IsEmpty()) return *this; if(m->GetName().Left(7)=="system.") return *this; methods.Add(m); return *this; } /** * open a socket. open a socket, bind to it and listen. * @param port port to listen at * @param listen_count number of clients in count * @param ssl use ssl encryption, default is no ssl encryption. If ssl should be used but the ssl * context is not set XmlRpcServer trys to load the ConfigFile("cacert.pem") and ConfigFile("pkey.pem") * if this fails the server wont start. * @warning if ssl context is not set the peer won't be veryfied * @returns true if succesfull, false else */ bool XmlRpcServer::Open(int port, int listen_count , bool ssl) { server_port=port; listen=listen_count; use_ssl=ssl; if(ssl) { if(sslcontext.IsEmpty()) { SSL_METHOD *meth = TLSv1_server_method(); sslcontext.Create(meth); sslcontext.VerifyPeer(false); String certfile(ConfigFile("cacert.pem")), keyfile(ConfigFile("pkey.pem")); if(!FileExists(certfile) || !FileExists(keyfile)) { String debug; debug << "unable to find: "; if(!FileExists(certfile)) debug << certfile << " "; if(!FileExists(keyfile)) debug << keyfile; debug <<"\n"; LOG(debug); return false; } String cert(LoadFile(certfile)); String pkey(LoadFile(keyfile)); sslcontext.UseCertificate(cert,pkey); } return SSLServerSocket(socket, sslcontext, server_port, false, listen, true); } else return ServerSocket(socket, server_port, false, listen, true); } /** * close the socket. close the socket */ void XmlRpcServer::Close() { socket.Close(); } /** * close and reopen the socket. close and reopen the socket by calling Close() and Open(). */ bool XmlRpcServer::Reopen() { Close(); return Open(server_port,listen,use_ssl); } /** * wait for connections. wait for one incomming connection return after max \p msec milliseconds * of waiting or after accepting a connection or after an error. * @param msec time to wait for one connection before returning */ void XmlRpcServer::RunOnce(int msec) { if(!socket.IsOpen()) return; Socket s; if(socket.Peek(msec)) { XmlRpcConnectionInfo ci; if(socket.Accept(s,&ci.ipadd,true,0)) { ci.timeout=10000; Thread().Run(THISBACK2(HandleRequest,s,RawToValue(ci))); } else { RLOG(NFormat("accept error: %d %s",socket.GetErrorCode(),socket.GetErrorText())); } } if(socket.IsError()) RLOG(NFormat("socket error: %d %s",socket.GetErrorCode(),socket.GetErrorText())); } /** * wait for connections. wait for incomming connections, accept them and pass them to * HandleRequest() and wait again in an infinite loop. * @see RunOnce() */ void XmlRpcServer::Run() { if(!socket.IsOpen()) return; while(true) { RunOnce(1000); } } /* * handle incomming requests. parse and response to incomming method calls. * @param s the socket the connection is on * @param ipadd the ip address of the calling client * @param value a Value containing ConnectionInfo */ void XmlRpcServer::HandleRequest(Socket &s, Value &value) { XmlRpcConnectionInfo ci = ValueTo(value); String data, response; LOG("\n\ngot a Request"); data = s.Read(ci.timeout); if(data.IsEmpty()) { Error(s,ETIMEOUT,"Request Timeout"); return; } XmlRpcParser p; XmlNode n; Value v; v=p.ParseMethodCall(data); if(v.IsError()) { Error(s,EBADREQUEST,"Bad Request: unable to understand xml method call"); return; } ValueArray params(v); if(params.GetCount()<1 || params[0].GetType() != STRING_V) { Error(s,EBADREQUEST,"Bad Request: unable to understand xml method call"); return; } for(int i=0;iGetName()==params[0]) { CheckSignatureAndRun(s,methods[i],params, ci.ipadd); return; } } if(response.IsEmpty()) Error(s,ENOTFOUND,"Not Found: the requested method was not found"); } /** * check for a signature and run the method. check wether the XmlRpcMethod object has a signature * equal to the one of the call and if so run the method and send the response * @param s socket the connection is on * @param m method that should be run * @param params the methodname and the parameter received within the method call * @param ipadd the ip address of the calling client */ void XmlRpcServer::CheckSignatureAndRun(Socket& s, XmlRpcMethod* m, ValueArray params, dword ipadd) { ValueArray va; m->Signature(va); LOG(NFormat("function: %s",m->GetName())); LOG(NFormat("Signature of function: %s",Value(va).ToString())); LOG(NFormat("Signature of call: %s",Value(params).ToString())); if(va.IsEmpty()) { Error(s,EINTERNAL,"internal server error"); return; } for(int i=0; iExecute(result,params, ipadd); LOG(NFormat("Result: %s",result.ToString())); if(!result.IsError()) { SendResponse(s,result); return; } Error(s,EMETHOD,NFormat("Method Error: %s",GetErrorText(result))); return; } } } Error(s,EBADREQUEST,"Bad Request: wrong method signature"); } /** * create xml response. create xml response. * @param v the value to be send * @return returns the string containing the xml representation of v */ String XmlRpcServer::Response(Value& v) { XmlNode n; XmlNode&xml = n("methodResponse")("params")("param").Add("value"); ValueToXml(xml,v); return AsXML(n,XML_HEADER); } /** * create an error response. create an error response with \p num as error number and \p desc as * description of the error * @param s socket the connection is on * @param num the error number * @param desc the description of the error */ void XmlRpcServer::Error(Socket& s, int num, String desc) { XmlNode n; LOG(NFormat("ERROR: %s (%d)",desc,num)); Value errorcode(num); XmlNode& code=n("methodResponse")("fault")("value")("struct").Add("member"); code.Add("name").Add().CreateText("faultCode"); code.Add("value").Add("int").Add().CreateText(errorcode.ToString()); XmlNode& text=n("methodResponse")("fault")("value")("struct").Add("member"); text.Add("name").Add().CreateText("faultString"); text.Add("value").Add("string").Add().CreateText(desc); SendResponse(s,AsXML(n,XML_HEADER)); } /** * send http response. send a response with a http header and the data in \p response. * @param s socket the connection is on * @param response a string sent as body (should be an xml document) */ void XmlRpcServer::SendResponse(Socket& s, String response) { String out; out <<"HTTP/1.0 200 OK\r\n"; out <<"Server: Ultimate++ XmlRPC Server/any OS\r\n"; out <<"Connection: close\r\n"; out <<"Content-Length: "<