// parameters.cpp /** * Copyright (C) 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include #include "mongo/bson/json.h" #include "mongo/bson/mutable/document.h" #include "mongo/client/replica_set_monitor.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/internal_user_auth.h" #include "mongo/db/commands.h" #include "mongo/db/server_parameters.h" #include "mongo/db/storage/storage_options.h" #include "mongo/logger/parse_log_component_settings.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" using std::string; using std::stringstream; namespace mongo { namespace { void appendParameterNames(stringstream& help) { help << "supported:\n"; const ServerParameter::Map& m = ServerParameterSet::getGlobal()->getMap(); for (ServerParameter::Map::const_iterator i = m.begin(); i != m.end(); ++i) { help << " " << i->first << "\n"; } } } class CmdGet : public Command { public: CmdGet() : Command("getParameter") {} virtual bool slaveOk() const { return true; } virtual bool adminOnly() const { return true; } virtual bool isWriteCommandForConfigServer() const { return false; } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector* out) { ActionSet actions; actions.addAction(ActionType::getParameter); out->push_back(Privilege(ResourcePattern::forClusterResource(), actions)); } virtual void help(stringstream& help) const { help << "get administrative option(s)\nexample:\n"; help << "{ getParameter:1, notablescan:1 }\n"; appendParameterNames(help); help << "{ getParameter:'*' } to get everything\n"; } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { bool all = *cmdObj.firstElement().valuestrsafe() == '*'; int before = result.len(); const ServerParameter::Map& m = ServerParameterSet::getGlobal()->getMap(); for (ServerParameter::Map::const_iterator i = m.begin(); i != m.end(); ++i) { if (all || cmdObj.hasElement(i->first.c_str())) { i->second->append(txn, result, i->second->name()); } } if (before == result.len()) { errmsg = "no option found to get"; return false; } return true; } } cmdGet; class CmdSet : public Command { public: CmdSet() : Command("setParameter") {} virtual bool slaveOk() const { return true; } virtual bool adminOnly() const { return true; } virtual bool isWriteCommandForConfigServer() const { return false; } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector* out) { ActionSet actions; actions.addAction(ActionType::setParameter); out->push_back(Privilege(ResourcePattern::forClusterResource(), actions)); } virtual void help(stringstream& help) const { help << "set administrative option(s)\n"; help << "{ setParameter:1, : }\n"; appendParameterNames(help); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { int numSet = 0; bool found = false; const ServerParameter::Map& parameterMap = ServerParameterSet::getGlobal()->getMap(); // First check that we aren't setting the same parameter twice and that we actually are // setting parameters that we have registered and can change at runtime BSONObjIterator parameterCheckIterator(cmdObj); // We already know that "setParameter" will be the first element in this object, so skip // past that parameterCheckIterator.next(); // Set of all the parameters the user is attempting to change std::map parametersToSet; // Iterate all parameters the user passed in to do the initial validation checks, // including verifying that we are not setting the same parameter twice. while (parameterCheckIterator.more()) { BSONElement parameter = parameterCheckIterator.next(); std::string parameterName = parameter.fieldName(); ServerParameter::Map::const_iterator foundParameter = parameterMap.find(parameterName); // Check to see if this is actually a valid parameter if (foundParameter == parameterMap.end()) { errmsg = str::stream() << "attempted to set unrecognized parameter [" << parameterName << "], use help:true to see options "; return false; } // Make sure we are allowed to change this parameter if (!foundParameter->second->allowedToChangeAtRuntime()) { errmsg = str::stream() << "not allowed to change [" << parameterName << "] at runtime"; return false; } // Make sure we are only setting this parameter once if (parametersToSet.count(parameterName)) { errmsg = str::stream() << "attempted to set parameter [" << parameterName << "] twice in the same setParameter command, " << "once to value: [" << parametersToSet[parameterName].toString(false) << "], and once to value: [" << parameter.toString(false) << "]"; return false; } parametersToSet[parameterName] = parameter; } // Iterate the parameters that we have confirmed we are setting and actually set them. // Not that if setting any one parameter fails, the command will fail, but the user // won't see what has been set and what hasn't. See SERVER-8552. for (std::map::iterator it = parametersToSet.begin(); it != parametersToSet.end(); ++it) { BSONElement parameter = it->second; std::string parameterName = it->first; ServerParameter::Map::const_iterator foundParameter = parameterMap.find(parameterName); if (foundParameter == parameterMap.end()) { errmsg = str::stream() << "Parameter: " << parameterName << " that was " << "avaliable during our first lookup in the registered " << "parameters map is no longer available."; return false; } if (numSet == 0) { foundParameter->second->append(txn, result, "was"); } Status status = foundParameter->second->set(parameter); if (status.isOK()) { numSet++; continue; } errmsg = status.reason(); result.append("code", status.code()); return false; } if (numSet == 0 && !found) { errmsg = "no option found to set, use help:true to see options "; return false; } return true; } } cmdSet; namespace { using logger::globalLogDomain; using logger::LogComponent; using logger::LogComponentSetting; using logger::LogSeverity; using logger::parseLogComponentSettings; class LogLevelSetting : public ServerParameter { public: LogLevelSetting() : ServerParameter(ServerParameterSet::getGlobal(), "logLevel") {} virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) { b << name << globalLogDomain()->getMinimumLogSeverity().toInt(); } virtual Status set(const BSONElement& newValueElement) { int newValue; if (!newValueElement.coerce(&newValue) || newValue < 0) return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for logLevel: " << newValueElement); LogSeverity newSeverity = (newValue > 0) ? LogSeverity::Debug(newValue) : LogSeverity::Log(); globalLogDomain()->setMinimumLoggedSeverity(newSeverity); return Status::OK(); } virtual Status setFromString(const std::string& str) { int newValue; Status status = parseNumberFromString(str, &newValue); if (!status.isOK()) return status; if (newValue < 0) return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for logLevel: " << newValue); LogSeverity newSeverity = (newValue > 0) ? LogSeverity::Debug(newValue) : LogSeverity::Log(); globalLogDomain()->setMinimumLoggedSeverity(newSeverity); return Status::OK(); } } logLevelSetting; /** * Log component verbosity. * Log levels of log component hierarchy. * Negative value for a log component means the default log level will be used. */ class LogComponentVerbositySetting : public ServerParameter { MONGO_DISALLOW_COPYING(LogComponentVerbositySetting); public: LogComponentVerbositySetting() : ServerParameter(ServerParameterSet::getGlobal(), "logComponentVerbosity") {} virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) { BSONObj currentSettings; _get(¤tSettings); b << name << currentSettings; } virtual Status set(const BSONElement& newValueElement) { if (!newValueElement.isABSONObj()) { return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << "log component verbosity is not a BSON object: " << newValueElement); } return _set(newValueElement.Obj()); } virtual Status setFromString(const std::string& str) { try { return _set(mongo::fromjson(str)); } catch (const DBException& ex) { return ex.toStatus(); } } private: /** * Returns current settings as a BSON document. * The "default" log component is an implementation detail. Don't expose this to users. */ void _get(BSONObj* output) const { static const string defaultLogComponentName = LogComponent(LogComponent::kDefault).getShortName(); mutablebson::Document doc; for (int i = 0; i < int(LogComponent::kNumLogComponents); ++i) { LogComponent component = static_cast(i); int severity = -1; if (globalLogDomain()->hasMinimumLogSeverity(component)) { severity = globalLogDomain()->getMinimumLogSeverity(component).toInt(); } // Save LogComponent::kDefault LogSeverity at root if (component == LogComponent::kDefault) { doc.root().appendInt("verbosity", severity); continue; } mutablebson::Element element = doc.makeElementObject(component.getShortName()); element.appendInt("verbosity", severity); mutablebson::Element parentElement = _getParentElement(doc, component); parentElement.pushBack(element); } BSONObj result = doc.getObject(); output->swap(result); invariant(!output->hasField(defaultLogComponentName)); } /** * Updates component hierarchy log levels. * * BSON Format: * { * verbosity: 4, <-- maps to 'default' log component. * componentA: { * verbosity: 2, <-- sets componentA's log level to 2. * componentB: { * verbosity: 1, <-- sets componentA.componentB's log level to 1. * } * componentC: { * verbosity: -1, <-- clears componentA.componentC's log level so that * its final loglevel will be inherited from componentA. * } * }, * componentD : 3 <-- sets componentD's log level to 3 (alternative to * subdocument with 'verbosity' field). * } * * For the default component, the log level is read from the top-level * "verbosity" field. * For non-default components, we look up the element using the component's * dotted name. If the "" field is a number, the log * level will be read from the field's value. * Otherwise, we assume that the "" field is an * object with a "verbosity" field that holds the log level for the component. * The more verbose format with the "verbosity" field is intended to support * setting of log levels of both parent and child log components in the same * BSON document. * * Ignore elements in BSON object that do not map to a log component's dotted * name. */ Status _set(const BSONObj& bsonSettings) const { StatusWith> parseStatus = parseLogComponentSettings(bsonSettings); if (!parseStatus.isOK()) { return parseStatus.getStatus(); } std::vector settings = parseStatus.getValue(); std::vector::iterator it = settings.begin(); for (; it < settings.end(); ++it) { LogComponentSetting newSetting = *it; // Negative value means to clear log level of component. if (newSetting.level < 0) { globalLogDomain()->clearMinimumLoggedSeverity(newSetting.component); continue; } // Convert non-negative value to Log()/Debug(N). LogSeverity newSeverity = (newSetting.level > 0) ? LogSeverity::Debug(newSetting.level) : LogSeverity::Log(); globalLogDomain()->setMinimumLoggedSeverity(newSetting.component, newSeverity); } return Status::OK(); } /** * Search document for element corresponding to log component's parent. */ static mutablebson::Element _getParentElement(mutablebson::Document& doc, LogComponent component) { // Hide LogComponent::kDefault if (component == LogComponent::kDefault) { return doc.end(); } LogComponent parentComponent = component.parent(); // Attach LogComponent::kDefault children to root if (parentComponent == LogComponent::kDefault) { return doc.root(); } mutablebson::Element grandParentElement = _getParentElement(doc, parentComponent); return grandParentElement.findFirstChildNamed(parentComponent.getShortName()); } } logComponentVerbositySetting; } // namespace namespace { class SSLModeSetting : public ServerParameter { public: SSLModeSetting() : ServerParameter(ServerParameterSet::getGlobal(), "sslMode", false, // allowedToChangeAtStartup true // allowedToChangeAtRuntime ) {} std::string sslModeStr() { switch (sslGlobalParams.sslMode.load()) { case SSLParams::SSLMode_disabled: return "disabled"; case SSLParams::SSLMode_allowSSL: return "allowSSL"; case SSLParams::SSLMode_preferSSL: return "preferSSL"; case SSLParams::SSLMode_requireSSL: return "requireSSL"; default: return "undefined"; } } virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) { b << name << sslModeStr(); } virtual Status set(const BSONElement& newValueElement) { try { return setFromString(newValueElement.String()); } catch (MsgAssertionException msg) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for sslMode via setParameter command: " << newValueElement); } } virtual Status setFromString(const std::string& str) { #ifndef MONGO_CONFIG_SSL return Status(ErrorCodes::IllegalOperation, mongoutils::str::stream() << "Unable to set sslMode, SSL support is not compiled into server"); #endif if (str != "disabled" && str != "allowSSL" && str != "preferSSL" && str != "requireSSL") { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for sslMode via setParameter command: " << str); } int oldMode = sslGlobalParams.sslMode.load(); if (str == "preferSSL" && oldMode == SSLParams::SSLMode_allowSSL) { sslGlobalParams.sslMode.store(SSLParams::SSLMode_preferSSL); } else if (str == "requireSSL" && oldMode == SSLParams::SSLMode_preferSSL) { sslGlobalParams.sslMode.store(SSLParams::SSLMode_requireSSL); } else { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Illegal state transition for sslMode, attempt to change from " << sslModeStr() << " to " << str); } return Status::OK(); } } sslModeSetting; class ClusterAuthModeSetting : public ServerParameter { public: ClusterAuthModeSetting() : ServerParameter(ServerParameterSet::getGlobal(), "clusterAuthMode", false, // allowedToChangeAtStartup true // allowedToChangeAtRuntime ) {} std::string clusterAuthModeStr() { switch (serverGlobalParams.clusterAuthMode.load()) { case ServerGlobalParams::ClusterAuthMode_keyFile: return "keyFile"; case ServerGlobalParams::ClusterAuthMode_sendKeyFile: return "sendKeyFile"; case ServerGlobalParams::ClusterAuthMode_sendX509: return "sendX509"; case ServerGlobalParams::ClusterAuthMode_x509: return "x509"; default: return "undefined"; } } virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) { b << name << clusterAuthModeStr(); } virtual Status set(const BSONElement& newValueElement) { try { return setFromString(newValueElement.String()); } catch (MsgAssertionException msg) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for clusterAuthMode via setParameter command: " << newValueElement); } } virtual Status setFromString(const std::string& str) { #ifndef MONGO_CONFIG_SSL return Status(ErrorCodes::IllegalOperation, mongoutils::str::stream() << "Unable to set clusterAuthMode, " << "SSL support is not compiled into server"); #endif if (str != "keyFile" && str != "sendKeyFile" && str != "sendX509" && str != "x509") { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Invalid value for clusterAuthMode via setParameter command: " << str); } int oldMode = serverGlobalParams.clusterAuthMode.load(); int sslMode = sslGlobalParams.sslMode.load(); if (str == "sendX509" && oldMode == ServerGlobalParams::ClusterAuthMode_sendKeyFile) { if (sslMode == SSLParams::SSLMode_disabled || sslMode == SSLParams::SSLMode_allowSSL) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Illegal state transition for clusterAuthMode, " << "need to enable SSL for outgoing connections"); } serverGlobalParams.clusterAuthMode.store(ServerGlobalParams::ClusterAuthMode_sendX509); #ifdef MONGO_CONFIG_SSL setInternalUserAuthParams( BSON(saslCommandMechanismFieldName << "MONGODB-X509" << saslCommandUserDBFieldName << "$external" << saslCommandUserFieldName << getSSLManager()->getSSLConfiguration().clientSubjectName)); #endif } else if (str == "x509" && oldMode == ServerGlobalParams::ClusterAuthMode_sendX509) { serverGlobalParams.clusterAuthMode.store(ServerGlobalParams::ClusterAuthMode_x509); } else { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Illegal state transition for clusterAuthMode, change from " << clusterAuthModeStr() << " to " << str); } return Status::OK(); } } clusterAuthModeSetting; ExportedServerParameter QuietSetting( ServerParameterSet::getGlobal(), "quiet", &serverGlobalParams.quiet); ExportedServerParameter MaxConsecutiveFailedChecksSetting( ServerParameterSet::getGlobal(), "replMonitorMaxFailedChecks", &ReplicaSetMonitor::maxConsecutiveFailedChecks); ExportedServerParameter TraceExceptionsSetting( ServerParameterSet::getGlobal(), "traceExceptions", &DBException::traceExceptions); } }