wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Copyright 2011 Mark Cavage, Inc.  All rights reserved.
 
const EventEmitter = require('events').EventEmitter
const util = require('util')
 
const assert = require('assert-plus')
const asn1 = require('@ldapjs/asn1')
const logger = require('../logger')
 
const messages = require('@ldapjs/messages')
const AbandonRequest = messages.AbandonRequest
const AddRequest = messages.AddRequest
const AddResponse = messages.AddResponse
const BindRequest = messages.BindRequest
const BindResponse = messages.BindResponse
const CompareRequest = messages.CompareRequest
const CompareResponse = messages.CompareResponse
const DeleteRequest = messages.DeleteRequest
const DeleteResponse = messages.DeleteResponse
const ExtendedRequest = messages.ExtensionRequest
const ExtendedResponse = messages.ExtensionResponse
const ModifyRequest = messages.ModifyRequest
const ModifyResponse = messages.ModifyResponse
const ModifyDNRequest = messages.ModifyDnRequest
const ModifyDNResponse = messages.ModifyDnResponse
const SearchRequest = messages.SearchRequest
const SearchEntry = messages.SearchResultEntry
const SearchReference = messages.SearchResultReference
const SearchResponse = require('./search_response')
const UnbindRequest = messages.UnbindRequest
const LDAPResult = messages.LdapResult
 
const Protocol = require('@ldapjs/protocol')
 
/// --- Globals
 
const BerReader = asn1.BerReader
 
/// --- API
 
function Parser (options = {}) {
  assert.object(options)
 
  EventEmitter.call(this)
 
  this.buffer = null
  this.log = options.log || logger
}
util.inherits(Parser, EventEmitter)
 
/**
 * The LDAP server/client implementations will receive data from a stream and feed
 * it into this method. This method will collect that data into an internal
 * growing buffer. As that buffer fills with enough data to constitute a valid
 * LDAP message, the data will be parsed, emitted as a message object, and
 * reset the buffer to account for any next message in the stream.
 */
Parser.prototype.write = function (data) {
  if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') }
 
  let nextMessage = null
  const self = this
 
  function end () {
    if (nextMessage) { return self.write(nextMessage) }
 
    return true
  }
 
  self.buffer = self.buffer ? Buffer.concat([self.buffer, data]) : data
 
  let ber = new BerReader(self.buffer)
 
  let foundSeq = false
  try {
    foundSeq = ber.readSequence()
  } catch (e) {
    this.emit('error', e)
  }
 
  if (!foundSeq || ber.remain < ber.length) {
    // ENOTENOUGH
    return false
  } else if (ber.remain > ber.length) {
    // ETOOMUCH
 
    // This is an odd branch. Basically, it is setting `nextMessage` to
    // a buffer that represents data part of a message subsequent to the one
    // being processed. It then re-creates `ber` as a representation of
    // the message being processed and advances its offset to the value
    // position of the TLV.
 
    // Set `nextMessage` to the bytes subsequent to the current message's
    // value bytes. That is, slice from the byte immediately following the
    // current message's value bytes until the end of the buffer.
    nextMessage = self.buffer.slice(ber.offset + ber.length)
 
    const currOffset = ber.offset
    ber = new BerReader(ber.buffer.subarray(0, currOffset + ber.length))
    ber.readSequence()
 
    assert.equal(ber.remain, ber.length)
  }
 
  // If we're here, ber holds the message, and nextMessage is temporarily
  // pointing at the next sequence of data (if it exists)
  self.buffer = null
 
  let message
  try {
    if (Object.prototype.toString.call(ber) === '[object BerReader]') {
      // Parse the BER into a JavaScript object representation. The message
      // objects require the full sequence in order to construct the object.
      // At this point, we have already read the sequence tag and length, so
      // we need to rewind the buffer a bit. The `.sequenceToReader` method
      // does this for us.
      message = messages.LdapMessage.parse(ber.sequenceToReader())
    } else {
      // Bail here if peer isn't speaking protocol at all
      message = this.getMessage(ber)
    }
 
    if (!message) {
      return end()
    }
 
    // TODO: find a better way to handle logging now that messages and the
    // server are decoupled. ~ jsumners 2023-02-17
    message.log = this.log
  } catch (e) {
    this.emit('error', e, message)
    return false
  }
 
  this.emit('message', message)
  return end()
}
 
Parser.prototype.getMessage = function (ber) {
  assert.ok(ber)
 
  const self = this
 
  const messageId = ber.readInt()
  const type = ber.readSequence()
 
  let Message
  switch (type) {
    case Protocol.operations.LDAP_REQ_ABANDON:
      Message = AbandonRequest
      break
 
    case Protocol.operations.LDAP_REQ_ADD:
      Message = AddRequest
      break
 
    case Protocol.operations.LDAP_RES_ADD:
      Message = AddResponse
      break
 
    case Protocol.operations.LDAP_REQ_BIND:
      Message = BindRequest
      break
 
    case Protocol.operations.LDAP_RES_BIND:
      Message = BindResponse
      break
 
    case Protocol.operations.LDAP_REQ_COMPARE:
      Message = CompareRequest
      break
 
    case Protocol.operations.LDAP_RES_COMPARE:
      Message = CompareResponse
      break
 
    case Protocol.operations.LDAP_REQ_DELETE:
      Message = DeleteRequest
      break
 
    case Protocol.operations.LDAP_RES_DELETE:
      Message = DeleteResponse
      break
 
    case Protocol.operations.LDAP_REQ_EXTENSION:
      Message = ExtendedRequest
      break
 
    case Protocol.operations.LDAP_RES_EXTENSION:
      Message = ExtendedResponse
      break
 
    case Protocol.operations.LDAP_REQ_MODIFY:
      Message = ModifyRequest
      break
 
    case Protocol.operations.LDAP_RES_MODIFY:
      Message = ModifyResponse
      break
 
    case Protocol.operations.LDAP_REQ_MODRDN:
      Message = ModifyDNRequest
      break
 
    case Protocol.operations.LDAP_RES_MODRDN:
      Message = ModifyDNResponse
      break
 
    case Protocol.operations.LDAP_REQ_SEARCH:
      Message = SearchRequest
      break
 
    case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
      Message = SearchEntry
      break
 
    case Protocol.operations.LDAP_RES_SEARCH_REF:
      Message = SearchReference
      break
 
    case Protocol.operations.LDAP_RES_SEARCH:
      Message = SearchResponse
      break
 
    case Protocol.operations.LDAP_REQ_UNBIND:
      Message = UnbindRequest
      break
 
    default:
      this.emit('error',
        new Error('Op 0x' + (type ? type.toString(16) : '??') +
                        ' not supported'),
        new LDAPResult({
          messageId,
          protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
        }))
 
      return false
  }
 
  return new Message({
    messageId,
    log: self.log
  })
}
 
/// --- Exports
 
module.exports = Parser