/* 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 .
*/
#include "pch.h"
#include "../util/miniwebserver.h"
#include "../util/web/html.h"
#include "../util/md5.hpp"
#include "db.h"
#include "repl.h"
#include "replpair.h"
#include "instance.h"
#include "security.h"
#include "stats/snapshots.h"
#include "background.h"
#include "commands.h"
#include
#include
#undef assert
#define assert MONGO_assert
namespace mongo {
using namespace mongoutils::html;
extern string bind_ip;
extern const char *replInfo;
bool getInitialSyncCompleted();
time_t started = time(0);
/*
string toString() {
stringstream ss;
unsigned long long dt = last - start;
ss << dt/1000;
ss << '\t';
ss << timeLocked/1000 << '\t';
if( dt )
ss << (timeLocked*100)/dt << '%';
return ss.str();
}
*/
struct Timing {
Timing() {
start = timeLocked = 0;
}
unsigned long long start, timeLocked;
};
bool _bold;
string bold(bool x) {
_bold = x;
return x ? "" : "";
}
string bold() {
return _bold ? "" : "";
}
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)
{}
// caller locks
void doLockedStuff(stringstream& ss) {
ss << "# databases: " << dbHolder.size() << '\n';
if( ClientCursor::byLocSize()>500 )
ss << bold(ClientCursor::byLocSize()>10000) << "Cursors byLoc.size(): " << ClientCursor::byLocSize() << bold() << '\n';
ss << "\nreplication: ";
if( *replInfo )
ss << "\nreplInfo: " << replInfo << "\n\n";
if( replSet ) {
ss << a("", "see replSetGetStatus link top of page") << "--replSet " << cmdLine.replSet << '\n';
}
else {
ss << "\nmaster: " << replSettings.master << '\n';
ss << "slave: " << replSettings.slave << '\n';
if ( replPair ) {
ss << "replpair:\n";
ss << replPair->getInfo();
}
bool seemCaughtUp = getInitialSyncCompleted();
if ( !seemCaughtUp ) ss << "";
ss << "initialSyncCompleted: " << seemCaughtUp;
if ( !seemCaughtUp ) ss << "";
ss << '\n';
}
auto_ptr delta = statsSnapshots.computeDelta();
if ( delta.get() ){
ss << "\ndbtop (occurences|percent of elapsed)\n";
ss << "
";
ss << "
";
ss << a("http://www.mongodb.org/display/DOCS/Developer+FAQ#DeveloperFAQ-What%27sa%22namespace%22%3F", "namespace") <<
"NS
"
"
total
"
"
Reads
"
"
Writes
"
"
Queries
"
"
GetMores
"
"
Inserts
"
"
Updates
"
"
Removes
";
ss << "
\n";
display( ss , (double) delta->elapsed() , "GLOBAL" , delta->globalUsageDiff() );
Top::UsageMap usage = delta->collectionUsageDiff();
for ( Top::UsageMap::iterator i=usage.begin(); i != usage.end(); i++ ){
display( ss , (double) delta->elapsed() , i->first , i->second );
}
ss << "
";
}
statsSnapshots.outputLockInfoHTML( ss );
BackgroundOperation::dump(ss);
}
void display( stringstream& ss , double elapsed , const Top::UsageData& usage ){
ss << "
";
ss << usage.count;
ss << "
";
double per = 100 * ((double)usage.time)/elapsed;
ss << setprecision(2) << fixed << per << "%";
ss << "
";
}
void display( stringstream& ss , double elapsed , const string& ns , const Top::CollectionData& data ){
if ( ns != "GLOBAL" && data.total.count == 0 )
return;
ss << "
" << ns << "
";
display( ss , elapsed , data.total );
display( ss , elapsed , data.readLock );
display( ss , elapsed , data.writeLock );
display( ss , elapsed , data.queries );
display( ss , elapsed , data.getmore );
display( ss , elapsed , data.insert );
display( ss , elapsed , data.update );
display( ss , elapsed , data.remove );
ss << "
\n";
}
void tablecell( stringstream& ss , bool b ){
ss << "
" << (b ? "X" : "") << "
";
}
template< typename T>
void tablecell( stringstream& ss , const T& t ){
ss << "
" << t << "
";
}
void doUnlockedStuff(stringstream& ss) {
/* this is in the header already ss << "port: " << port << '\n'; */
ss << mongodVersion() << '\n';
ss << "git hash: " << gitVersion() << '\n';
ss << "sys info: " << sysInfo() << '\n';
ss << "uptime: " << time(0)-started << " seconds\n";
if ( replAllDead )
ss << "replication replAllDead=" << replAllDead << "\n";
ss << a("", "information on caught assertion exceptions");
ss << "assertions:\n";
for ( int i = 0; i < 4; i++ ) {
if ( lastAssert[i].isSet() ) {
if ( i == 3 ) ss << "uassert";
else if( i == 2 ) ss << "massert";
else if( i == 0 ) ss << "assert";
else if( i == 1 ) ss << "warnassert";
else ss << i;
ss << ' ' << lastAssert[i].toString();
}
}
ss << "\n
";
ss << "
"
<< "
Client
"
<< "
OpId
"
<< "
Active
"
<< "
LockType
"
<< "
Waiting
"
<< "
SecsRunning
"
<< "
Op
"
<< "
NameSpace
"
<< "
Query
"
<< "
client
"
<< "
msg
"
<< "
progress
"
<< "
\n";
{
scoped_lock bl(Client::clientsMutex);
for( set::iterator i = Client::clients.begin(); i != Client::clients.end(); i++ ) {
Client *c = *i;
CurOp& co = *(c->curop());
ss << "
" << c->desc() << "
";
tablecell( ss , co.opNum() );
tablecell( ss , co.active() );
tablecell( ss , co.getLockType() );
tablecell( ss , co.isWaitingForLock() );
if ( co.active() )
tablecell( ss , co.elapsedSeconds() );
else
tablecell( ss , "" );
tablecell( ss , co.getOp() );
tablecell( ss , co.getNS() );
if ( co.haveQuery() )
tablecell( ss , co.query() );
else
tablecell( ss , "" );
tablecell( ss , co.getRemoteString() );
tablecell( ss , co.getMessage() );
tablecell( ss , co.getProgressMeter().toString() );
ss << "
\n";
}
}
ss << "
\n";
}
bool allowed( const char * rq , vector& headers, const SockAddr &from ){
if ( from.isLocalHost() )
return true;
Client::GodScope gs;
if ( db.findOne( "admin.system.users" , BSONObj() , 0 , QueryOption_SlaveOk ).isEmpty() )
return true;
string auth = getHeader( rq , "Authorization" );
if ( auth.size() > 0 && auth.find( "Digest " ) == 0 ){
auth = auth.substr( 7 ) + ", ";
map parms;
pcrecpp::StringPiece input( auth );
string name, val;
pcrecpp::RE re("(\\w+)=\"?(.*?)\"?, ");
while ( re.Consume( &input, &name, &val) ){
parms[name] = val;
}
BSONObj user = db.findOne( "admin.system.users" , BSON( "user" << parms["username"] ) );
if ( ! user.isEmpty() ){
string ha1 = user["pwd"].str();
string ha2 = md5simpledigest( (string)"GET" + ":" + parms["uri"] );
string 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;
r = md5simpledigest( r );
if ( r == 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& headers, // if completely empty, content-type: text/html will be added
const SockAddr &from
)
{
if ( url.size() > 1 ) {
if ( url.find( "/_status" ) == 0 ){
if ( ! allowed( rq , headers, from ) ){
responseCode = 401;
headers.push_back( "Content-Type: text/plain" );
responseMsg = "not allowed\n";
return;
}
headers.push_back( "Content-Type: application/json" );
generateServerStatus( url , responseMsg );
responseCode = 200;
return;
}
if ( ! cmdLine.rest ) {
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" );
return;
}
if( url.find("/_commands") == 0 ) {
if ( ! allowed( rq , headers, from ) ){
responseCode = 401;
headers.push_back( "Content-Type: text/plain" );
responseMsg = "not allowed\n";
return;
}
headers.push_back( "Content-Type: text/html" );
stringstream ss;
ss << "Commands List";
ss << p(a("/", "", "Back"));
ss << p("MongoDB List of Commands\n");
const map *m = Command::commandsByBestName();
ss << "S:slave-only N:no-lock R:read-lock W:write-lock A:admin-only \n";
ss << "
";
ss << "
Command
Attributes
Help
\n";
for( map::const_iterator i = m->begin(); i != m->end(); i++ ) {
i->second->htmlHelp(ss);
}
ss << "