Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

connect.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. 'use strict';
  2. const net = require('net');
  3. const tls = require('tls');
  4. const Connection = require('./connection');
  5. const Query = require('./commands').Query;
  6. const createClientInfo = require('../topologies/shared').createClientInfo;
  7. const MongoError = require('../error').MongoError;
  8. const MongoNetworkError = require('../error').MongoNetworkError;
  9. const defaultAuthProviders = require('../auth/defaultAuthProviders').defaultAuthProviders;
  10. const WIRE_CONSTANTS = require('../wireprotocol/constants');
  11. const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION;
  12. const MAX_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_SERVER_VERSION;
  13. const MIN_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_WIRE_VERSION;
  14. const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION;
  15. let AUTH_PROVIDERS;
  16. function connect(options, callback) {
  17. if (AUTH_PROVIDERS == null) {
  18. AUTH_PROVIDERS = defaultAuthProviders(options.bson);
  19. }
  20. if (options.family !== void 0) {
  21. makeConnection(options.family, options, (err, socket) => {
  22. if (err) {
  23. callback(err, socket); // in the error case, `socket` is the originating error event name
  24. return;
  25. }
  26. performInitialHandshake(new Connection(socket, options), options, callback);
  27. });
  28. return;
  29. }
  30. return makeConnection(6, options, (err, ipv6Socket) => {
  31. if (err) {
  32. makeConnection(4, options, (err, ipv4Socket) => {
  33. if (err) {
  34. callback(err, ipv4Socket); // in the error case, `ipv4Socket` is the originating error event name
  35. return;
  36. }
  37. performInitialHandshake(new Connection(ipv4Socket, options), options, callback);
  38. });
  39. return;
  40. }
  41. performInitialHandshake(new Connection(ipv6Socket, options), options, callback);
  42. });
  43. }
  44. function getSaslSupportedMechs(options) {
  45. if (!(options && options.credentials)) {
  46. return {};
  47. }
  48. const credentials = options.credentials;
  49. // TODO: revisit whether or not items like `options.user` and `options.dbName` should be checked here
  50. const authMechanism = credentials.mechanism;
  51. const authSource = credentials.source || options.dbName || 'admin';
  52. const user = credentials.username || options.user;
  53. if (typeof authMechanism === 'string' && authMechanism.toUpperCase() !== 'DEFAULT') {
  54. return {};
  55. }
  56. if (!user) {
  57. return {};
  58. }
  59. return { saslSupportedMechs: `${authSource}.${user}` };
  60. }
  61. function checkSupportedServer(ismaster, options) {
  62. const serverVersionHighEnough =
  63. ismaster &&
  64. typeof ismaster.maxWireVersion === 'number' &&
  65. ismaster.maxWireVersion >= MIN_SUPPORTED_WIRE_VERSION;
  66. const serverVersionLowEnough =
  67. ismaster &&
  68. typeof ismaster.minWireVersion === 'number' &&
  69. ismaster.minWireVersion <= MAX_SUPPORTED_WIRE_VERSION;
  70. if (serverVersionHighEnough) {
  71. if (serverVersionLowEnough) {
  72. return null;
  73. }
  74. const message = `Server at ${options.host}:${options.port} reports minimum wire version ${
  75. ismaster.minWireVersion
  76. }, but this version of the Node.js Driver requires at most ${MAX_SUPPORTED_WIRE_VERSION} (MongoDB ${MAX_SUPPORTED_SERVER_VERSION})`;
  77. return new MongoError(message);
  78. }
  79. const message = `Server at ${options.host}:${
  80. options.port
  81. } reports maximum wire version ${ismaster.maxWireVersion ||
  82. 0}, but this version of the Node.js Driver requires at least ${MIN_SUPPORTED_WIRE_VERSION} (MongoDB ${MIN_SUPPORTED_SERVER_VERSION})`;
  83. return new MongoError(message);
  84. }
  85. function performInitialHandshake(conn, options, _callback) {
  86. const callback = function(err, ret) {
  87. if (err && conn) {
  88. conn.destroy();
  89. }
  90. _callback(err, ret);
  91. };
  92. let compressors = [];
  93. if (options.compression && options.compression.compressors) {
  94. compressors = options.compression.compressors;
  95. }
  96. const handshakeDoc = Object.assign(
  97. {
  98. ismaster: true,
  99. client: createClientInfo(options),
  100. compression: compressors
  101. },
  102. getSaslSupportedMechs(options)
  103. );
  104. const start = new Date().getTime();
  105. runCommand(conn, 'admin.$cmd', handshakeDoc, options, (err, ismaster) => {
  106. if (err) {
  107. callback(err, null);
  108. return;
  109. }
  110. if (ismaster.ok === 0) {
  111. callback(new MongoError(ismaster), null);
  112. return;
  113. }
  114. const supportedServerErr = checkSupportedServer(ismaster, options);
  115. if (supportedServerErr) {
  116. callback(supportedServerErr, null);
  117. return;
  118. }
  119. // resolve compression
  120. if (ismaster.compression) {
  121. const agreedCompressors = compressors.filter(
  122. compressor => ismaster.compression.indexOf(compressor) !== -1
  123. );
  124. if (agreedCompressors.length) {
  125. conn.agreedCompressor = agreedCompressors[0];
  126. }
  127. if (options.compression && options.compression.zlibCompressionLevel) {
  128. conn.zlibCompressionLevel = options.compression.zlibCompressionLevel;
  129. }
  130. }
  131. // NOTE: This is metadata attached to the connection while porting away from
  132. // handshake being done in the `Server` class. Likely, it should be
  133. // relocated, or at very least restructured.
  134. conn.ismaster = ismaster;
  135. conn.lastIsMasterMS = new Date().getTime() - start;
  136. const credentials = options.credentials;
  137. if (!ismaster.arbiterOnly && credentials) {
  138. credentials.resolveAuthMechanism(ismaster);
  139. authenticate(conn, credentials, callback);
  140. return;
  141. }
  142. callback(null, conn);
  143. });
  144. }
  145. const LEGAL_SSL_SOCKET_OPTIONS = [
  146. 'pfx',
  147. 'key',
  148. 'passphrase',
  149. 'cert',
  150. 'ca',
  151. 'ciphers',
  152. 'NPNProtocols',
  153. 'ALPNProtocols',
  154. 'servername',
  155. 'ecdhCurve',
  156. 'secureProtocol',
  157. 'secureContext',
  158. 'session',
  159. 'minDHSize',
  160. 'crl',
  161. 'rejectUnauthorized'
  162. ];
  163. function parseConnectOptions(family, options) {
  164. const host = typeof options.host === 'string' ? options.host : 'localhost';
  165. if (host.indexOf('/') !== -1) {
  166. return { path: host };
  167. }
  168. const result = {
  169. family,
  170. host,
  171. port: typeof options.port === 'number' ? options.port : 27017,
  172. rejectUnauthorized: false
  173. };
  174. return result;
  175. }
  176. function parseSslOptions(family, options) {
  177. const result = parseConnectOptions(family, options);
  178. // Merge in valid SSL options
  179. for (const name in options) {
  180. if (options[name] != null && LEGAL_SSL_SOCKET_OPTIONS.indexOf(name) !== -1) {
  181. result[name] = options[name];
  182. }
  183. }
  184. // Override checkServerIdentity behavior
  185. if (options.checkServerIdentity === false) {
  186. // Skip the identiy check by retuning undefined as per node documents
  187. // https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
  188. result.checkServerIdentity = function() {
  189. return undefined;
  190. };
  191. } else if (typeof options.checkServerIdentity === 'function') {
  192. result.checkServerIdentity = options.checkServerIdentity;
  193. }
  194. // Set default sni servername to be the same as host
  195. if (result.servername == null) {
  196. result.servername = result.host;
  197. }
  198. return result;
  199. }
  200. function makeConnection(family, options, _callback) {
  201. const useSsl = typeof options.ssl === 'boolean' ? options.ssl : false;
  202. const keepAlive = typeof options.keepAlive === 'boolean' ? options.keepAlive : true;
  203. let keepAliveInitialDelay =
  204. typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
  205. const noDelay = typeof options.noDelay === 'boolean' ? options.noDelay : true;
  206. const connectionTimeout =
  207. typeof options.connectionTimeout === 'number' ? options.connectionTimeout : 30000;
  208. const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
  209. const rejectUnauthorized =
  210. typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;
  211. if (keepAliveInitialDelay > socketTimeout) {
  212. keepAliveInitialDelay = Math.round(socketTimeout / 2);
  213. }
  214. let socket;
  215. const callback = function(err, ret) {
  216. if (err && socket) {
  217. socket.destroy();
  218. }
  219. _callback(err, ret);
  220. };
  221. try {
  222. if (useSsl) {
  223. socket = tls.connect(parseSslOptions(family, options));
  224. if (typeof socket.disableRenegotiation === 'function') {
  225. socket.disableRenegotiation();
  226. }
  227. } else {
  228. socket = net.createConnection(parseConnectOptions(family, options));
  229. }
  230. } catch (err) {
  231. return callback(err);
  232. }
  233. socket.setKeepAlive(keepAlive, keepAliveInitialDelay);
  234. socket.setTimeout(connectionTimeout);
  235. socket.setNoDelay(noDelay);
  236. const errorEvents = ['error', 'close', 'timeout', 'parseError', 'connect'];
  237. function errorHandler(eventName) {
  238. return err => {
  239. errorEvents.forEach(event => socket.removeAllListeners(event));
  240. socket.removeListener('connect', connectHandler);
  241. callback(connectionFailureError(eventName, err), eventName);
  242. };
  243. }
  244. function connectHandler() {
  245. errorEvents.forEach(event => socket.removeAllListeners(event));
  246. if (socket.authorizationError && rejectUnauthorized) {
  247. return callback(socket.authorizationError);
  248. }
  249. socket.setTimeout(socketTimeout);
  250. callback(null, socket);
  251. }
  252. socket.once('error', errorHandler('error'));
  253. socket.once('close', errorHandler('close'));
  254. socket.once('timeout', errorHandler('timeout'));
  255. socket.once('parseError', errorHandler('parseError'));
  256. socket.once('connect', connectHandler);
  257. }
  258. const CONNECTION_ERROR_EVENTS = ['error', 'close', 'timeout', 'parseError'];
  259. function runCommand(conn, ns, command, options, callback) {
  260. if (typeof options === 'function') (callback = options), (options = {});
  261. const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
  262. const bson = conn.options.bson;
  263. const query = new Query(bson, ns, command, {
  264. numberToSkip: 0,
  265. numberToReturn: 1
  266. });
  267. function errorHandler(err) {
  268. conn.resetSocketTimeout();
  269. CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
  270. conn.removeListener('message', messageHandler);
  271. callback(err, null);
  272. }
  273. function messageHandler(msg) {
  274. if (msg.responseTo !== query.requestId) {
  275. return;
  276. }
  277. conn.resetSocketTimeout();
  278. CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
  279. conn.removeListener('message', messageHandler);
  280. msg.parse({ promoteValues: true });
  281. callback(null, msg.documents[0]);
  282. }
  283. conn.setSocketTimeout(socketTimeout);
  284. CONNECTION_ERROR_EVENTS.forEach(eventName => conn.once(eventName, errorHandler));
  285. conn.on('message', messageHandler);
  286. conn.write(query.toBin());
  287. }
  288. function authenticate(conn, credentials, callback) {
  289. const mechanism = credentials.mechanism;
  290. if (!AUTH_PROVIDERS[mechanism]) {
  291. callback(new MongoError(`authMechanism '${mechanism}' not supported`));
  292. return;
  293. }
  294. const provider = AUTH_PROVIDERS[mechanism];
  295. provider.auth(runCommand, [conn], credentials, err => {
  296. if (err) return callback(err);
  297. callback(null, conn);
  298. });
  299. }
  300. function connectionFailureError(type, err) {
  301. switch (type) {
  302. case 'error':
  303. return new MongoNetworkError(err);
  304. case 'timeout':
  305. return new MongoNetworkError(`connection timed out`);
  306. case 'close':
  307. return new MongoNetworkError(`connection closed`);
  308. default:
  309. return new MongoNetworkError(`unknown network error`);
  310. }
  311. }
  312. module.exports = connect;