# -*- coding: binary -*-

module Msf
  ###
  #
  # This module exposes methods for querying a remote LDAP service
  #
  ###
  module Exploit::Remote::LDAP
    module Server
      include Exploit::Remote::SocketServer

      #
      # Initializes an exploit module that serves LDAP requests
      #
      def initialize(info = {})
        super

        register_options(
          [
            OptPort.new('SRVPORT', [true, 'The local port to listen on.', 389]),
            OptPath.new('LDIF_FILE', [ false, 'Directory LDIF file path']),
          ], Exploit::Remote::LDAP::Server
        )

        register_advanced_options(
          [
            OptBool.new('LdapServerUdp', [true, 'Serve UDP LDAP requests', true]),
            OptBool.new('LdapServerTcp', [true, 'Serve TCP LDAP requests', true])
          ], Exploit::Remote::LDAP::Server
        )
      end

      attr_accessor :service # :nodoc:

      #
      # Read LDIF file - from https://github.com/ruby-ldap/ruby-net-ldap/blob/master/testserver/ldapserver.rb#L162
      #
      # @ return [Hash] parsed ldif file
      def read_ldif
        return if datastore['LDIF_FILE'].blank? || !File.exist?(datastore['LDIF_FILE'])

        ary = File.readlines(datastore['LDIF_FILE'])
        ldif = {}
        while (line = ary.shift) && line.chomp!
          next unless line =~ /^dn:\s*/i

          dn = Regexp.last_match.post_match
          ldif[dn] = {}
          while (attrib = ary.shift) && attrib.chomp! && attrib =~ /^(\w+)\s*:\s*/
            ldif[dn][Regexp.last_match(1)] ||= []
            ldif[dn][Regexp.last_match(1)] << Regexp.last_match.post_match
          end
        end
        ldif
      end

      #
      # Handle incoming requests
      # Override this method in modules to take flow control
      #
      def on_dispatch_request(cli, data)
        service.default_dispatch_request(cli, data)
      end

      #
      # Handle incoming requests
      # Override this method in modules to take flow control
      #
      def on_send_response(cli, data)
        cli.write(data)
      end

      #
      # Starts the server
      #
      def start_service
        comm = _determine_server_comm(bindhost)
        self.service = Rex::ServiceManager.start(
          Rex::Proto::LDAP::Server,
          bindhost,
          bindport,
          datastore['LdapServerUdp'],
          datastore['LdapServerTcp'],
          read_ldif,
          comm,
          { 'Msf' => framework, 'MsfExploit' => self }
        )

        service.dispatch_request_proc = proc do |cli, data|
          on_dispatch_request(cli, data)
        end
        service.send_response_proc = proc do |cli, data|
          on_send_response(cli, data)
        end
      rescue ::Errno::EACCES => e
        raise Rex::BindFailed, e.message
      end
    end
  end
end
