<commit_before># $Id$
#
# Copyright (C) 2006 by <NAME>. All Rights Reserved.
# Gmail account: garbagecat10.
#
# This is an LDAP server intended for unit testing of Net::LDAP.
# It implements as much of the protocol as we have the stomach
# to implement but serves static data. Use ldapsearch to test
# this server!
#
# To make this easier to write, we use the Ruby/EventMachine
# reactor library.
#


require 'stringio'

#------------------------------------------------

class String
  def read_ber! syntax=nil
    s = StringIO.new self
    pdu = s.read_ber(syntax)
    if pdu
      if s.eof?
        slice!(0, length)
      else
        slice!(0, length - s.read.length)
      end
    end
    pdu
  end
end


module LdapServer

  LdapServerAsnSyntax = {
    :application => {
      :constructed => {
        0 => :array,               # LDAP BindRequest
        3 => :array                # LDAP SearchRequest
      },
      :primitive => {
        2 => :string,              # ldapsearch sends this to unbind
      }
    },
    :context_specific => {
      :primitive => {
        0 => :string,              # simple auth (password)
        7 => :string               # present filter
      },
    }
  }

  def post_init
    $logger.info "Accepted LDAP connection"
    @authenticated = false
  end

  def receive_data data
    @data ||= ""; @data << data
    while pdu = @data.read_ber!(LdapServerAsnSyntax)
      begin
      handle_ldap_pdu pdu
      rescue
        $logger.error "closing connection due to error #{$!}"
        close_connection
      end
    end
  end

  def handle_ldap_pdu pdu
    tag_id = pdu[1].ber_identifier
    case tag_id
    when 0x60
      handle_bind_request pdu
    when 0x63
      handle_search_request pdu
    when 0x42
      # bizarre thing, it's a null object (primitive application-2)
      # sent by ldapsearch to request an unbind (or a kiss-off, not sure which)
      close_connection_after_writing
    else
      $logger.error "received unknown packet-type #{tag_id}"
      close_connection_after_writing
    end
  end

  def handle_bind_request pdu
    # TODO, return a proper LDAP error instead of blowing up on version error
    if pdu[1][0] != 3
      send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
    elsif pdu[1][1] != "cn=bigshot,dc=bayshorenetworks,dc=com"
      send_ldap_response 1, pdu[0].to_i, 48, "", "Who are you?"
    elsif pdu[1][2].ber_identifier != 0x80
      send_ldap_response 1, pdu[0].to_i, 7, "", "Keep it simple, man"
    elsif pdu[1][2] != "opensesame"
      send_ldap_response 1, pdu[0].to_i, 49, "", "Make my day"
    else
      @authenticated = true
      send_ldap_response 1, pdu[0].to_i, 0, pdu[1][1], "I'll take it"
    end
  end

  def handle_search_request pdu
    unless @authenticated
      send_ldap_response 5, pdu[0].to_i, 50, "", "Who did you say you were?"
      return
    end

    treebase = pdu[1][0]
    if treebase != "dc=bayshorenetworks,dc=com"
      send_ldap_response 5, pdu[0].to_i, 32, "", "unknown treebase"
      return
    end

    msgid = pdu[0].to_i.to_ber

    $ldif.each {|dn, entry|

      attrs = []
      entry.each {|k, v|
        attrvals = v.map {|v1| v1.to_ber}.to_ber_set
        attrs << [k.to_ber, attrvals].to_ber_sequence
      }

      appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4)
      pkt = [msgid.to_ber, appseq].to_ber_sequence
      send_data pkt
    }

    # pdu[1][7] is the attributes. It's an empty array to signify ALL attributes.
    puts "WARNING, not interpreting attributes specifier"
=begin
Search Response ::=
      CHOICE {
           entry          [APPLICATION 4] SEQUENCE {
                               objectName     LDAPDN,
                               attributes     SEQUENCE OF SEQUENCE {
                                                   AttributeType,
                                                   SET OF AttributeValue
                                              }
                          },
           resultCode     [APPLICATION 5] LDAPResult
       }
=end

=begin
    send_data( [
      pdu[0].to_i.to_ber, [
        "abcdefghijklmnopqrstuvwxyz".to_ber, [

          [
            "mail".to_ber, ["aaa".to_ber, "bbb".to_ber, "ccc".to_ber].to_ber_set
          ].to_ber_sequence,
          [
            "objectclass".to_ber, ["111".to_ber, "222".to_ber, "333".to_ber].to_ber_set
          ].to_ber_sequence,
          [
            "cn".to_ber, ["CNCNCNCN".to_ber].to_ber_set
          ].to_ber_sequence,

        ].to_ber_sequence
      ].to_ber_appsequence(4)
    ].to_ber_sequence)

    send_data( [
      pdu[0].to_i.to_ber, [
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_ber, [

          [
            "mail".to_ber, ["aaa".to_ber, "bbb".to_ber, "ccc".to_ber].to_ber_set
          ].to_ber_sequence,
          [
            "objectclass".to_ber, ["111".to_ber, "222".to_ber, "333".to_ber].to_ber_set
          ].to_ber_sequence,
          [
            "cn".to_ber, ["CNCNCNCN".to_ber].to_ber_set
          ].to_ber_sequence,

        ].to_ber_sequence
      ].to_ber_appsequence(4)
    ].to_ber_sequence)
