// // Copyright (C) 2005 SIPfoundry Inc. // Licensed by SIPfoundry under the LGPL license. // // Copyright (C) 2005 Pingtel Corp. // Licensed to SIPfoundry under a Contributor Agreement. // // $$ ////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES // APPLICATION INCLUDES #include "os/OsReadLock.h" #include "os/OsWriteLock.h" #include "os/OsSysLog.h" #include "os/OsConfigDb.h" #include "utl/UtlSListIterator.h" #include "utl/UtlHashMapIterator.h" #include "net/XmlRpcDispatch.h" #include "configrpc/ConfigRPC.h" // EXTERNAL FUNCTIONS // EXTERNAL VARIABLES // CONSTANTS const char* MethodName[ConfigRPC_Callback::NumMethods] = { "configurationParameter.version", "configurationParameter.get", "configurationParameter.set", "configurationParameter.delete" }; // STATICS OsRWMutex* ConfigRPC::spDatabaseLock = new OsRWMutex(OsBSem::Q_PRIORITY); UtlHashBag ConfigRPC::sDatabases; bool ConfigRPC::sRegistered = false; /* //////////////////////////// PUBLIC //////////////////////////////////// */ /// Construct an instance to allow RPC access to a database. ConfigRPC::ConfigRPC( const char* dbName ///< dbName known to XMLRPC methods ,const char* versionId ///< version of this database ,const UtlString& dbPath ///< path to persistent store for this db ,ConfigRPC_Callback* callback ///< connection to controlling application ) :UtlString(dbName) ,mVersion(versionId) ,mPath(dbPath) ,mCallback(callback) { assert(dbName && *dbName != '\000'); assert(versionId && *versionId != '\000'); assert(!dbPath.isNull()); assert(callback); OsWriteLock lock(*spDatabaseLock); if ( ! sDatabases.find(this) ) { OsSysLog::add( FAC_KERNEL, PRI_INFO, "ConfigRPC:: register access to db name '%s'", dbName); sDatabases.insert(this); } else { OsSysLog::add( FAC_KERNEL, PRI_CRIT, "ConfigRPC:: duplicate db name '%s'", dbName); } } OsStatus ConfigRPC::load(OsConfigDb& dataset) { OsStatus status = dataset.loadFromFile(mPath); if ( OS_SUCCESS != status ) { OsSysLog::add(FAC_KERNEL, PRI_ERR, "ConfigRPC failed to load '%s' from '%s'", data(), mPath.data() ); } return status; } OsStatus ConfigRPC::store(OsConfigDb& dataset) { OsStatus status = dataset.storeToFile(mPath); if ( OS_SUCCESS != status ) { OsSysLog::add(FAC_KERNEL, PRI_ERR, "ConfigRPC failed to store '%s' to '%s'", data(), mPath.data() ); } return status; } /// Destroy the instance to disconnect access to the database. ConfigRPC::~ConfigRPC() { OsWriteLock lock(*spDatabaseLock); sDatabases.remove(this); } /***************************************************************** * Default ConfigRPC_Callback *****************************************************************/ ConfigRPC_Callback::ConfigRPC_Callback() { } // Access check function XmlRpcMethod::ExecutionStatus ConfigRPC_Callback::accessAllowed( const HttpRequestContext& requestContext, Method method ) const { OsSysLog::add(FAC_KERNEL, PRI_INFO, "ConfigRPC default accessAllowed for %s", MethodName[method] ); return XmlRpcMethod::OK; } /// Invoked after the database has been modified void ConfigRPC_Callback::modified() { // in the abstract base class this is a no-op OsSysLog::add(FAC_KERNEL, PRI_INFO, "ConfigRPC default modified"); } ConfigRPC_Callback::~ConfigRPC_Callback() { } /// Instantiate this to allow configuration from hosts in the same SIP domain ConfigRPC_InDomainCallback::ConfigRPC_InDomainCallback(const UtlString& domain ///< to allow ) :mAllowedDomain(domain) { } /// Access check function XmlRpcMethod::ExecutionStatus ConfigRPC_InDomainCallback::accessAllowed( const HttpRequestContext& requestContext, ConfigRPC_Callback::Method method ) const { XmlRpcMethod::ExecutionStatus isAllowed = ( requestContext.isTrustedPeer(mAllowedDomain) ? XmlRpcMethod::OK : XmlRpcMethod::FAILED ); /* * - XmlRpcMethod::OK if allowed * - XmlRpcMethod::FAILED if not allowed, * - XmlRpcMethod::REQUIRE_AUTHENTICATION if authentication is missing or invalid. */ if (XmlRpcMethod::FAILED == isAllowed) { OsSysLog::add(FAC_KERNEL, PRI_WARNING, "ConfigRPC_InDomainCallback disallowed configuration from untrusted peer" ); } return isAllowed; } /// Implements the XML-RPC method configurationParameter.version /** * Inputs: * string dbName Name of the database. * Outputs: * string version_id The version identifier. * * This method should be called by a configuration application to * confirm that it is using a compatible definition of the database * definition. If the returned version_id does not match what the * configuration application expects, it should not modify the configuration.. */ class ConfigRPC_version : public XmlRpcMethod { public: static XmlRpcMethod* get() { return new ConfigRPC_version(); } protected: virtual bool execute(const HttpRequestContext& requestContext, ///< request context UtlSList& params, ///< request param list void* userData, ///< user data XmlRpcResponse& response, ///< request response ExecutionStatus& status ) { UtlString* dbName = dynamic_cast(params.at(0)); if (dbName && !dbName->isNull()) { OsReadLock lock(*ConfigRPC::spDatabaseLock); ConfigRPC* db = ConfigRPC::find(*dbName); if (db) { status = db->mCallback->accessAllowed(requestContext, ConfigRPC_Callback::Version); if ( XmlRpcMethod::OK == status ) { response.setResponse(&db->mVersion); } else { UtlString faultMsg("Access Denied"); response.setFault(XmlRpcMethod::FAILED, faultMsg.data()); } } else { UtlString faultMsg; faultMsg.append("db lookup failed for '"); faultMsg.append(*dbName); faultMsg.append("'"); response.setFault( XmlRpcResponse::UnregisteredMethod, faultMsg.data()); status = XmlRpcMethod::FAILED; } } else { response.setFault( XmlRpcResponse::EmptyParameterValue ,"'dbname' parameter is missing or invalid type" ); status = XmlRpcMethod::FAILED; } return true; } }; /// Implements the XML-RPC method configurationParameter.get /* * Parameters Type Name Description * Inputs: * string db_name configuration data set name * array * string parameter name of parameter to return * Outputs: * struct * string parameter parameter name (key) * string value parameter value * * Returns the name and value for each parameter in the input array * of parameter names. If any parameter in the set is undefined, a * PARAMETER_UNDEFINED fault is returned. * * To get all the parameters in the database, call this method with just the db_name. * When called with just the db_name, if the dataset is empty (there are no parameters * defined), a DATASET_EMPTY fault is returned. */ class ConfigRPC_get : public XmlRpcMethod { public: static XmlRpcMethod* get() { return new ConfigRPC_get(); } protected: virtual bool execute(const HttpRequestContext& requestContext, ///< request context UtlSList& params, ///< request param list void* userData, ///< user data XmlRpcResponse& response, ///< request response ExecutionStatus& status ) { UtlString* dbName = dynamic_cast(params.at(0)); if (dbName && !dbName->isNull()) { OsReadLock lock(*ConfigRPC::spDatabaseLock); // find the dataset registered with this name ConfigRPC* db = ConfigRPC::find(*dbName); if (db) { // check with the application to see if this request is authorized on this dataset status = db->mCallback->accessAllowed(requestContext, ConfigRPC_Callback::Get); if ( XmlRpcMethod::OK == status ) { // read in the dataset OsConfigDb dataset; OsStatus datasetStatus = db->load(dataset); if ( OS_SUCCESS == datasetStatus ) { // get the list of names that the request is asking for UtlContainable* secondParam = params.at(1); if ( secondParam ) { UtlSList* nameList = dynamic_cast(secondParam); if (nameList) { /* * Iterate over the requested names * - All must be present or the request is an error * - For each name found, add the name and value to the * selectedParams hash to be returned in a success response. */ UtlHashMap selectedParams; UtlSListIterator requestedNames(*nameList); UtlString* requestedName = NULL; bool allNamesFound = true; while ( allNamesFound && (requestedName = dynamic_cast(requestedNames())) ) { UtlString* paramValue = new UtlString(); if ( OS_SUCCESS == dataset.get(*requestedName, *paramValue) ) { UtlString* paramName = new UtlString(*requestedName); // put it into the results selectedParams.insertKeyAndValue(paramName, paramValue); } else { allNamesFound = false; delete paramValue; } } if (allNamesFound) { // all were found - return the name/value pairs response.setResponse(&selectedParams); } else { // at least one name was not found - return an error. UtlString faultMsg; faultMsg.append("parameter name '"); faultMsg.append(*requestedName); faultMsg.append("' not found"); response.setFault(ConfigRPC::nameNotFound, faultMsg.data()); status = XmlRpcMethod::FAILED; } selectedParams.destroyAll(); } else { // The second parameter was not a list response.setFault( ConfigRPC::invalidType ,"namelist parameter is not an array" ); status = XmlRpcMethod::FAILED; } } else // no parameter names specified { // return all names UtlHashMap allParams; UtlString lastKey; OsStatus iterateStatus; UtlString* paramName; UtlString* paramValue; bool notEmpty = false; for ( ( paramName = new UtlString() ,paramValue = new UtlString() ,iterateStatus = dataset.getNext(lastKey, *paramName, *paramValue) ); OS_SUCCESS == iterateStatus; ( lastKey = *paramName ,paramName = new UtlString() ,paramValue = new UtlString() ,iterateStatus = dataset.getNext(lastKey, *paramName, *paramValue) ) ) { notEmpty = true; // got at least one parameter // put it into the result array allParams.insertKeyAndValue(paramName, paramValue); } // on the final iteration these were not used delete paramName; delete paramValue; if (notEmpty) { response.setResponse(&allParams); allParams.destroyAll(); } else { // there is no way to send a well-formed but empty response, // so a 'get all' on an empty dataset returns a fault. UtlString faultMsg; faultMsg.append("dataset '"); faultMsg.append(*dbName); faultMsg.append("' has no parameters"); response.setFault(ConfigRPC::emptyDataset, faultMsg); status = XmlRpcMethod::FAILED; } } } else { UtlString faultMsg("dataset load failed"); response.setFault(ConfigRPC::loadFailed, faultMsg); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg("Access Denied"); response.setFault(XmlRpcMethod::FAILED, faultMsg.data()); } } else { UtlString faultMsg; faultMsg.append("db lookup failed for '"); faultMsg.append(*dbName); faultMsg.append("'"); response.setFault( XmlRpcResponse::UnregisteredMethod, faultMsg.data()); status = XmlRpcMethod::FAILED; } } else { response.setFault( XmlRpcResponse::EmptyParameterValue ,"'dbname' parameter is missing or invalid type" ); status = XmlRpcMethod::FAILED; } return true; } }; /// Implements the XML-RPC method configurationParameter.set method /** * Parameters Type Name Description * Inputs: * string db_name configuration data set name * struct * string parameter parameter name (key) * string value parameter value * ... * Outputs: * integer number of values set * * Sets each 'parameter' / 'value' pair in 'db_name'. Either all * sets are made or none are made. */ class ConfigRPC_set : public XmlRpcMethod { public: static XmlRpcMethod* get() { return new ConfigRPC_set(); } protected: virtual bool execute(const HttpRequestContext& requestContext, ///< request context UtlSList& params, ///< request param list void* userData, ///< user data XmlRpcResponse& response, ///< request response ExecutionStatus& status ) { UtlString* dbName = dynamic_cast(params.at(0)); if (dbName && !dbName->isNull()) { OsReadLock lock(*ConfigRPC::spDatabaseLock); ConfigRPC* db = ConfigRPC::find(*dbName); if (db) { status = db->mCallback->accessAllowed(requestContext, ConfigRPC_Callback::Set); if ( XmlRpcMethod::OK == status ) { // read in the dataset OsConfigDb dataset; OsStatus datasetStatus = db->load(dataset); if ( OS_SUCCESS == datasetStatus ) { // get the list of names that the request is asking for UtlContainable* secondParam = params.at(1); if ( secondParam ) { UtlHashMap* paramList = dynamic_cast(secondParam); if (paramList) { /* * Iterate over the requested name/value pairs */ UtlHashMapIterator params(*paramList); UtlContainable* nextParam = NULL; size_t paramsSet = 0; while ( XmlRpcMethod::OK == status && (nextParam = params()) ) { UtlString* name = dynamic_cast(params.key()); if ( name ) { UtlString* value = dynamic_cast(params.value()); if (value) { dataset.set(*name, *value); paramsSet++; } else { UtlString faultMsg; faultMsg.append("parameter name '"); faultMsg.append(*name); faultMsg.append("' value is not a string"); response.setFault(ConfigRPC::invalidType, faultMsg.data()); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg; faultMsg.append("parameter number "); char paramIndex[10]; sprintf(paramIndex,"%d", paramsSet + 1); faultMsg.append(paramIndex); faultMsg.append(" name is not a string"); response.setFault(ConfigRPC::invalidType, faultMsg.data()); status = XmlRpcMethod::FAILED; } } if ( XmlRpcMethod::OK == status ) { if (OS_SUCCESS == db->store(dataset)) { UtlInt numberSet(paramList->entries()); response.setResponse(&numberSet); } else { response.setFault( ConfigRPC::storeFailed ,"error storing dataset" ); status = XmlRpcMethod::FAILED; } } } else { // The second parameter was not a list response.setFault( ConfigRPC::invalidType ,"second parameter is not a struct" ); status = XmlRpcMethod::FAILED; } } else // no parameter names specified { // No second parameter response.setFault( ConfigRPC::invalidType ,"no second parameter of name/value pairs" ); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg("dataset load failed"); response.setFault(ConfigRPC::loadFailed, faultMsg); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg("Access Denied"); response.setFault(XmlRpcMethod::FAILED, faultMsg.data()); } } else { UtlString faultMsg; faultMsg.append("db lookup failed for '"); faultMsg.append(*dbName); faultMsg.append("'"); response.setFault( XmlRpcResponse::UnregisteredMethod, faultMsg.data()); status = XmlRpcMethod::FAILED; } } else { response.setFault( XmlRpcResponse::EmptyParameterValue ,"'dbname' parameter is missing or invalid type" ); status = XmlRpcMethod::FAILED; } return true; } }; /* Method: configurationParameter.delete Parameters Type Name Description Inputs: string db_name configuration data set name string parameter parameter name (key) Outputs: (none) Removes 'parameter' from 'db_name'. This causes the value of 'parameter' to be undefined. It is not an error to invoke the delete method on an undefined parameter. */ class ConfigRPC_delete : public XmlRpcMethod { public: static XmlRpcMethod* get() { return new ConfigRPC_delete(); } protected: virtual bool execute(const HttpRequestContext& requestContext, ///< request context UtlSList& params, ///< request param list void* userData, ///< user data XmlRpcResponse& response, ///< request response ExecutionStatus& status ) { UtlString* dbName = dynamic_cast(params.at(0)); if (dbName && !dbName->isNull()) { OsReadLock lock(*ConfigRPC::spDatabaseLock); ConfigRPC* db = ConfigRPC::find(*dbName); if (db) { status = db->mCallback->accessAllowed(requestContext, ConfigRPC_Callback::Set); if ( XmlRpcMethod::OK == status ) { // read in the dataset OsConfigDb dataset; OsStatus datasetStatus = db->load(dataset); if ( OS_SUCCESS == datasetStatus ) { // get the list of names that the request is trying to delete UtlContainable* secondParam = params.at(1); if ( secondParam ) { UtlSList* nameList = dynamic_cast(secondParam); if (nameList) { /* * Iterate over the names * - For each name found, delete it from the dataset and count it */ UtlSListIterator deleteNames(*nameList); UtlString* deleteName = NULL; size_t deleted = 0; while ((deleteName = dynamic_cast(deleteNames()))) { if (OS_SUCCESS == dataset.remove(*deleteName)) { deleted++; } } if (OS_SUCCESS == db->store(dataset)) { status = XmlRpcMethod::OK; UtlInt deletedCount(deleted); response.setResponse(&deletedCount); } else { response.setFault( ConfigRPC::storeFailed ,"error storing dataset" ); status = XmlRpcMethod::FAILED; } } else { // The second parameter was not a list response.setFault( ConfigRPC::invalidType ,"namelist parameter is not an array" ); status = XmlRpcMethod::FAILED; } } else // No second parameter { response.setFault( ConfigRPC::invalidType ,"no second parameter list of names to delete" ); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg("dataset load failed"); response.setFault(ConfigRPC::loadFailed, faultMsg); status = XmlRpcMethod::FAILED; } } else { UtlString faultMsg("Access Denied"); response.setFault(XmlRpcMethod::FAILED, faultMsg.data()); } } else { UtlString faultMsg; faultMsg.append("db lookup failed for '"); faultMsg.append(*dbName); faultMsg.append("'"); response.setFault( XmlRpcResponse::UnregisteredMethod, faultMsg.data()); status = XmlRpcMethod::FAILED; } } else { response.setFault( XmlRpcResponse::EmptyParameterValue ,"'dbname' parameter is missing or invalid type" ); status = XmlRpcMethod::FAILED; } return true; } }; // Must be called once to connect the configurationParameter methods void ConfigRPC::registerMethods(XmlRpcDispatch& rpc /* xmlrpc dispatch service to use */) { OsWriteLock lock(*spDatabaseLock); if (!sRegistered) { rpc.addMethod(MethodName[ConfigRPC_Callback::Version], ConfigRPC_version::get, NULL); rpc.addMethod(MethodName[ConfigRPC_Callback::Get], ConfigRPC_get::get, NULL); rpc.addMethod(MethodName[ConfigRPC_Callback::Set], ConfigRPC_set::get, NULL); rpc.addMethod(MethodName[ConfigRPC_Callback::Delete], ConfigRPC_delete::get, NULL); sRegistered = true; } }