1 // dbeval.cpp
2
3
4 /**
5 * Copyright (C) 2018-present MongoDB, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the Server Side Public License, version 1,
9 * as published by MongoDB, Inc.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * Server Side Public License for more details.
15 *
16 * You should have received a copy of the Server Side Public License
17 * along with this program. If not, see
18 * <http://www.mongodb.com/licensing/server-side-public-license>.
19 *
20 * As a special exception, the copyright holders give permission to link the
21 * code of portions of this program with the OpenSSL library under certain
22 * conditions as described in each individual source file and distribute
23 * linked combinations including the program with the OpenSSL library. You
24 * must comply with the Server Side Public License in all respects for
25 * all of the code used other than as permitted herein. If you modify file(s)
26 * with this exception, you may extend this exception to your version of the
27 * file(s), but you are not obligated to do so. If you do not wish to do so,
28 * delete this exception statement from your version. If you delete this
29 * exception statement from all source files in the program, then also delete
30 * it in the license file.
31 */
32
33 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
34
35 #include "mongo/platform/basic.h"
36
37 #include "mongo/bson/util/builder.h"
38 #include "mongo/db/auth/authorization_manager.h"
39 #include "mongo/db/auth/authorization_manager_global.h"
40 #include "mongo/db/auth/authorization_session.h"
41 #include "mongo/db/client.h"
42 #include "mongo/db/commands.h"
43 #include "mongo/db/db_raii.h"
44 #include "mongo/db/introspect.h"
45 #include "mongo/db/jsobj.h"
46 #include "mongo/db/json.h"
47 #include "mongo/db/operation_context.h"
48 #include "mongo/db/s/operation_sharding_state.h"
49 #include "mongo/s/stale_exception.h"
50 #include "mongo/scripting/engine.h"
51 #include "mongo/util/log.h"
52 #include "mongo/util/scopeguard.h"
53
54 namespace mongo {
55
56 using std::unique_ptr;
57 using std::dec;
58 using std::endl;
59 using std::string;
60 using std::stringstream;
61
62 namespace {
63
64 const int edebug = 0;
65
dbEval(OperationContext * opCtx,const string & dbName,const BSONObj & cmd,BSONObjBuilder & result,string & errmsg)66 bool dbEval(OperationContext* opCtx,
67 const string& dbName,
68 const BSONObj& cmd,
69 BSONObjBuilder& result,
70 string& errmsg) {
71 RARELY {
72 warning() << "the eval command is deprecated" << startupWarningsLog;
73 }
74
75 const BSONElement e = cmd.firstElement();
76 uassert(
77 10046, "eval needs Code", e.type() == Code || e.type() == CodeWScope || e.type() == String);
78
79 const char* code = 0;
80 switch (e.type()) {
81 case String:
82 case Code:
83 code = e.valuestr();
84 break;
85 case CodeWScope:
86 code = e.codeWScopeCode();
87 break;
88 default:
89 verify(0);
90 }
91
92 verify(code);
93
94 if (!getGlobalScriptEngine()) {
95 errmsg = "db side execution is disabled";
96 return false;
97 }
98
99 unique_ptr<Scope> s(getGlobalScriptEngine()->newScope());
100 s->registerOperation(opCtx);
101
102 ScriptingFunction f = s->createFunction(code);
103 if (f == 0) {
104 errmsg = string("compile failed: ") + s->getError();
105 return false;
106 }
107
108 s->localConnectForDbEval(opCtx, dbName.c_str());
109
110 if (e.type() == CodeWScope) {
111 s->init(e.codeWScopeScopeDataUnsafe());
112 }
113
114 BSONObj args;
115 {
116 BSONElement argsElement = cmd.getField("args");
117 if (argsElement.type() == Array) {
118 args = argsElement.embeddedObject();
119 if (edebug) {
120 log() << "args:" << args;
121 log() << "code:\n" << redact(code);
122 }
123 }
124 }
125
126 int res;
127 {
128 Timer t;
129 res = s->invoke(f, &args, 0, 0);
130 int m = t.millis();
131 if (m > serverGlobalParams.slowMS) {
132 log() << "dbeval slow, time: " << dec << m << "ms " << dbName;
133 if (m >= 1000)
134 log() << redact(code);
135 else
136 OCCASIONALLY log() << redact(code);
137 }
138 }
139
140 if (res || s->isLastRetNativeCode()) {
141 result.append("errno", (double)res);
142 errmsg = "invoke failed: ";
143 if (s->isLastRetNativeCode())
144 errmsg += "cannot return native function";
145 else
146 errmsg += s->getError();
147
148 return false;
149 }
150
151 s->append(result, "retval", "__returnValue");
152
153 return true;
154 }
155
156
157 class CmdEval : public ErrmsgCommandDeprecated {
158 public:
slaveOk() const159 virtual bool slaveOk() const {
160 return false;
161 }
162
help(stringstream & help) const163 virtual void help(stringstream& help) const {
164 help << "DEPRECATED\n"
165 << "Evaluate javascript at the server.\n"
166 << "http://dochub.mongodb.org/core/serversidecodeexecution";
167 }
supportsWriteConcern(const BSONObj & cmd) const168 virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
169 return false;
170 }
addRequiredPrivileges(const std::string & dbname,const BSONObj & cmdObj,std::vector<Privilege> * out)171 virtual void addRequiredPrivileges(const std::string& dbname,
172 const BSONObj& cmdObj,
173 std::vector<Privilege>* out) {
174 RoleGraph::generateUniversalPrivileges(out);
175 }
176
CmdEval()177 CmdEval() : ErrmsgCommandDeprecated("eval", "$eval") {}
178
errmsgRun(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,string & errmsg,BSONObjBuilder & result)179 bool errmsgRun(OperationContext* opCtx,
180 const string& dbname,
181 const BSONObj& cmdObj,
182 string& errmsg,
183 BSONObjBuilder& result) {
184 // Note: 'eval' is not allowed to touch sharded namespaces, but we can't check the
185 // shardVersions of the namespaces accessed in the script until the script is evaluated.
186 // Instead, we enforce that the script does not access sharded namespaces by ensuring the
187 // shardVersion is set to UNSHARDED on the OperationContext before sending the script to be
188 // evaluated.
189 auto& oss = OperationShardingState::get(opCtx);
190 uassert(ErrorCodes::IllegalOperation,
191 "can't send a shardVersion with the 'eval' command, since you can't use sharded "
192 "collections from 'eval'",
193 !oss.hasShardVersion());
194
195 // Set the shardVersion to UNSHARDED. The "namespace" used does not matter, because if a
196 // shardVersion is set on the OperationContext, a check for a different namespace will
197 // default to UNSHARDED.
198 oss.setShardVersion(NamespaceString(dbname), ChunkVersion::UNSHARDED());
199 const auto shardVersionGuard =
200 MakeGuard([&]() { oss.unsetShardVersion(NamespaceString(dbname)); });
201
202 try {
203 if (cmdObj["nolock"].trueValue()) {
204 return dbEval(opCtx, dbname, cmdObj, result, errmsg);
205 }
206
207 Lock::GlobalWrite lk(opCtx);
208
209 OldClientContext ctx(opCtx, dbname, false /* no shard version checking here */);
210
211 return dbEval(opCtx, dbname, cmdObj, result, errmsg);
212 } catch (const AssertionException& ex) {
213 // Convert a stale shardVersion error to a stronger error to prevent this node or the
214 // sending node from believing it needs to refresh its routing table.
215 if (ex.code() == ErrorCodes::StaleConfig) {
216 uasserted(ErrorCodes::BadValue,
217 str::stream() << "can't use sharded collection from db.eval");
218 }
219 throw;
220 }
221 }
222
223 } cmdeval;
224
225 } // namespace
226 } // namespace mongo
227