=end

    send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?"
  end

  def send_ldap_response pkt_tag, msgid, code, dn, text
    send_data( [msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag) ].to_ber )
  end

end


#------------------------------------------------

# Rather bogus, a global method, which reads a HARDCODED filename
# parses out LDIF data. It will be used to serve LDAP queries out of this server.
#
def load_test_data
  ary = File.readlines( "./testdata.ldif" )
  hash = {}<commit_msg>ldapserver now correctly selects which attributes to return in a search, based on the client's request.<commit_after># $Id$
#
# Copyright (C) 2006 by <NAME>. All Rights Reserved.
# Gmail account: garbagecat10.
#
# This is an LDAP server intended for unit testing of Net::LDAP.
# It implements as much of the protocol as we have the stomach
# to implement but serves static data. Use ldapsearch to test
# this server!
#
# To make this easier to write, we use the Ruby/EventMachine
# reactor library.
#


require 'stringio'

#------------------------------------------------

class String
  def read_ber! syntax=nil
    s = StringIO.new self
    pdu = s.read_ber(syntax)
    if pdu
      if s.eof?
        slice!(0, length)
      else
        slice!(0, length - s.read.length)
      end
    end
    pdu
  end
end


module LdapServer

  LdapServerAsnSyntax = {
    :application => {
      :constructed => {
        0 => :array,               # LDAP BindRequest
        3 => :array                # LDAP SearchRequest
      },
      :primitive => {
        2 => :string,              # ldapsearch sends this to unbind
      }
    },
    :context_specific => {
      :primitive => {
        0 => :string,              # simple auth (password)
        7 => :string               # present filter
      },
    }
  }

  def post_init
    $logger.info "Accepted LDAP connection"
    @authenticated = false
  end

  def receive_data data
    @data ||= ""; @data << data
    while pdu = @data.read_ber!(LdapServerAsnSyntax)
      begin
      handle_ldap_pdu pdu
      rescue
        $logger.error "closing connection due to error #{$!}"
        close_connection
      end
    end
  end

  def handle_ldap_pdu pdu
    tag_id = pdu[1].ber_identifier
    case tag_id
    when 0x60
      handle_bind_request pdu
    when 0x63
      handle_search_request pdu
    when 0x42
      # bizarre thing, it's a null object (primitive application-2)
      # sent by ldapsearch to request an unbind (or a kiss-off, not sure which)
      close_connection_after_writing
    else
      $logger.error "received unknown packet-type #{tag_id}"
      close_connection_after_writing
    end
  end

  def handle_bind_request pdu
    # TODO, return a proper LDAP error instead of blowing up on version error
    if pdu[1][0] != 3
      send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
    elsif pdu[1][1] != "cn=bigshot,dc=bayshorenetworks,dc=com"
      send_ldap_response 1, pdu[0].to_i, 48, "", "Who are you?"
    elsif pdu[1][2].ber_identifier != 0x80
      send_ldap_response 1, pdu[0].to_i, 7, "", "Keep it simple, man"
    elsif pdu[1][2] != "opensesame"
      send_ldap_response 1, pdu[0].to_i, 49, "", "Make my day"
    else
      @authenticated = true
      send_ldap_response 1, pdu[0].to_i, 0, pdu[1][1], "I'll take it"
    end
  end



  #--
  # Search Response ::=
  #       CHOICE {
  #            entry          [APPLICATION 4] SEQUENCE {
  #                                objectName     LDAPDN,
  #                                attributes     SEQUENCE OF SEQUENCE {
  #                                                    AttributeType,
  #                                                    SET OF AttributeValue
  #                                               }
  #                           },
  #            resultCode     [APPLICATION 5] LDAPResult
  #        }
  def handle_search_request pdu
    unless @authenticated
      send_ldap_response 5, pdu[0].to_i, 50, "", "Who did you say you were?"
      return
    end

    treebase = pdu[1][0]
    if treebase != "dc=bayshorenetworks,dc=com"
      send_ldap_response 5, pdu[0].to_i, 32, "", "unknown treebase"
      return
    end

    msgid = pdu[0].to_i.to_ber

    # pdu[1][7] is the list of requested attributes.
    # If it's an empty array, that means that *all* attributes were requested.
    requested_attrs = if pdu[1][7].length > 0
      pdu[1][7].map {|a| a.downcase}
    else
      :all
    end


    $ldif.each {|dn, entry|

      attrs = []
      entry.each {|k, v|
        if requested_attrs == :all or requested_attrs.include?(k.downcase)
          attrvals = v.map {|v1| v1.to_ber}.to_ber_set
          attrs << [k.to_ber, attrvals].to_ber_sequence
        end
      }

      appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4)
      pkt = [msgid.to_ber, appseq].to_ber_sequence
      send_data pkt
    }


    send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?"
  end



  def send_ldap_response pkt_tag, msgid, code, dn, text
    send_data( [msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag) ].to_ber )
  end

end


#------------------------------------------------

# <NAME>, a global method, which reads a HARDCODED filename
# parses out LDIF data. It will be used to serve LDAP queries out of this server.
#
def load_test_data
  ary = File.readlines( "./testdata.ldif" )
  hash = {}
{'fraction_non_alphanumeric': 0.11978084262233138, 'fraction_numerical': 0.017098054033629323, 'mean_word_length': 1.9375693673695893, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1172813', 'n_tokens_mistral': 3647, 'n_tokens_neox': 3472, 'n_words': 915}