mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 09:32:32 +01:00
544 lines
18 KiB
C++
544 lines
18 KiB
C++
/* 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/>.
|
|
*/
|
|
|
|
#include "pch.h"
|
|
#include "../util/miniwebserver.h"
|
|
#include "../util/mongoutils/html.h"
|
|
#include "../util/md5.hpp"
|
|
#include "db.h"
|
|
#include "instance.h"
|
|
#include "security.h"
|
|
#include "stats/snapshots.h"
|
|
#include "background.h"
|
|
#include "commands.h"
|
|
#include "../util/version.h"
|
|
#include "../util/ramlog.h"
|
|
#include <pcrecpp.h>
|
|
#include "dbwebserver.h"
|
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
|
#undef assert
|
|
#define assert MONGO_assert
|
|
|
|
namespace mongo {
|
|
|
|
using namespace mongoutils::html;
|
|
using namespace bson;
|
|
|
|
time_t started = time(0);
|
|
|
|
struct Timing {
|
|
Timing() {
|
|
start = timeLocked = 0;
|
|
}
|
|
unsigned long long start, timeLocked;
|
|
};
|
|
|
|
bool execCommand( Command * c ,
|
|
Client& client , int queryOptions ,
|
|
const char *ns, BSONObj& cmdObj ,
|
|
BSONObjBuilder& result,
|
|
bool fromRepl );
|
|
|
|
class DbWebServer : public MiniWebServer {
|
|
public:
|
|
DbWebServer(const string& ip, int port) : MiniWebServer(ip, port) {
|
|
WebStatusPlugin::initAll();
|
|
}
|
|
|
|
private:
|
|
|
|
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 << "sys info: " << sysInfo() << '\n';
|
|
ss << "uptime: " << time(0)-started << " seconds\n";
|
|
ss << "</pre>";
|
|
}
|
|
|
|
private:
|
|
|
|
bool allowed( const char * rq , vector<string>& headers, const SockAddr &from ) {
|
|
if ( from.isLocalHost() )
|
|
return true;
|
|
|
|
if ( ! webHaveAdminUsers() )
|
|
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+)=\"?(.*?)\"?, ");
|
|
while ( re.Consume( &input, &name, &val) ){
|
|
parms[name] = val;
|
|
}
|
|
|
|
BSONObj user = webGetAdminUser( parms["username"] );
|
|
if ( ! user.isEmpty() ){
|
|
string ha1 = user["pwd"].str();
|
|
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;
|
|
string r1 = md5simpledigest( r.str() );
|
|
|
|
if ( r1 == parms["response"] )
|
|
return true;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
stringstream authHeader;
|
|
authHeader
|
|
<< "WWW-Authenticate: "
|
|
<< "Digest realm=\"mongo\", "
|
|
<< "nonce=\"abc\", "
|
|
<< "algorithm=MD5, qop=\"auth\" "
|
|
;
|
|
|
|
headers.push_back( authHeader.str() );
|
|
return 0;
|
|
}
|
|
|
|
virtual void doRequest(
|
|
const char *rq, // the full request
|
|
string url,
|
|
// set these and return them:
|
|
string& responseMsg,
|
|
int& responseCode,
|
|
vector<string>& headers, // if completely empty, content-type: text/html will be added
|
|
const SockAddr &from
|
|
)
|
|
{
|
|
if ( url.size() > 1 ) {
|
|
|
|
if ( ! allowed( rq , headers, from ) ) {
|
|
responseCode = 401;
|
|
headers.push_back( "Content-Type: text/plain" );
|
|
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 ) && ! cmdLine.rest ){
|
|
_rejectREST( responseMsg , responseCode , headers );
|
|
}else{
|
|
string callback = params.getStringField("jsonp");
|
|
uassert(13453, "server not started with --jsonp", callback.empty() || cmdLine.jsonp);
|
|
|
|
handler->handle( rq , url , params , responseMsg , responseCode , headers , from );
|
|
|
|
if (responseCode == 200 && !callback.empty()){
|
|
responseMsg = callback + '(' + responseMsg + ')';
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if ( ! cmdLine.rest ) {
|
|
_rejectREST( responseMsg , responseCode , headers );
|
|
return;
|
|
}
|
|
|
|
responseCode = 404;
|
|
headers.push_back( "Content-Type: text/html" );
|
|
responseMsg = "<html><body>unknown url</body></html>\n";
|
|
return;
|
|
}
|
|
|
|
// generate home page
|
|
|
|
if ( ! allowed( rq , headers, from ) ){
|
|
responseCode = 401;
|
|
responseMsg = "not allowed\n";
|
|
return;
|
|
}
|
|
|
|
responseCode = 200;
|
|
stringstream ss;
|
|
string dbname;
|
|
{
|
|
stringstream z;
|
|
z << "mongod " << 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";
|
|
|
|
//ss << "<a href=\"/_status\">_status</a>";
|
|
{
|
|
const map<string, Command*> *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( map<string, Command*>::const_iterator i = m->begin(); i != m->end(); i++ ) {
|
|
stringstream h;
|
|
i->second->help(h);
|
|
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';
|
|
/*
|
|
ss << "HTTP <a "
|
|
"title=\"click for documentation on this http interface\""
|
|
"href=\"http://www.mongodb.org/display/DOCS/Http+Interface\">admin port</a>:" << _port << "<p>\n";
|
|
*/
|
|
|
|
doUnlockedStuff(ss);
|
|
|
|
WebStatusPlugin::runAll( ss );
|
|
|
|
ss << "</body></html>\n";
|
|
responseMsg = ss.str();
|
|
|
|
|
|
}
|
|
|
|
void _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" );
|
|
}
|
|
|
|
};
|
|
// ---
|
|
|
|
bool prisort( const Prioritizable * a , const Prioritizable * b ){
|
|
return a->priority() < b->priority();
|
|
}
|
|
|
|
// -- 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( 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(ss);
|
|
}
|
|
|
|
}
|
|
|
|
vector<WebStatusPlugin*> * WebStatusPlugin::_plugins = 0;
|
|
|
|
// -- basic statuc plugins --
|
|
|
|
class LogPlugin : public WebStatusPlugin {
|
|
public:
|
|
LogPlugin() : WebStatusPlugin( "Log" , 100 ), _log(0){
|
|
}
|
|
|
|
virtual void init(){
|
|
assert( ! _log );
|
|
_log = new RamLog();
|
|
Logstream::get().addGlobalTee( _log );
|
|
}
|
|
|
|
virtual void run( stringstream& ss ){
|
|
_log->toHTML( ss );
|
|
}
|
|
RamLog * _log;
|
|
};
|
|
|
|
LogPlugin * logPlugin = new LogPlugin();
|
|
|
|
// -- handler framework ---
|
|
|
|
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;
|
|
|
|
// --- basic handlers ---
|
|
|
|
class FavIconHandler : public DbWebHandler {
|
|
public:
|
|
FavIconHandler() : DbWebHandler( "favicon.ico" , 0 , false ){}
|
|
|
|
virtual void handle( const char *rq, string url, BSONObj params,
|
|
string& responseMsg, int& responseCode,
|
|
vector<string>& headers, const SockAddr &from ){
|
|
responseCode = 404;
|
|
headers.push_back( "Content-Type: text/plain" );
|
|
responseMsg = "no favicon\n";
|
|
}
|
|
|
|
} faviconHandler;
|
|
|
|
class StatusHandler : public DbWebHandler {
|
|
public:
|
|
StatusHandler() : DbWebHandler( "_status" , 1 , false ){}
|
|
|
|
virtual void handle( const char *rq, string url, BSONObj params,
|
|
string& responseMsg, int& responseCode,
|
|
vector<string>& headers, const SockAddr &from ){
|
|
headers.push_back( "Content-Type: application/json" );
|
|
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 );
|
|
assert( c );
|
|
assert( c->locktype() == 0 );
|
|
|
|
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( "admin.$cmd" , co , errmsg , sub , false ) )
|
|
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( const char *rq, string url, BSONObj params,
|
|
string& responseMsg, int& responseCode,
|
|
vector<string>& headers, const SockAddr &from ){
|
|
headers.push_back( "Content-Type: text/html" );
|
|
responseCode = 200;
|
|
|
|
stringstream ss;
|
|
ss << start("Commands List");
|
|
ss << p( a("/", "back", "Home") );
|
|
ss << p( "<b>MongoDB List of <a href=\"http://www.mongodb.org/display/DOCS/Commands\">Commands</a></b>\n" );
|
|
const map<string, Command*> *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( map<string, Command*>::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 map<string,Command*> *m = Command::webCommands();
|
|
if( ! m )
|
|
return 0;
|
|
|
|
map<string,Command*>::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( const char *rq, string url, BSONObj params,
|
|
string& responseMsg, int& responseCode,
|
|
vector<string>& headers, const SockAddr &from ) {
|
|
string cmd;
|
|
bool text = false;
|
|
assert( _cmd( url , cmd , text, params ) );
|
|
Command * c = _cmd( cmd );
|
|
assert( c );
|
|
|
|
BSONObj cmdObj = BSON( cmd << 1 );
|
|
Client& client = cc();
|
|
|
|
BSONObjBuilder result;
|
|
execCommand(c, client, 0, "admin.", cmdObj , result, false);
|
|
|
|
responseCode = 200;
|
|
|
|
string j = result.done().jsonString(JS, text );
|
|
responseMsg = j;
|
|
|
|
if( text ){
|
|
headers.push_back( "Content-Type: text/plain" );
|
|
responseMsg += '\n';
|
|
}
|
|
else {
|
|
headers.push_back( "Content-Type: application/json" );
|
|
}
|
|
|
|
}
|
|
|
|
} commandsHandler;
|
|
|
|
// --- external ----
|
|
|
|
string prettyHostName() {
|
|
stringstream s;
|
|
s << getHostName();
|
|
if( mongo::cmdLine.port != CmdLine::DefaultDBPort )
|
|
s << ':' << mongo::cmdLine.port;
|
|
return s.str();
|
|
}
|
|
|
|
void webServerThread() {
|
|
Client::initThread("websvr");
|
|
const int p = cmdLine.port + 1000;
|
|
DbWebServer mini(cmdLine.bind_ip, p);
|
|
log() << "web admin interface listening on port " << p << endl;
|
|
mini.initAndListen();
|
|
cc().shutdown();
|
|
}
|
|
|
|
} // namespace mongo
|