#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_name.h"
#include "mongo/db/auth/user.h"
#include "mongo/db/background.h"
#include "mongo/db/commands.h"
#include "mongo/db/db.h"
#include "mongo/db/instance.h"
#include "mongo/db/stats/snapshots.h"
#include "mongo/util/admin_access.h"
#include "mongo/util/md5.hpp"
#include "mongo/util/mongoutils/html.h"
#include "mongo/util/net/miniwebserver.h"
#include "mongo/util/ramlog.h"
#include "mongo/util/version.h"
namespace mongo {
using namespace mongoutils::html;
using namespace bson;
struct Timing {
Timing() {
start = timeLocked = 0;
}
unsigned long long start, timeLocked;
};
class DbWebServer : public MiniWebServer {
public:
DbWebServer(const string& ip, int port, const AdminAccess* webUsers)
: MiniWebServer("admin web console", 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'; */
ss << "";
ss << mongodVersion() << '\n';
ss << "git hash: " << gitVersion() << '\n';
ss << openSSLVersion("OpenSSL version: ", "\n");
ss << "sys info: " << sysInfo() << '\n';
ss << "uptime: " << time(0)-serverGlobalParams.started << " seconds\n";
ss << "
";
}
void _authorizePrincipal(const UserName& userName) {
Status status = cc().getAuthorizationSession()->addAndAuthorizeUser(userName);
uassertStatusOK(status);
}
bool allowed( const char * rq , vector& headers, const SockAddr &from ) {
if ( from.isLocalHost() || !_webUsers->haveAdminUsers() ) {
// TODO(spencer): should the above check use "&&" not "||"? Currently this is much
// more permissive than the server's localhost auth bypass.
cc().getAuthorizationSession()->grantInternalAuthorization();
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+)=\"?(.*?)\"?,\\s*");
while ( re.Consume( &input, &name, &val) ) {
parms[name] = val;
}
// Only users in the admin DB are visible by the webserver
UserName userName(parms["username"], "admin");
User* user;
AuthorizationManager& authzManager =
cc().getAuthorizationSession()->getAuthorizationManager();
Status status = authzManager.acquireUser(userName, &user);
if (!status.isOK()) {
if (status.code() != ErrorCodes::UserNotFound) {
uasserted(17051, status.reason());
}
} else {
uassert(17090,
"External users don't have a password",
!user->getCredentials().isExternal);
string ha1 = user->getCredentials().password;
authzManager.releaseUser(user);
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"] ) {
_authorizePrincipal(userName);
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 ( ! allowed( 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 {
string callback = params.getStringField("jsonp");
uassert(13453, "server not started with --jsonp",
callback.empty() || serverGlobalParams.jsonp);
handler->handle( 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( 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 << "_status";
{
const map *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::const_iterator i = m->begin(); i != m->end(); i++ ) {
stringstream h;
i->second->help(h);
string help = h.str();
ss << "first << "?text=1\"";
if( help != "no help defined" )
ss << " title=\"" << help << '"';
ss << ">" << i->first << " ";
}
ss << '\n';
}
}
ss << '\n';
/*
ss << "HTTP admin port:" << _port << "\n";
*/
doUnlockedStuff(ss);
WebStatusPlugin::runAll( ss );
ss << "