diff options
Diffstat (limited to 'db/dbwebserver.cpp')
-rw-r--r-- | db/dbwebserver.cpp | 316 |
1 files changed, 151 insertions, 165 deletions
diff --git a/db/dbwebserver.cpp b/db/dbwebserver.cpp index f17a283..7aa6148 100644 --- a/db/dbwebserver.cpp +++ b/db/dbwebserver.cpp @@ -32,6 +32,7 @@ #include "../util/version.h" #include "../util/ramlog.h" #include <pcrecpp.h> +#include "../util/admin_access.h" #include "dbwebserver.h" #include <boost/date_time/posix_time/posix_time.hpp> #undef assert @@ -52,18 +53,20 @@ namespace mongo { }; bool execCommand( Command * c , - Client& client , int queryOptions , - const char *ns, BSONObj& cmdObj , - BSONObjBuilder& result, + 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) { + DbWebServer(const string& ip, int port, const AdminAccess* webUsers) + : MiniWebServer(ip, port), _webUsers(webUsers) { WebStatusPlugin::initAll(); } private: + const AdminAccess* _webUsers; // not owned here void doUnlockedStuff(stringstream& ss) { /* this is in the header already ss << "port: " << port << '\n'; */ @@ -75,37 +78,35 @@ namespace mongo { ss << "</pre>"; } - private: - bool allowed( const char * rq , vector<string>& headers, const SockAddr &from ) { if ( from.isLocalHost() ) return true; - if ( ! webHaveAdminUsers() ) + if ( ! _webUsers->haveAdminUsers() ) return true; string auth = getHeader( rq , "Authorization" ); - if ( auth.size() > 0 && auth.find( "Digest " ) == 0 ){ + 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) ){ + while ( re.Consume( &input, &name, &val) ) { parms[name] = val; } - BSONObj user = webGetAdminUser( parms["username"] ); - if ( ! user.isEmpty() ){ + BSONObj user = _webUsers->getAdminUser( 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() ){ + if ( parms["nc"].size() && parms["cnonce"].size() && parms["qop"].size() ) { r << ':'; r << parms["nc"]; r << ':'; @@ -116,22 +117,20 @@ namespace mongo { 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\" " - ; - + authHeader + << "WWW-Authenticate: " + << "Digest realm=\"mongo\", " + << "nonce=\"abc\", " + << "algorithm=MD5, qop=\"auth\" " + ; + headers.push_back( authHeader.str() ); return 0; } @@ -144,24 +143,39 @@ namespace mongo { 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 ) + if ( handler ) { + if ( handler->requiresREST( url ) && ! cmdLine.rest ) { _rejectREST( responseMsg , responseCode , headers ); - else - handler->handle( rq , url , responseMsg , responseCode , headers , from ); + } + 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; } } @@ -171,27 +185,27 @@ namespace mongo { _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 ) ){ + if ( ! allowed( rq , headers, from ) ) { responseCode = 401; responseMsg = "not allowed\n"; return; - } + } responseCode = 200; stringstream ss; string dbname; { stringstream z; - z << "mongod " << prettyHostName(); + z << cmdLine.binaryName << ' ' << prettyHostName(); dbname = z.str(); } ss << start(dbname) << h2(dbname); @@ -202,12 +216,18 @@ namespace mongo { { 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 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++ ) { + 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\""; + ss << "<a href=\"/" << i->first << "?text=1\""; if( help != "no help defined" ) ss << " title=\"" << help << '"'; ss << ">" << i->first << "</a> "; @@ -216,69 +236,67 @@ namespace mongo { } } 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"; - */ + /* + 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" ); + 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 ){ + + 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 ) + 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(){ + 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 ){ + void WebStatusPlugin::runAll( stringstream& ss ) { if ( ! _plugins ) return; - - for ( unsigned i=0; i<_plugins->size(); i++ ){ + + for ( unsigned i=0; i<_plugins->size(); i++ ) { WebStatusPlugin * p = (*_plugins)[i]; - ss << "<hr>\n" + ss << "<hr>\n" << "<b>" << p->_name << "</b>"; - + ss << " " << p->_subHeading; ss << "<br>\n"; - + p->run(ss); } @@ -290,29 +308,30 @@ namespace mongo { class LogPlugin : public WebStatusPlugin { public: - LogPlugin() : WebStatusPlugin( "Log" , 100 ), _log(0){ + LogPlugin() : WebStatusPlugin( "Log" , 100 ), _log(0) { } - - virtual void init(){ + + virtual void init() { assert( ! _log ); _log = new RamLog(); Logstream::get().addGlobalTee( _log ); } - virtual void run( stringstream& ss ){ + 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){ + : Prioritizable(priority), _name(name) , _requiresREST(requiresREST) { - { // setup strings + { + // setup strings _defaultUrl = "/"; _defaultUrl += name; @@ -320,8 +339,9 @@ namespace mongo { ss << name << " priority: " << priority << " rest: " << requiresREST; _toString = ss.str(); } - - { // add to handler list + + { + // add to handler list if ( ! _handlers ) _handlers = new vector<DbWebHandler*>(); _handlers->push_back( this ); @@ -329,11 +349,11 @@ namespace mongo { } } - DbWebHandler * DbWebHandler::findHandler( const string& url ){ + DbWebHandler * DbWebHandler::findHandler( const string& url ) { if ( ! _handlers ) return 0; - - for ( unsigned i=0; i<_handlers->size(); i++ ){ + + for ( unsigned i=0; i<_handlers->size(); i++ ) { DbWebHandler * h = (*_handlers)[i]; if ( h->handles( url ) ) return h; @@ -341,76 +361,71 @@ namespace mongo { return 0; } - + vector<DbWebHandler*> * DbWebHandler::_handlers = 0; // --- basic handlers --- class FavIconHandler : public DbWebHandler { public: - FavIconHandler() : DbWebHandler( "favicon.ico" , 0 , false ){} + FavIconHandler() : DbWebHandler( "favicon.ico" , 0 , false ) {} - virtual void handle( const char *rq, string url, + virtual void handle( const char *rq, string url, BSONObj params, string& responseMsg, int& responseCode, - vector<string>& headers, const SockAddr &from ){ + 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, + 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 ){ + vector<string>& headers, const SockAddr &from ) { headers.push_back( "Content-Type: application/json" ); responseCode = 200; - + static vector<string> commands; - if ( commands.size() == 0 ){ + if ( commands.size() == 0 ) { commands.push_back( "serverStatus" ); commands.push_back( "buildinfo" ); } - - BSONObj params; - if ( url.find( "?" ) != string::npos ) { - MiniWebServer::parseParams( params , url.substr( url.find( "?" ) + 1 ) ); - } - + BSONObjBuilder buf(1024); - - for ( unsigned i=0; i<commands.size(); i++ ){ + + 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() ){ + + 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(); } @@ -419,14 +434,14 @@ namespace mongo { class CommandListHandler : public DbWebHandler { public: - CommandListHandler() : DbWebHandler( "_commands" , 1 , true ){} - - virtual void handle( const char *rq, string url, + 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 ){ + 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") ); @@ -435,41 +450,21 @@ namespace mongo { 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++ ) + 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 ) const { - const char * x = url.c_str(); - - if ( x[0] != '/' ){ - // this should never happen - return false; - } - - if ( strchr( x + 1 , '/' ) ) - return false; - - x++; + CommandsHandler() : DbWebHandler( "DUMMY COMMANDS" , 2 , true ) {} - const char * end = strstr( x , "?text" ); - if ( end ){ - text = true; - cmd = string( x , end - x ); - } - else { - text = false; - cmd = string(x); - } - + bool _cmd( const string& url , string& cmd , bool& text, bo params ) const { + cmd = str::after(url, '/'); + text = params["text"].boolean(); return true; } @@ -477,45 +472,43 @@ namespace mongo { 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 { + virtual bool handles( const string& url ) const { string cmd; bool text; - if ( ! _cmd( url , cmd , text ) ) + if ( ! _cmd( url , cmd , text, bo() ) ) return false; - - return _cmd( cmd ); + return _cmd(cmd) != 0; } - - virtual void handle( const char *rq, string url, + + virtual void handle( const char *rq, string url, BSONObj params, string& responseMsg, int& responseCode, - vector<string>& headers, const SockAddr &from ){ - + vector<string>& headers, const SockAddr &from ) { string cmd; bool text = false; - assert( _cmd( url , cmd , text ) ); + 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 ); + + string j = result.done().jsonString(Strict, text ); responseMsg = j; - - if( text ){ + + if( text ) { headers.push_back( "Content-Type: text/plain" ); responseMsg += '\n'; } @@ -524,23 +517,16 @@ namespace mongo { } } - + } commandsHandler; // --- external ---- - string prettyHostName() { - stringstream s; - s << getHostName(); - if( mongo::cmdLine.port != CmdLine::DefaultDBPort ) - s << ':' << mongo::cmdLine.port; - return s.str(); - } - - void webServerThread() { + void webServerThread(const AdminAccess* adminAccess) { + boost::scoped_ptr<const AdminAccess> adminAccessPtr(adminAccess); // adminAccess is owned here Client::initThread("websvr"); const int p = cmdLine.port + 1000; - DbWebServer mini(cmdLine.bind_ip, p); + DbWebServer mini(cmdLine.bind_ip, p, adminAccessPtr.get()); log() << "web admin interface listening on port " << p << endl; mini.initAndListen(); cc().shutdown(); |