# frozen_string_literal: true

module Msf
  module OptionalSession
    module LDAP
      include Msf::OptionalSession

      RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT LDAPDomain LDAPUsername LDAPPassword THREADS]
      REQUIRED_OPTIONS = %w[RHOSTS RPORT THREADS]

      def initialize(info = {})
        super(
          update_info(
            info,
            'SessionTypes' => %w[ldap]
          )
        )

        if optional_session_enabled?
          register_option_group(name: 'SESSION',
                                description: 'Used when connecting via an existing SESSION',
                                option_names: ['SESSION'])
          register_option_group(name: 'RHOST',
                                description: 'Used when making a new connection via RHOSTS',
                                option_names: RHOST_GROUP_OPTIONS,
                                required_options: REQUIRED_OPTIONS)

          register_options(
            [
              Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
              Msf::Opt::RHOST(nil, false),
              Msf::Opt::RPORT(389, false)
            ]
          )

          add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
        end
      end

      def optional_session_enabled?
        framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
      end

      # @see #ldap_open
      # @return [Object] The result of whatever the block that was
      #   passed in via the "block" parameter yielded.
      def ldap_connect(opts = {}, &block)
        if session && !opts[:base].blank?
          session.client.base = opts[:base]
        end
        return yield session.client if session

        ldap_open(get_connect_opts.merge(opts), &block)
      rescue ::StandardError => e
        handle_error(e)
      end

      # Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
      # resulting connection object to the caller of this method.
      #
      # @param opts [Hash] A hash containing the connection options for the
      #   LDAP connection to the target server.
      # @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
      #   the target LDAP server.
      def ldap_new(opts = {})
        if session && !opts[:base].blank?
          session.client.base = opts[:base]
        end
        return yield session.client if session

        super
      rescue ::StandardError => e
        handle_error(e)
      end

      private

      def handle_error(e)
        case e
        when ::Net::LDAP::ResponseMissingOrInvalidError
          elog("LDAP Client response missing or invalid: #{e.class}", error: e)
          if session
            print_error("Killing session #{session.sid} due to missing or invalid response from the server.")
            session.kill
          end
        else
          elog("LDAP Client: #{e.class}", error: e)
          # Re-raise other exceptions so they can be handled elsewhere
          raise e
        end
      end
    end
  end
end
