#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.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/background.h"
#include "mongo/db/commands.h"
#include "mongo/db/db.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/service_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'
auto&& vii = VersionInfoInterface::instance();
ss << "";
ss << mongodVersion(vii) << '\n';
ss << "git hash: " << vii.gitVersion() << '\n';
ss << vii.openSSLVersion("OpenSSL version: ", "\n");
ss << "uptime: " << time(0) - serverGlobalParams.started << " seconds\n";
ss << "
";
}
bool prisort(const Prioritizable* a, const Prioritizable* b) {
return a->priority() < b->priority();
}
void htmlHelp(Command* command, stringstream& ss) {
string helpStr;
{
stringstream h;
command->help(h);
helpStr = h.str();
}
ss << "\n";
if (command->isWebUI())
ss << "getName() << "?text=1\">";
ss << command->getName();
if (command->isWebUI())
ss << "";
ss << " | \n";
ss << "";
ss << "UNUSED ";
if (command->slaveOk())
ss << "S ";
if (command->adminOnly())
ss << "A";
ss << " | ";
ss << "";
if (helpStr != "no help defined") {
const char* p = helpStr.c_str();
while (*p) {
if (*p == '<') {
ss << "<";
p++;
continue;
} else if (*p == '{')
ss << "";
else if (*p == '}') {
ss << "} ";
p++;
continue;
}
if (strncmp(p, "http:", 5) == 0) {
ss << "";
q = p;
if (str::startsWith(q, "http://www.mongodb.org/display/"))
q += 31;
while (*q && *q != ' ' && *q != '\n') {
ss << (*q == '+' ? ' ' : *q);
q++;
if (*q == '#')
while (*q && *q != ' ' && *q != '\n')
q++;
}
ss << "";
p = q;
continue;
}
if (*p == '\n')
ss << " ";
else
ss << *p;
p++;
}
}
ss << " | ";
ss << "
\n";
}
class LogPlugin : public WebStatusPlugin {
public:
LogPlugin() : WebStatusPlugin("Log", 100), _log(0) {
_log = RamLog::get("global");
}
virtual void init() {}
virtual void run(OperationContext* opCtx, stringstream& ss) {
_log->toHTML(ss);
}
RamLog* _log;
};
class FavIconHandler : public DbWebHandler {
public:
FavIconHandler() : DbWebHandler("favicon.ico", 0, false) {}
virtual void handle(OperationContext* opCtx,
const char* rq,
const std::string& url,
BSONObj params,
string& responseMsg,
int& responseCode,
vector& 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* opCtx,
const char* rq,
const std::string& url,
BSONObj params,
string& responseMsg,
int& responseCode,
vector& headers,
const SockAddr& from) {
headers.push_back("Content-Type: application/json;charset=utf-8");
responseCode = 200;
static vector 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(opCtx, "admin.$cmd", co, 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* opCtx,
const char* rq,
const std::string& url,
BSONObj params,
string& responseMsg,
int& responseCode,
vector& 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(
"MongoDB List of "
"Commands"
"\n");
const Command::CommandMap* m = Command::commandsByBestName();
ss << "S:slave-ok R:read-lock W:write-lock A:admin-only
\n";
ss << table();
ss << "Command | Attributes | Help |
\n";
for (Command::CommandMap::const_iterator i = m->begin(); i != m->end(); ++i) {
htmlHelp(i->second, 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& cmdName) const {
Command* cmd = Command::findCommand(cmdName);
if (cmd && cmd->isWebUI()) {
return cmd;
}
return nullptr;
}
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* opCtx,
const char* rq,
const std::string& url,
BSONObj params,
string& responseMsg,
int& responseCode,
vector& 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).setCommandArgs(cmdObj).setMetadata(
rpc::makeEmptyMetadata());
auto cmdRequestMsg = requestBuilder.done();
rpc::CommandRequest cmdRequest{&cmdRequestMsg};
rpc::CommandReplyBuilder cmdReplyBuilder{};
Command::execCommand(opCtx, c, cmdRequest, &cmdReplyBuilder);
auto cmdReplyMsg = cmdReplyBuilder.done();
rpc::CommandReply cmdReply{&cmdReplyMsg};
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, ServiceContext* ctx, AdminAccess* webUsers)
: MiniWebServer("admin web console", ip, port, ctx), _webUsers(webUsers) {
WebStatusPlugin::initAll();
}
void DbWebServer::doRequest(const char* rq,
string url,
string& responseMsg,
int& responseCode,
vector& headers,
const SockAddr& from) {
Client* client = &cc();
auto opCtx = client->makeOperationContext();
if (url.size() > 1) {
if (!_allowed(opCtx.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(
opCtx.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 = "unknown url\n";
return;
}
// generate home page
if (!_allowed(opCtx.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 << "List all commands | \n";
ss << "Replica set status
\n";
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")
<< ": ";
auto m = Command::commandsByBestName();
for (Command::CommandMap::const_iterator i = m->begin(); i != m->end(); ++i) {
if (!i->second->isWebUI())
continue;
stringstream h;
i->second->help(h);
const string help = h.str();
ss << "first << "?text=1\"";
if (help != "no help defined") {
ss << " title=\"" << help << '"';
}
ss << ">" << i->first << " ";
}
ss << '\n';
doUnlockedStuff(ss);
WebStatusPlugin::runAll(opCtx.get(), ss);
ss << "