Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

615 lines (481 sloc) 20.347 kb
/* dbwebserver.cpp
This is the administrative web page displayed on port 28017.
*/
/**
* Copyright (C) 2008 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 <http://www.gnu.org/licenses/>.
*
* 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 "mongo/db/dbwebserver.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <pcrecpp.h>
#include "mongo/base/init.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/auth/user.h"
#include "mongo/db/background.h"
#include "mongo/db/commands.h"
#include "mongo/db/db.h"
#include "mongo/db/service_context.h"
#include "mongo/db/instance.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/stats/snapshots.h"
#include "mongo/rpc/command_reply.h"
#include "mongo/rpc/command_reply_builder.h"
#include "mongo/rpc/command_request.h"
#include "mongo/rpc/command_request_builder.h"
#include "mongo/rpc/metadata.h"
#include "mongo/util/admin_access.h"
#include "mongo/util/md5.hpp"
#include "mongo/util/mongoutils/html.h"
#include "mongo/util/ramlog.h"
#include "mongo/util/version.h"
namespace mongo {
using std::map;
using std::stringstream;
using std::vector;
using namespace html;
namespace {
void doUnlockedStuff(stringstream& ss) {
// This is in the header already ss << "port: " << port << '\n'
ss << "<pre>";
ss << mongodVersion() << '\n';
ss << "git hash: " << gitVersion() << '\n';
ss << openSSLVersion("OpenSSL version: ", "\n");
ss << "uptime: " << time(0) - serverGlobalParams.started << " seconds\n";
ss << "</pre>";
}
bool prisort(const Prioritizable* a, const Prioritizable* b) {
return a->priority() < b->priority();
}
struct Timing {
Timing() {
start = timeLocked = 0;
}
unsigned long long start, timeLocked;
};
class LogPlugin : public WebStatusPlugin {
public:
LogPlugin() : WebStatusPlugin("Log", 100), _log(0) {
_log = RamLog::get("global");
}
virtual void init() {}
virtual void run(OperationContext* txn, stringstream& ss) {
_log->toHTML(ss);
}
RamLog * _log;
};
class FavIconHandler : public DbWebHandler {
public:
FavIconHandler() : DbWebHandler("favicon.ico", 0, false) {}
virtual void handle(OperationContext* txn,
const char *rq, const std::string& url, BSONObj params,
string& responseMsg, int& responseCode,
vector<string>& headers, const SockAddr &from) {
responseCode = 404;
headers.push_back("Content-Type: text/plain;charset=utf-8");
responseMsg = "no favicon\n";
}
} faviconHandler;
class StatusHandler : public DbWebHandler {
public:
StatusHandler() : DbWebHandler("_status", 1, false) {}
virtual void handle(OperationContext* txn,
const char *rq, const std::string& url, BSONObj params,
string& responseMsg, int& responseCode,
vector<string>& headers, const SockAddr &from) {
headers.push_back("Content-Type: application/json;charset=utf-8");
responseCode = 200;
static vector<string> commands;
if (commands.size() == 0) {
commands.push_back("serverStatus");
commands.push_back("buildinfo");
}
BSONObjBuilder buf(1024);
for (unsigned i = 0; i<commands.size(); i++) {
string cmd = commands[i];
Command * c = Command::findCommand(cmd);
verify(c);
BSONObj co;
{
BSONObjBuilder b;
b.append(cmd, 1);
if (cmd == "serverStatus" && params["repl"].type()) {
b.append("repl", atoi(params["repl"].valuestr()));
}
co = b.obj();
}
string errmsg;
BSONObjBuilder sub;
if (!c->run(txn, "admin.$cmd", co, 0, errmsg, sub))
buf.append(cmd, errmsg);
else
buf.append(cmd, sub.obj());
}
responseMsg = buf.obj().jsonString();
}
} statusHandler;
class CommandListHandler : public DbWebHandler {
public:
CommandListHandler() : DbWebHandler("_commands", 1, true) {}
virtual void handle(OperationContext* txn,
const char *rq, const std::string& url, BSONObj params,
string& responseMsg, int& responseCode,
vector<string>& headers, const SockAddr &from) {
headers.push_back("Content-Type: text/html;charset=utf-8");
responseCode = 200;
stringstream ss;
ss << start("Commands List");
ss << p(a("/", "back", "Home"));
ss << p("<b>MongoDB List of "
"<a href=\"http://dochub.mongodb.org/core/commands\">Commands</a>"
"</b>\n");
const Command::CommandMap* m = Command::commandsByBestName();
ss << "S:slave-ok R:read-lock W:write-lock A:admin-only<br>\n";
ss << table();
ss << "<tr><th>Command</th><th>Attributes</th><th>Help</th></tr>\n";
for (Command::CommandMap::const_iterator i = m->begin(); i != m->end(); ++i) {
i->second->htmlHelp(ss);
}
ss << _table() << _end();
responseMsg = ss.str();
}
} commandListHandler;
class CommandsHandler : public DbWebHandler {
public:
CommandsHandler() : DbWebHandler("DUMMY COMMANDS", 2, true) {}
bool _cmd(const string& url, string& cmd, bool& text, bo params) const {
cmd = str::after(url, '/');
text = params["text"].boolean();
return true;
}
Command * _cmd(const string& cmd) const {
const Command::CommandMap* m = Command::webCommands();
if (!m)
return 0;
Command::CommandMap::const_iterator i = m->find(cmd);
if (i == m->end())
return 0;
return i->second;
}
virtual bool handles(const string& url) const {
string cmd;
bool text;
if (!_cmd(url, cmd, text, bo()))
return false;
return _cmd(cmd) != 0;
}
virtual void handle(OperationContext* txn,
const char *rq, const std::string& url, BSONObj params,
string& responseMsg, int& responseCode,
vector<string>& headers, const SockAddr &from) {
string cmd;
bool text = false;
verify(_cmd(url, cmd, text, params));
Command * c = _cmd(cmd);
verify(c);
BSONObj cmdObj = BSON(cmd << 1);
rpc::CommandRequestBuilder requestBuilder{};
requestBuilder.setDatabase("admin")
.setCommandName(cmd)
.setMetadata(rpc::metadata::empty())
.setCommandArgs(cmdObj);
auto cmdRequestMsg = requestBuilder.done();
rpc::CommandRequest cmdRequest{cmdRequestMsg.get()};
rpc::CommandReplyBuilder cmdReplyBuilder{};
// TODO: remove cmdObj from parameters (SERVER-18236)
Command::execCommand(txn, c, cmdObj, cmdRequest, &cmdReplyBuilder);
auto cmdReplyMsg = cmdReplyBuilder.done();
rpc::CommandReply cmdReply{cmdReplyMsg.get()};
responseCode = 200;
string j = cmdReply.getCommandReply().jsonString(Strict, text);
responseMsg = j;
if (text) {
headers.push_back("Content-Type: text/plain;charset=utf-8");
responseMsg += '\n';
}
else {
headers.push_back("Content-Type: application/json;charset=utf-8");
}
}
} commandsHandler;
MONGO_INITIALIZER(WebStatusLogPlugin)(InitializerContext*) {
if (serverGlobalParams.isHttpInterfaceEnabled) {
new LogPlugin;
}
return Status::OK();
}
} // namespace
DbWebServer::DbWebServer(const string& ip, int port, AdminAccess* webUsers)
: MiniWebServer("admin web console", ip, port),
_webUsers(webUsers) {
WebStatusPlugin::initAll();
}
void DbWebServer::doRequest(const char *rq,
string url,
string& responseMsg,
int& responseCode,
vector<string>& headers,
const SockAddr &from) {
boost::scoped_ptr<OperationContext> txn(getGlobalServiceContext()->newOpCtx());
if (url.size() > 1) {
if (!_allowed(txn.get(), rq, headers, from)) {
responseCode = 401;
headers.push_back("Content-Type: text/plain;charset=utf-8");
responseMsg = "not allowed\n";
return;
}
{
BSONObj params;
const size_t pos = url.find("?");
if (pos != string::npos) {
MiniWebServer::parseParams(params, url.substr(pos + 1));
url = url.substr(0, pos);
}
DbWebHandler * handler = DbWebHandler::findHandler(url);
if (handler) {
if (handler->requiresREST(url) && !serverGlobalParams.rest) {
_rejectREST(responseMsg, responseCode, headers);
}
else {
const string callback = params.getStringField("jsonp");
uassert(13453,
"server not started with --jsonp",
callback.empty() || serverGlobalParams.jsonp);
handler->handle(txn.get(),
rq,
url,
params,
responseMsg,
responseCode,
headers,
from);
if (responseCode == 200 && !callback.empty()) {
responseMsg = callback + '(' + responseMsg + ')';
}
}
return;
}
}
if (!serverGlobalParams.rest) {
_rejectREST(responseMsg, responseCode, headers);
return;
}
responseCode = 404;
headers.push_back("Content-Type: text/html;charset=utf-8");
responseMsg = "<html><body>unknown url</body></html>\n";
return;
}
// generate home page
if (!_allowed(txn.get(), rq, headers, from)) {
responseCode = 401;
headers.push_back("Content-Type: text/plain;charset=utf-8");
responseMsg = "not allowed\n";
return;
}
responseCode = 200;
stringstream ss;
string dbname;
{
stringstream z;
z << serverGlobalParams.binaryName << ' ' << prettyHostName();
dbname = z.str();
}
ss << start(dbname) << h2(dbname);
ss << "<p><a href=\"/_commands\">List all commands</a> | \n";
ss << "<a href=\"/_replSet\">Replica set status</a></p>\n";
{
const Command::CommandMap* m = Command::webCommands();
if (m) {
ss << a("",
"These read-only context-less commands can be executed from the web "
"interface. Results are json format, unless ?text=1 is appended in which "
"case the result is output as text for easier human viewing",
"Commands")
<< ": ";
for (Command::CommandMap::const_iterator i = m->begin(); i != m->end(); ++i) {
stringstream h;
i->second->help(h);
const string help = h.str();
ss << "<a href=\"/" << i->first << "?text=1\"";
if (help != "no help defined") {
ss << " title=\"" << help << '"';
}
ss << ">" << i->first << "</a> ";
}
ss << '\n';
}
}
ss << '\n';
doUnlockedStuff(ss);
WebStatusPlugin::runAll(txn.get(), ss);
ss << "</body></html>\n";
responseMsg = ss.str();
headers.push_back("Content-Type: text/html;charset=utf-8");
}
bool DbWebServer::_allowed(OperationContext* txn,
const char * rq,
vector<string>& headers,
const SockAddr &from) {
AuthorizationSession* authSess = AuthorizationSession::get(cc());
if (!authSess->getAuthorizationManager().isAuthEnabled()) {
return true;
}
if (from.isLocalHost() && !_webUsers->haveAdminUsers(txn)) {
authSess->grantInternalAuthorization();
return true;
}
string auth = getHeader(rq, "Authorization");
if (auth.size() > 0 && auth.find("Digest ") == 0) {
auth = auth.substr(7) + ", ";
map<string, string> parms;
pcrecpp::StringPiece input(auth);
string name, val;
pcrecpp::RE re("(\\w+)=\"?(.*?)\"?,\\s*");
while (re.Consume(&input, &name, &val)) {
parms[name] = val;
}
// Only users in the admin DB are visible by the webserver
UserName userName(parms["username"], "admin");
User* user;
AuthorizationManager& authzManager =
AuthorizationSession::get(cc())->getAuthorizationManager();
Status status = authzManager.acquireUser(txn, userName, &user);
if (!status.isOK()) {
if (status.code() != ErrorCodes::UserNotFound) {
uasserted(17051, status.reason());
}
}
else {
uassert(17090,
"External users don't have a password",
!user->getCredentials().isExternal);
string ha1 = user->getCredentials().password;
authzManager.releaseUser(user);
if (ha1.empty()) {
return false;
}
const string ha2 = md5simpledigest((string)"GET" + ":" + parms["uri"]);
stringstream r;
r << ha1 << ':' << parms["nonce"];
if (parms["nc"].size() && parms["cnonce"].size() && parms["qop"].size()) {
r << ':';
r << parms["nc"];
r << ':';
r << parms["cnonce"];
r << ':';
r << parms["qop"];
}
r << ':';
r << ha2;
const string r1 = md5simpledigest(r.str());
if (r1 == parms["response"]) {
Status status = authSess->addAndAuthorizeUser(txn, userName);
uassertStatusOK(status);
return true;
}
}
}
stringstream authHeader;
authHeader << "WWW-Authenticate: "
<< "Digest realm=\"mongo\", "
<< "nonce=\"abc\", "
<< "algorithm=MD5, qop=\"auth\" ";
headers.push_back(authHeader.str());
return 0;
}
void DbWebServer::_rejectREST(string& responseMsg, int& responseCode, vector<string>& headers) {
responseCode = 403;
stringstream ss;
ss << "REST is not enabled. use --rest to turn on.\n";
ss << "check that port " << _port << " is secured for the network too.\n";
responseMsg = ss.str();
headers.push_back("Content-Type: text/plain;charset=utf-8");
}
// -- status framework ---
WebStatusPlugin::WebStatusPlugin( const string& secionName , double priority , const string& subheader )
: Prioritizable(priority), _name( secionName ) , _subHeading( subheader ) {
if ( ! _plugins )
_plugins = new vector<WebStatusPlugin*>();
_plugins->push_back( this );
}
void WebStatusPlugin::initAll() {
if ( ! _plugins )
return;
sort( _plugins->begin(), _plugins->end() , prisort );
for ( unsigned i=0; i<_plugins->size(); i++ )
(*_plugins)[i]->init();
}
void WebStatusPlugin::runAll(OperationContext* txn, stringstream& ss) {
if ( ! _plugins )
return;
for ( unsigned i=0; i<_plugins->size(); i++ ) {
WebStatusPlugin * p = (*_plugins)[i];
ss << "<hr>\n"
<< "<b>" << p->_name << "</b>";
ss << " " << p->_subHeading;
ss << "<br>\n";
p->run(txn, ss);
}
}
vector<WebStatusPlugin*> * WebStatusPlugin::_plugins = 0;
DbWebHandler::DbWebHandler( const string& name , double priority , bool requiresREST )
: Prioritizable(priority), _name(name) , _requiresREST(requiresREST) {
{
// setup strings
_defaultUrl = "/";
_defaultUrl += name;
stringstream ss;
ss << name << " priority: " << priority << " rest: " << requiresREST;
_toString = ss.str();
}
{
// add to handler list
if ( ! _handlers )
_handlers = new vector<DbWebHandler*>();
_handlers->push_back( this );
sort( _handlers->begin() , _handlers->end() , prisort );
}
}
DbWebHandler * DbWebHandler::findHandler( const string& url ) {
if ( ! _handlers )
return 0;
for ( unsigned i=0; i<_handlers->size(); i++ ) {
DbWebHandler * h = (*_handlers)[i];
if ( h->handles( url ) )
return h;
}
return 0;
}
vector<DbWebHandler*> * DbWebHandler::_handlers = 0;
void webServerListenThread(boost::shared_ptr<DbWebServer> dbWebServer) {
Client::initThread("websvr");
dbWebServer->initAndListen();
}
} // namespace mongo
Jump to Line
Something went wrong with that request. Please try again.