"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveCname = exports.performGSSAPICanonicalizeHostName = exports.GSSAPI = exports.GSSAPICanonicalizationValue = void 0; const dns = require("dns"); const deps_1 = require("../../deps"); const error_1 = require("../../error"); const utils_1 = require("../../utils"); const auth_provider_1 = require("./auth_provider"); /** @public */ exports.GSSAPICanonicalizationValue = Object.freeze({ on: true, off: false, none: 'none', forward: 'forward', forwardAndReverse: 'forwardAndReverse' }); async function externalCommand(connection, command) { return connection.commandAsync((0, utils_1.ns)('$external.$cmd'), command, undefined); } let krb; class GSSAPI extends auth_provider_1.AuthProvider { async auth(authContext) { const { connection, credentials } = authContext; if (credentials == null) { throw new error_1.MongoMissingCredentialsError('Credentials required for GSSAPI authentication'); } const { username } = credentials; const client = await makeKerberosClient(authContext); const payload = await client.step(''); const saslStartResponse = await externalCommand(connection, saslStart(payload)); const negotiatedPayload = await negotiate(client, 10, saslStartResponse.payload); const saslContinueResponse = await externalCommand(connection, saslContinue(negotiatedPayload, saslStartResponse.conversationId)); const finalizePayload = await finalize(client, username, saslContinueResponse.payload); await externalCommand(connection, { saslContinue: 1, conversationId: saslContinueResponse.conversationId, payload: finalizePayload }); } } exports.GSSAPI = GSSAPI; async function makeKerberosClient(authContext) { const { hostAddress } = authContext.options; const { credentials } = authContext; if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) { throw new error_1.MongoInvalidArgumentError('Connection must have host and port and credentials defined.'); } loadKrb(); if ('kModuleError' in krb) { throw krb['kModuleError']; } const { initializeClient } = krb; const { username, password } = credentials; const mechanismProperties = credentials.mechanismProperties; const serviceName = mechanismProperties.SERVICE_NAME ?? 'mongodb'; const host = await performGSSAPICanonicalizeHostName(hostAddress.host, mechanismProperties); const initOptions = {}; if (password != null) { // TODO(NODE-5139): These do not match the typescript options in initializeClient Object.assign(initOptions, { user: username, password: password }); } const spnHost = mechanismProperties.SERVICE_HOST ?? host; let spn = `${serviceName}${process.platform === 'win32' ? '/' : '@'}${spnHost}`; if ('SERVICE_REALM' in mechanismProperties) { spn = `${spn}@${mechanismProperties.SERVICE_REALM}`; } return initializeClient(spn, initOptions); } function saslStart(payload) { return { saslStart: 1, mechanism: 'GSSAPI', payload, autoAuthorize: 1 }; } function saslContinue(payload, conversationId) { return { saslContinue: 1, conversationId, payload }; } async function negotiate(client, retries, payload) { try { const response = await client.step(payload); return response || ''; } catch (error) { if (retries === 0) { // Retries exhausted, raise error throw error; } // Adjust number of retries and call step again return negotiate(client, retries - 1, payload); } } async function finalize(client, user, payload) { // GSS Client Unwrap const response = await client.unwrap(payload); return client.wrap(response || '', { user }); } async function performGSSAPICanonicalizeHostName(host, mechanismProperties) { const mode = mechanismProperties.CANONICALIZE_HOST_NAME; if (!mode || mode === exports.GSSAPICanonicalizationValue.none) { return host; } // If forward and reverse or true if (mode === exports.GSSAPICanonicalizationValue.on || mode === exports.GSSAPICanonicalizationValue.forwardAndReverse) { // Perform the lookup of the ip address. const { address } = await dns.promises.lookup(host); try { // Perform a reverse ptr lookup on the ip address. const results = await dns.promises.resolvePtr(address); // If the ptr did not error but had no results, return the host. return results.length > 0 ? results[0] : host; } catch (error) { // This can error as ptr records may not exist for all ips. In this case // fallback to a cname lookup as dns.lookup() does not return the // cname. return resolveCname(host); } } else { // The case for forward is just to resolve the cname as dns.lookup() // will not return it. return resolveCname(host); } } exports.performGSSAPICanonicalizeHostName = performGSSAPICanonicalizeHostName; async function resolveCname(host) { // Attempt to resolve the host name try { const results = await dns.promises.resolveCname(host); // Get the first resolved host id return results.length > 0 ? results[0] : host; } catch { return host; } } exports.resolveCname = resolveCname; /** * Load the Kerberos library. */ function loadKrb() { if (!krb) { krb = (0, deps_1.getKerberos)(); } } //# sourceMappingURL=gssapi.js.map