-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathldap-proxy.go
281 lines (244 loc) · 8.57 KB
/
ldap-proxy.go
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// ldap-proxy: lighweght proxy for LDAP
// This application act as a TCP proxy between an application and
// a LDAP server, rectifying packets on the fly (eg. to emulate different
// LDAP servers).
//
// Copyright 2020 Nicola Ruggero
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"bytes"
"encoding/hex"
"flag"
"fmt"
"log"
"net"
"os"
"runtime/debug"
"time"
ber "github.com/go-asn1-ber/asn1-ber"
)
// Globals
const swVer = "1.0"
var verbose bool = false
// rectifier plugins structure
type rectifier struct {
req, res []byte
sendback bool
desc string
}
func logVerboseln(v ...interface{}) {
if verbose {
fmt.Println(v...)
}
}
func logVerbosef(format string, v ...interface{}) {
if verbose {
fmt.Printf(format, v...)
}
}
func main() {
// Command line options
localAddr := flag.String("local", ":3000", "local address")
remoteAddr := flag.String("remote", ":4000", "remote address")
verboseFlag := flag.Bool("verbose", false, "Print additional information")
showSwVer := flag.Bool("version", false, "Print software version and exit")
flag.Parse()
// Show Software version
if *showSwVer {
fmt.Printf("ldap-proxy: lighweght proxy for LDAP\n")
fmt.Printf("Version: %s\n", swVer)
os.Exit(1)
}
// Assign globally
verbose = *verboseFlag
log.Printf("Starting ldap-proxy: lighweght proxy for LDAP\n")
log.Printf("Version: %s\n", swVer)
// Listen for connections
ln, err := net.Listen("tcp", *localAddr)
if err != nil {
log.Fatal("Unable to create listener:", err)
}
defer ln.Close()
log.Println("Listening from: ", *localAddr)
log.Println("Sending to: ", *remoteAddr)
// Accept new incoming connections
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
// Start a new thread to handle the new incoming connection
go handleConn(conn, *remoteAddr)
}
}
// Handle new incoming connections, proxy data to remote server and send
// artificially crafted responses back to the clients when necessary
func handleConn(conn net.Conn, remoteAddr string) {
log.Println("New connection from: ", conn.RemoteAddr())
// Connect to remote server to proxy data to
rconn, err := net.Dial("tcp", remoteAddr)
if err != nil {
log.Println("Error dialing", err)
rconn.Close()
return
}
log.Println("Server connection to: ", rconn.RemoteAddr())
// Start 2 new threads to handle the requests/responses inside the connection
// we need 2 async threads otherwise an incomplete request/response
// may block the communication flow from the OSI L7 perspective
// because of infinite waiting for data from one of the counterparts
go handleRequest(conn, rconn, "client to proxy", true) // client to proxy
go handleRequest(rconn, conn, "server to proxy", false) // server to proxy
}
func handleRequest(conn net.Conn, rconn net.Conn, desc string, useRectifier bool) {
defer func() {
if r := recover(); r != nil {
logVerboseln("Recovering from panic:", r)
logVerboseln("Stack Trace:")
if verbose {
debug.PrintStack()
}
}
log.Println("handleRequest: deferred connection closure: ", desc)
conn.Close()
rconn.Close()
}()
// From source to proxy
buf := bufio.NewReader(conn)
// Loop while communication channel is alive
for {
// Read ASN.1 data from source
start := time.Now()
log.Println(" ber.PacketRead -> ", desc)
packet, err := ber.ReadPacket(buf)
if err != nil {
log.Println("Error read:", err)
return
}
t := time.Now()
elapsed := t.Sub(start)
logVerboseln(" Duration ber.PacketRead -> ", desc, elapsed)
// Calculate total lenght
packetLen := len(packet.Bytes())
log.Printf("Received %d bytes: %s\n", packetLen, desc)
logVerbosef("%s", hex.Dump(packet.Bytes()[:packetLen]))
// Calculate lenght of the ASN.1 packet data without headers
dataLen := packet.Data.Len()
packetDataOffset := packetLen - dataLen
logVerbosef("LEN-Data: %d\n", dataLen)
logVerbosef("%s", hex.Dump(packet.Bytes()[packetDataOffset:packetLen]))
// Sanity checks on the packet's children
childrenLen := len(packet.Children)
logVerbosef("LEN-Children: %d\n", childrenLen)
if childrenLen == 0 {
log.Println("Invalid packet: no children found")
continue
}
if packet.Children[0].Tag != ber.TagInteger {
log.Println("Unrecognized messageID", packet.Children[0].Value)
continue
}
// Calculate lenght of the remaining ASN.1 packet without headers and LDAP messageID
dataMessageIDLen := len(packet.Children[0].Bytes())
packetDataNoMsgIDOffset := packetDataOffset + dataMessageIDLen
messageID := packet.Children[0].Value.(int64)
logVerbosef("messageID: %d\n", messageID)
logVerbosef("LEN-messageID: %d\n", dataMessageIDLen)
logVerbosef("%s", hex.Dump(packet.Bytes()[packetDataNoMsgIDOffset:packetLen]))
// Prepare outgoing data
out := make([]byte, 0)
rectified := false
sendback := false
if useRectifier {
// use rectifier function to rectify data
log.Println("Rectifier enabled: processing")
// Create ASN.1 LDAP header (SEQUENCE + messageID)
// This is necessary to envelope the date after processing
rectifiedPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
rectifiedPacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "messageID"))
// Rectify data for *all* children
for i, child := range packet.Children[1:] {
d, r, s := rectifyData(child.Bytes())
// Check if data has been actually rectified
rectified = rectified || r
// Check if data need to be sentback and NOT forwarded to destination
sendback = sendback || s
log.Printf("Rectifier [%d]: rectified: %t sendback: %t", i, rectified, sendback)
rectifiedPacket.AppendChild(ber.DecodePacket(d))
}
out = append(out, rectifiedPacket.Bytes()[:]...)
} else {
// Copy data
log.Println("Rectifier disabled: copying")
out = append(out, packet.Bytes()[:]...)
}
// Write data to destination
start = time.Now()
if rectified && sendback {
log.Println(" rconn.Write (sendback) -> ", desc)
_, err = conn.Write(out)
} else {
log.Println(" rconn.Write -> ", desc)
_, err = rconn.Write(out)
}
if err != nil {
log.Println("Error write:", err)
return
}
t = time.Now()
elapsed = t.Sub(start)
logVerboseln(" Duration rconn.Write -> ", desc, elapsed)
}
}
func rectifyData(b []byte) ([]byte, bool, bool) {
logVerboseln("rectifyData: entering")
rectified := false
sendback := false
rectifiers := initRectifiers()
for _, singleRectifier := range rectifiers[:] {
if bytes.Contains(b, singleRectifier.req) {
b = singleRectifier.res
rectified = rectified || true
sendback = sendback || singleRectifier.sendback
logVerbosef("rectifyData [%s]: rectified\n", singleRectifier.desc)
} else {
rectified = rectified || false
sendback = sendback || false
logVerbosef("rectifyData [%s]: NOT rectified\n", singleRectifier.desc)
}
}
return b, rectified, sendback
}
// Initialize all rectifiers
func initRectifiers() []rectifier {
r := make([]rectifier, 0)
// Rectifier prova
r = append(r, rectifier{
req: []byte{
0x63, 0x33, 0x04, 0x00, 0x0a, 0x01, 0x00, 0x0a, 0x01, 0x03, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, /* c3.............. */
0x01, 0x01, 0x00, 0x87, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x61, 0x73, 0x73, /* .....objectClass */
0x30, 0x13, 0x04, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x53, 0x75, 0x62, /* 0...subschemaSub */
0x65, 0x6e, 0x74, 0x72, 0x79,
},
res: []byte{
0x64, 0x26, 0x04, 0x00, 0x30, 0x22, 0x30, 0x20, 0x04, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x68, /* d&..0"0 ..subsch */
0x65, 0x6d, 0x61, 0x53, 0x75, 0x62, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x31, 0x0b, 0x04, 0x09, 0x63, /* emaSubentry1...c */
0x6e, 0x3d, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, /* n=schema */
},
sendback: true, desc: "prova"})
return r
}