2009-10-14 16:29:32 -04:00
/* dbwebserver.cpp
This is the administrative web page displayed on port 28017.
*/
2008-11-29 20:01:58 -05:00
/**
* Copyright ( C ) 2008 10 gen Inc .
2008-12-28 20:28:49 -05:00
*
2008-11-29 20:01:58 -05:00
* 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 .
2008-12-28 20:28:49 -05:00
*
2008-11-29 20:01:58 -05:00
* 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 .
2008-12-28 20:28:49 -05:00
*
2008-11-29 20:01:58 -05:00
* 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/>.
*/
2010-04-27 15:27:52 -04:00
# include "pch.h"
2008-11-29 20:01:58 -05:00
# include "../util/miniwebserver.h"
2010-05-07 16:42:55 -04:00
# include "../util/mongoutils/html.h"
2009-01-30 15:06:33 -05:00
# include "../util/md5.hpp"
2008-11-29 20:01:58 -05:00
# include "db.h"
2009-01-02 16:41:13 -05:00
# include "instance.h"
2009-01-20 11:05:53 -05:00
# include "security.h"
2010-02-02 17:16:25 -05:00
# include "stats/snapshots.h"
2010-02-10 15:34:41 -05:00
# include "background.h"
2010-03-18 17:11:02 -04:00
# include "commands.h"
2010-05-28 12:08:36 -04:00
# include "../util/version.h"
2010-07-17 12:38:48 -04:00
# include "../util/ramlog.h"
2009-01-30 15:06:33 -05:00
# include <pcrecpp.h>
2010-07-27 10:55:48 -04:00
# include "dbwebserver.h"
2009-07-24 10:53:46 -04:00
# include <boost/date_time/posix_time/posix_time.hpp>
2009-10-15 16:47:35 -04:00
# undef assert
2010-04-23 20:09:32 -04:00
# define assert MONGO_assert
2009-07-24 10:53:46 -04:00
2009-01-14 17:09:51 -05:00
namespace mongo {
2010-04-27 12:51:49 -04:00
using namespace mongoutils : : html ;
2010-05-05 14:00:25 -04:00
using namespace bson ;
2010-04-27 12:51:49 -04:00
2009-01-15 10:17:11 -05:00
time_t started = time ( 0 ) ;
struct Timing {
Timing ( ) {
start = timeLocked = 0 ;
2008-11-30 21:00:54 -05:00
}
2009-01-15 10:17:11 -05:00
unsigned long long start , timeLocked ;
} ;
2008-11-30 21:00:54 -05:00
2010-04-19 21:05:46 -04:00
bool execCommand ( Command * c ,
Client & client , int queryOptions ,
const char * ns , BSONObj & cmdObj ,
BSONObjBuilder & result ,
bool fromRepl ) ;
2009-01-15 10:17:11 -05:00
class DbWebServer : public MiniWebServer {
public :
2010-07-28 12:26:38 -04:00
DbWebServer ( const string & ip , int port ) : MiniWebServer ( ip , port ) {
WebStatusPlugin : : initAll ( ) ;
2010-07-17 12:38:48 -04:00
}
2010-04-05 08:57:44 -04:00
2010-05-06 15:38:58 -04:00
private :
2009-01-14 17:17:24 -05:00
2009-01-15 10:17:11 -05:00
void doUnlockedStuff ( stringstream & ss ) {
2009-03-25 17:30:30 -04:00
/* this is in the header already ss << "port: " << port << '\n'; */
2010-07-28 12:51:26 -04:00
ss < < " <pre> " ;
2010-04-20 12:29:00 -04:00
ss < < mongodVersion ( ) < < ' \n ' ;
ss < < " git hash: " < < gitVersion ( ) < < ' \n ' ;
ss < < " sys info: " < < sysInfo ( ) < < ' \n ' ;
2010-04-23 15:50:49 -04:00
ss < < " uptime: " < < time ( 0 ) - started < < " seconds \n " ;
2010-07-28 12:51:26 -04:00
ss < < " </pre> " ;
2009-01-15 10:17:11 -05:00
}
2010-05-03 13:33:49 -04:00
2010-07-28 11:09:38 -04:00
private :
2010-05-06 15:38:58 -04:00
bool allowed ( const char * rq , vector < string > & headers , const SockAddr & from ) {
2010-04-02 13:01:32 -04:00
if ( from . isLocalHost ( ) )
2009-05-13 12:28:59 -04:00
return true ;
2010-05-06 15:38:58 -04:00
2010-07-28 11:09:38 -04:00
if ( ! webHaveAdminUsers ( ) )
return true ;
2010-03-26 16:33:12 -04:00
2009-01-30 15:06:33 -05:00
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 ;
}
2010-07-28 11:09:38 -04:00
BSONObj user = webGetAdminUser ( parms [ " username " ] ) ;
2009-01-30 15:06:33 -05:00
if ( ! user . isEmpty ( ) ) {
string ha1 = user [ " pwd " ] . str ( ) ;
string ha2 = md5simpledigest ( ( string ) " GET " + " : " + parms [ " uri " ] ) ;
2010-05-06 15:46:11 -04:00
stringstream r ;
r < < ha1 < < ' : ' < < parms [ " nonce " ] ;
2009-01-30 15:06:33 -05:00
if ( parms [ " nc " ] . size ( ) & & parms [ " cnonce " ] . size ( ) & & parms [ " qop " ] . size ( ) ) {
2010-05-06 15:46:11 -04:00
r < < ' : ' ;
r < < parms [ " nc " ] ;
r < < ' : ' ;
r < < parms [ " cnonce " ] ;
r < < ' : ' ;
r < < parms [ " qop " ] ;
2009-01-30 15:06:33 -05:00
}
2010-05-06 15:46:11 -04:00
r < < ' : ' ;
r < < ha2 ;
string r1 = md5simpledigest ( r . str ( ) ) ;
2009-01-30 15:06:33 -05:00
2010-05-06 15:46:11 -04:00
if ( r1 = = parms [ " response " ] )
2009-01-30 15:06:33 -05:00
return true ;
}
}
stringstream authHeader ;
authHeader
< < " WWW-Authenticate: "
< < " Digest realm= \" mongo \" , "
< < " nonce= \" abc \" , "
< < " algorithm=MD5, qop= \" auth \" "
;
headers . push_back ( authHeader . str ( ) ) ;
return 0 ;
}
2008-12-04 14:33:18 -05:00
2009-01-15 10:17:11 -05:00
virtual void doRequest (
const char * rq , // the full request
string url ,
// set these and return them:
string & responseMsg ,
int & responseCode ,
2009-05-13 12:28:59 -04:00
vector < string > & headers , // if completely empty, content-type: text/html will be added
const SockAddr & from
2009-01-15 10:17:11 -05:00
)
2008-12-04 14:33:18 -05:00
{
2009-01-15 10:17:11 -05:00
if ( url . size ( ) > 1 ) {
2010-07-27 11:47:48 -04:00
if ( ! allowed ( rq , headers , from ) ) {
responseCode = 401 ;
headers . push_back ( " Content-Type: text/plain " ) ;
responseMsg = " not allowed \n " ;
return ;
}
2010-05-06 15:38:58 -04:00
2010-07-27 10:55:48 -04:00
{
DbWebHandler * handler = DbWebHandler : : findHandler ( url ) ;
if ( handler ) {
if ( handler - > requiresREST ( url ) & & ! cmdLine . rest )
_rejectREST ( responseMsg , responseCode , headers ) ;
else
handler - > handle ( rq , url , responseMsg , responseCode , headers , from ) ;
return ;
}
}
2010-05-06 15:38:58 -04:00
2010-04-19 22:24:37 -04:00
if ( ! cmdLine . rest ) {
2010-07-27 10:55:48 -04:00
_rejectREST ( responseMsg , responseCode , headers ) ;
2010-04-19 22:24:37 -04:00
return ;
}
2010-07-28 10:13:56 -04:00
responseCode = 404 ;
headers . push_back ( " Content-Type: text/html " ) ;
responseMsg = " <html><body>unknown url</body></html> \n " ;
2009-01-15 10:17:11 -05:00
return ;
}
2010-07-28 12:26:38 -04:00
// generate home page
if ( ! allowed ( rq , headers , from ) ) {
responseCode = 401 ;
responseMsg = " not allowed \n " ;
return ;
}
2008-11-29 20:01:58 -05:00
2009-01-15 10:17:11 -05:00
responseCode = 200 ;
stringstream ss ;
string dbname ;
{
stringstream z ;
2010-07-27 12:11:04 -04:00
z < < " mongod " < < prettyHostName ( ) ;
2009-01-15 10:17:11 -05:00
dbname = z . str ( ) ;
2008-11-29 20:01:58 -05:00
}
2010-05-06 10:05:48 -04:00
ss < < start ( dbname ) < < h2 ( dbname ) ;
2010-07-30 11:08:30 -04:00
ss < < " <p><a href= \" /_commands \" >List all commands</a> | \n " ;
ss < < " <a href= \" /_replSet \" >Replica set status</a></p> \n " ;
2010-07-28 12:51:26 -04:00
2010-04-19 21:05:46 -04:00
//ss << "<a href=\"/_status\">_status</a>";
{
const map < string , Command * > * m = Command : : webCommands ( ) ;
if ( m ) {
2010-05-07 22:39:33 -04:00
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 " ) < < " : " ;
2010-04-19 21:05:46 -04:00
for ( map < string , Command * > : : const_iterator i = m - > begin ( ) ; i ! = m - > end ( ) ; i + + ) {
2010-04-23 15:50:49 -04:00
stringstream h ;
i - > second - > help ( h ) ;
string help = h . str ( ) ;
ss < < " <a href= \" / " < < i - > first < < " ?text \" " ;
if ( help ! = " no help defined " )
ss < < " title= \" " < < help < < ' " ' ;
ss < < " > " < < i - > first < < " </a> " ;
2010-04-19 21:05:46 -04:00
}
2010-04-20 12:29:00 -04:00
ss < < ' \n ' ;
2010-04-19 21:05:46 -04:00
}
}
2010-04-20 12:29:00 -04:00
ss < < ' \n ' ;
2010-07-30 11:08:30 -04:00
/*
2010-04-27 12:51:49 -04:00
ss < < " HTTP <a "
2010-04-22 18:43:37 -04:00
" title= \" click for documentation on this http interface \" "
2010-07-30 11:08:30 -04:00
" href= \" http://www.mongodb.org/display/DOCS/Http+Interface \" >admin port</a>: " < < _port < < " <p> \n " ;
*/
2010-04-23 16:55:31 -04:00
2009-01-15 10:17:11 -05:00
doUnlockedStuff ( ss ) ;
2010-07-28 12:26:38 -04:00
WebStatusPlugin : : runAll ( ss ) ;
ss < < " </body></html> \n " ;
2009-01-15 10:17:11 -05:00
responseMsg = ss . str ( ) ;
2009-05-13 11:58:24 -04:00
2010-07-28 12:26:38 -04:00
2008-11-29 20:01:58 -05:00
}
2010-07-27 10:55:48 -04:00
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 " ) ;
}
2009-01-14 17:17:24 -05:00
2009-01-15 10:17:11 -05:00
} ;
2010-07-28 12:26:38 -04:00
// ---
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 ;
2010-07-28 13:29:36 -04:00
} ;
LogPlugin * logPlugin = new LogPlugin ( ) ;
2008-11-29 20:01:58 -05:00
2010-07-27 10:55:48 -04:00
// -- handler framework ---
DbWebHandler : : DbWebHandler ( const string & name , double priority , bool requiresREST )
2010-07-28 12:26:38 -04:00
: Prioritizable ( priority ) , _name ( name ) , _requiresREST ( requiresREST ) {
2010-07-27 10:55:48 -04:00
{ // 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 ) ;
2010-07-28 12:26:38 -04:00
sort ( _handlers - > begin ( ) , _handlers - > end ( ) , prisort ) ;
2010-07-27 10:55:48 -04:00
}
}
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 ,
string & responseMsg , int & responseCode ,
vector < string > & headers , const SockAddr & from ) {
responseCode = 404 ;
headers . push_back ( " Content-Type: text/plain " ) ;
responseMsg = " no favicon \n " ;
}
} faviconHandler ;
2010-07-27 11:47:48 -04:00
class StatusHandler : public DbWebHandler {
public :
StatusHandler ( ) : DbWebHandler ( " _status " , 1 , false ) { }
virtual void handle ( const char * rq , string url ,
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 " ) ;
}
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 + + ) {
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 ;
2010-07-28 10:04:22 -04:00
class CommandListHandler : public DbWebHandler {
2010-07-27 11:47:48 -04:00
public :
2010-07-28 10:04:22 -04:00
CommandListHandler ( ) : DbWebHandler ( " _commands " , 1 , true ) { }
2010-07-27 11:47:48 -04:00
virtual void handle ( const char * rq , string url ,
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-only N:no-lock 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 ( ) ;
}
2010-07-28 10:04:22 -04:00
} 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 + + ;
const char * end = strstr ( x , " ?text " ) ;
if ( end ) {
text = true ;
cmd = string ( x , end - x ) ;
}
else {
text = false ;
cmd = string ( x ) ;
}
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 ) )
return false ;
return _cmd ( cmd ) ;
}
virtual void handle ( const char * rq , string url ,
string & responseMsg , int & responseCode ,
vector < string > & headers , const SockAddr & from ) {
string cmd ;
2010-07-28 17:26:09 -04:00
bool text = false ;
2010-07-28 10:04:22 -04:00
assert ( _cmd ( url , cmd , text ) ) ;
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 " ) ;
}
}
2010-07-27 11:47:48 -04:00
} commandsHandler ;
2010-07-27 10:55:48 -04:00
// --- external ----
2010-07-27 12:11:04 -04:00
string prettyHostName ( ) {
stringstream s ;
s < < getHostName ( ) ;
if ( mongo : : cmdLine . port ! = CmdLine : : DefaultDBPort )
s < < ' : ' < < mongo : : cmdLine . port ;
return s . str ( ) ;
}
2009-01-15 10:17:11 -05:00
void webServerThread ( ) {
2009-10-16 15:36:34 -04:00
Client : : initThread ( " websvr " ) ;
2010-04-05 08:57:44 -04:00
const int p = cmdLine . port + 1000 ;
2010-07-27 09:59:34 -04:00
DbWebServer mini ( cmdLine . bind_ip , p ) ;
2010-04-05 10:51:33 -04:00
log ( ) < < " web admin interface listening on port " < < p < < endl ;
mini . initAndListen ( ) ;
2009-10-16 15:36:34 -04:00
cc ( ) . shutdown ( ) ;
2009-01-15 10:17:11 -05:00
}
2009-01-14 17:09:51 -05:00
} // namespace mongo