forked from Deuxfleurs/bottin
Add ldapserver source in here & add support for client state
This commit is contained in:
parent
bade33cf15
commit
67fa504e20
10 changed files with 1078 additions and 23 deletions
257
ldapserver/client.go
Normal file
257
ldapserver/client.go
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ldap "github.com/vjeantet/goldap/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserState interface{}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
Numero int
|
||||||
|
srv *Server
|
||||||
|
rwc net.Conn
|
||||||
|
br *bufio.Reader
|
||||||
|
bw *bufio.Writer
|
||||||
|
chanOut chan *ldap.LDAPMessage
|
||||||
|
wg sync.WaitGroup
|
||||||
|
closing chan bool
|
||||||
|
requestList map[int]*Message
|
||||||
|
mutex sync.Mutex
|
||||||
|
writeDone chan bool
|
||||||
|
rawData []byte
|
||||||
|
userState UserState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetConn() net.Conn {
|
||||||
|
return c.rwc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetRaw() []byte {
|
||||||
|
return c.rawData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) SetConn(conn net.Conn) {
|
||||||
|
c.rwc = conn
|
||||||
|
c.br = bufio.NewReader(c.rwc)
|
||||||
|
c.bw = bufio.NewWriter(c.rwc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetMessageByID(messageID int) (*Message, bool) {
|
||||||
|
if requestToAbandon, ok := c.requestList[messageID]; ok {
|
||||||
|
return requestToAbandon, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Addr() net.Addr {
|
||||||
|
return c.rwc.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ReadPacket() (*messagePacket, error) {
|
||||||
|
mP, err := readMessagePacket(c.br)
|
||||||
|
c.rawData = make([]byte, len(mP.bytes))
|
||||||
|
copy(c.rawData, mP.bytes)
|
||||||
|
return mP, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) serve() {
|
||||||
|
defer c.close()
|
||||||
|
|
||||||
|
c.closing = make(chan bool)
|
||||||
|
if onc := c.srv.OnNewConnection; onc != nil {
|
||||||
|
if err := onc(c.rwc); err != nil {
|
||||||
|
Logger.Printf("Erreur OnNewConnection: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the ldap response queue to be writted to client (buffered to 20)
|
||||||
|
// buffered to 20 means that If client is slow to handler responses, Server
|
||||||
|
// Handlers will stop to send more respones
|
||||||
|
c.chanOut = make(chan *ldap.LDAPMessage)
|
||||||
|
c.writeDone = make(chan bool)
|
||||||
|
// for each message in c.chanOut send it to client
|
||||||
|
go func() {
|
||||||
|
for msg := range c.chanOut {
|
||||||
|
c.writeMessage(msg)
|
||||||
|
}
|
||||||
|
close(c.writeDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Listen for server signal to shutdown
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.srv.chDone: // server signals shutdown process
|
||||||
|
c.wg.Add(1)
|
||||||
|
r := NewExtendedResponse(LDAPResultUnwillingToPerform)
|
||||||
|
r.SetDiagnosticMessage("server is about to stop")
|
||||||
|
r.SetResponseName(NoticeOfDisconnection)
|
||||||
|
|
||||||
|
m := ldap.NewLDAPMessageWithProtocolOp(r)
|
||||||
|
|
||||||
|
c.chanOut <- m
|
||||||
|
c.wg.Done()
|
||||||
|
c.rwc.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||||
|
return
|
||||||
|
case <-c.closing:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.requestList = make(map[int]*Message)
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
if c.srv.ReadTimeout != 0 {
|
||||||
|
c.rwc.SetReadDeadline(time.Now().Add(c.srv.ReadTimeout))
|
||||||
|
}
|
||||||
|
if c.srv.WriteTimeout != 0 {
|
||||||
|
c.rwc.SetWriteDeadline(time.Now().Add(c.srv.WriteTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read client input as a ASN1/BER binary message
|
||||||
|
messagePacket, err := c.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||||
|
Logger.Printf("Sorry client %d, i can not wait anymore (reading timeout) ! %s", c.Numero, err)
|
||||||
|
} else {
|
||||||
|
Logger.Printf("Error readMessagePacket: %s", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert ASN1 binaryMessage to a ldap Message
|
||||||
|
message, err := messagePacket.readMessage()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Logger.Printf("Error reading Message : %s\n\t%x", err.Error(), messagePacket.bytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Logger.Printf("<<< %d - %s - hex=%x", c.Numero, message.ProtocolOpName(), messagePacket)
|
||||||
|
|
||||||
|
// TODO: Use a implementation to limit runnuning request by client
|
||||||
|
// solution 1 : when the buffered output channel is full, send a busy
|
||||||
|
// solution 2 : when 10 client requests (goroutines) are running, send a busy message
|
||||||
|
// And when the limit is reached THEN send a BusyLdapMessage
|
||||||
|
|
||||||
|
// When message is an UnbindRequest, stop serving
|
||||||
|
if _, ok := message.ProtocolOp().(ldap.UnbindRequest); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If client requests a startTls, do not handle it in a
|
||||||
|
// goroutine, connection has to remain free until TLS is OK
|
||||||
|
// @see RFC https://tools.ietf.org/html/rfc4511#section-4.14.1
|
||||||
|
if req, ok := message.ProtocolOp().(ldap.ExtendedRequest); ok {
|
||||||
|
if req.RequestName() == NoticeOfStartTLS {
|
||||||
|
c.wg.Add(1)
|
||||||
|
c.ProcessRequestMessage(&message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: go/non go routine choice should be done in the ProcessRequestMessage
|
||||||
|
// not in the client.serve func
|
||||||
|
c.wg.Add(1)
|
||||||
|
go c.ProcessRequestMessage(&message)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// close closes client,
|
||||||
|
// * stop reading from client
|
||||||
|
// * signals to all currently running request processor to stop
|
||||||
|
// * wait for all request processor to end
|
||||||
|
// * close client connection
|
||||||
|
// * signal to server that client shutdown is ok
|
||||||
|
func (c *client) close() {
|
||||||
|
Logger.Printf("client %d close()", c.Numero)
|
||||||
|
close(c.closing)
|
||||||
|
|
||||||
|
// stop reading from client
|
||||||
|
c.rwc.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||||
|
Logger.Printf("client %d close() - stop reading from client", c.Numero)
|
||||||
|
|
||||||
|
// signals to all currently running request processor to stop
|
||||||
|
c.mutex.Lock()
|
||||||
|
for messageID, request := range c.requestList {
|
||||||
|
Logger.Printf("Client %d close() - sent abandon signal to request[messageID = %d]", c.Numero, messageID)
|
||||||
|
go request.Abandon()
|
||||||
|
}
|
||||||
|
c.mutex.Unlock()
|
||||||
|
Logger.Printf("client %d close() - Abandon signal sent to processors", c.Numero)
|
||||||
|
|
||||||
|
c.wg.Wait() // wait for all current running request processor to end
|
||||||
|
close(c.chanOut) // No more message will be sent to client, close chanOUT
|
||||||
|
Logger.Printf("client [%d] request processors ended", c.Numero)
|
||||||
|
|
||||||
|
<-c.writeDone // Wait for the last message sent to be written
|
||||||
|
c.rwc.Close() // close client connection
|
||||||
|
Logger.Printf("client [%d] connection closed", c.Numero)
|
||||||
|
|
||||||
|
c.srv.wg.Done() // signal to server that client shutdown is ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) writeMessage(m *ldap.LDAPMessage) {
|
||||||
|
data, _ := m.Write()
|
||||||
|
Logger.Printf(">>> %d - %s - hex=%x", c.Numero, m.ProtocolOpName(), data.Bytes())
|
||||||
|
c.bw.Write(data.Bytes())
|
||||||
|
c.bw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseWriter interface is used by an LDAP handler to
|
||||||
|
// construct an LDAP response.
|
||||||
|
type ResponseWriter interface {
|
||||||
|
// Write writes the LDAPResponse to the connection as part of an LDAP reply.
|
||||||
|
Write(po ldap.ProtocolOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriterImpl struct {
|
||||||
|
chanOut chan *ldap.LDAPMessage
|
||||||
|
messageID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w responseWriterImpl) Write(po ldap.ProtocolOp) {
|
||||||
|
m := ldap.NewLDAPMessageWithProtocolOp(po)
|
||||||
|
m.SetMessageID(w.messageID)
|
||||||
|
w.chanOut <- m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ProcessRequestMessage(message *ldap.LDAPMessage) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
|
||||||
|
var m Message
|
||||||
|
m = Message{
|
||||||
|
LDAPMessage: message,
|
||||||
|
Done: make(chan bool, 2),
|
||||||
|
Client: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.registerRequest(&m)
|
||||||
|
defer c.unregisterRequest(&m)
|
||||||
|
|
||||||
|
var w responseWriterImpl
|
||||||
|
w.chanOut = c.chanOut
|
||||||
|
w.messageID = m.MessageID().Int()
|
||||||
|
|
||||||
|
c.srv.Handler.ServeLDAP(c.userState, w, &m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) registerRequest(m *Message) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
c.requestList[m.MessageID().Int()] = m
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) unregisterRequest(m *Message) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
delete(c.requestList, m.MessageID().Int())
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}
|
96
ldapserver/constants.go
Normal file
96
ldapserver/constants.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import ldap "github.com/vjeantet/goldap/message"
|
||||||
|
|
||||||
|
// LDAP Application Codes
|
||||||
|
const (
|
||||||
|
ApplicationBindRequest = 0
|
||||||
|
ApplicationBindResponse = 1
|
||||||
|
ApplicationUnbindRequest = 2
|
||||||
|
ApplicationSearchRequest = 3
|
||||||
|
ApplicationSearchResultEntry = 4
|
||||||
|
ApplicationSearchResultDone = 5
|
||||||
|
ApplicationModifyRequest = 6
|
||||||
|
ApplicationModifyResponse = 7
|
||||||
|
ApplicationAddRequest = 8
|
||||||
|
ApplicationAddResponse = 9
|
||||||
|
ApplicationDelRequest = 10
|
||||||
|
ApplicationDelResponse = 11
|
||||||
|
ApplicationModifyDNRequest = 12
|
||||||
|
ApplicationModifyDNResponse = 13
|
||||||
|
ApplicationCompareRequest = 14
|
||||||
|
ApplicationCompareResponse = 15
|
||||||
|
ApplicationAbandonRequest = 16
|
||||||
|
ApplicationSearchResultReference = 19
|
||||||
|
ApplicationExtendedRequest = 23
|
||||||
|
ApplicationExtendedResponse = 24
|
||||||
|
)
|
||||||
|
|
||||||
|
// LDAP Result Codes
|
||||||
|
const (
|
||||||
|
LDAPResultSuccess = 0
|
||||||
|
LDAPResultOperationsError = 1
|
||||||
|
LDAPResultProtocolError = 2
|
||||||
|
LDAPResultTimeLimitExceeded = 3
|
||||||
|
LDAPResultSizeLimitExceeded = 4
|
||||||
|
LDAPResultCompareFalse = 5
|
||||||
|
LDAPResultCompareTrue = 6
|
||||||
|
LDAPResultAuthMethodNotSupported = 7
|
||||||
|
LDAPResultStrongAuthRequired = 8
|
||||||
|
LDAPResultReferral = 10
|
||||||
|
LDAPResultAdminLimitExceeded = 11
|
||||||
|
LDAPResultUnavailableCriticalExtension = 12
|
||||||
|
LDAPResultConfidentialityRequired = 13
|
||||||
|
LDAPResultSaslBindInProgress = 14
|
||||||
|
LDAPResultNoSuchAttribute = 16
|
||||||
|
LDAPResultUndefinedAttributeType = 17
|
||||||
|
LDAPResultInappropriateMatching = 18
|
||||||
|
LDAPResultConstraintViolation = 19
|
||||||
|
LDAPResultAttributeOrValueExists = 20
|
||||||
|
LDAPResultInvalidAttributeSyntax = 21
|
||||||
|
LDAPResultNoSuchObject = 32
|
||||||
|
LDAPResultAliasProblem = 33
|
||||||
|
LDAPResultInvalidDNSyntax = 34
|
||||||
|
LDAPResultAliasDereferencingProblem = 36
|
||||||
|
LDAPResultInappropriateAuthentication = 48
|
||||||
|
LDAPResultInvalidCredentials = 49
|
||||||
|
LDAPResultInsufficientAccessRights = 50
|
||||||
|
LDAPResultBusy = 51
|
||||||
|
LDAPResultUnavailable = 52
|
||||||
|
LDAPResultUnwillingToPerform = 53
|
||||||
|
LDAPResultLoopDetect = 54
|
||||||
|
LDAPResultNamingViolation = 64
|
||||||
|
LDAPResultObjectClassViolation = 65
|
||||||
|
LDAPResultNotAllowedOnNonLeaf = 66
|
||||||
|
LDAPResultNotAllowedOnRDN = 67
|
||||||
|
LDAPResultEntryAlreadyExists = 68
|
||||||
|
LDAPResultObjectClassModsProhibited = 69
|
||||||
|
LDAPResultAffectsMultipleDSAs = 71
|
||||||
|
LDAPResultOther = 80
|
||||||
|
|
||||||
|
ErrorNetwork = 200
|
||||||
|
ErrorFilterCompile = 201
|
||||||
|
ErrorFilterDecompile = 202
|
||||||
|
ErrorDebugging = 203
|
||||||
|
)
|
||||||
|
|
||||||
|
// Modify Request Operation code
|
||||||
|
const (
|
||||||
|
ModifyRequestChangeOperationAdd = 0
|
||||||
|
ModifyRequestChangeOperationDelete = 1
|
||||||
|
ModifyRequestChangeOperationReplace = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const SearchRequestScopeBaseObject = 0
|
||||||
|
const SearchRequestSingleLevel = 1
|
||||||
|
const SearchRequestHomeSubtree = 2
|
||||||
|
|
||||||
|
// Extended operation responseName and requestName
|
||||||
|
const (
|
||||||
|
NoticeOfDisconnection ldap.LDAPOID = "1.3.6.1.4.1.1466.2003"
|
||||||
|
NoticeOfCancel ldap.LDAPOID = "1.3.6.1.1.8"
|
||||||
|
NoticeOfStartTLS ldap.LDAPOID = "1.3.6.1.4.1.1466.20037"
|
||||||
|
NoticeOfWhoAmI ldap.LDAPOID = "1.3.6.1.4.1.4203.1.11.3"
|
||||||
|
NoticeOfGetConnectionID ldap.LDAPOID = "1.3.6.1.4.1.26027.1.6.2"
|
||||||
|
NoticeOfPasswordModify ldap.LDAPOID = "1.3.6.1.4.1.4203.1.11.1"
|
||||||
|
)
|
33
ldapserver/logger.go
Normal file
33
ldapserver/logger.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Logger logger
|
||||||
|
|
||||||
|
// Logger represents log.Logger functions from the standard library
|
||||||
|
type logger interface {
|
||||||
|
Fatal(v ...interface{})
|
||||||
|
Fatalf(format string, v ...interface{})
|
||||||
|
Fatalln(v ...interface{})
|
||||||
|
|
||||||
|
Panic(v ...interface{})
|
||||||
|
Panicf(format string, v ...interface{})
|
||||||
|
Panicln(v ...interface{})
|
||||||
|
|
||||||
|
Print(v ...interface{})
|
||||||
|
Printf(format string, v ...interface{})
|
||||||
|
Println(v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Logger = log.New(os.Stdout, "", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DiscardingLogger can be used to disable logging output
|
||||||
|
DiscardingLogger = log.New(ioutil.Discard, "", 0)
|
||||||
|
)
|
55
ldapserver/message.go
Normal file
55
ldapserver/message.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ldap "github.com/vjeantet/goldap/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
*ldap.LDAPMessage
|
||||||
|
Client *client
|
||||||
|
Done chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) String() string {
|
||||||
|
return fmt.Sprintf("MessageId=%d, %s", m.MessageID(), m.ProtocolOpName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abandon close the Done channel, to notify handler's user function to stop any
|
||||||
|
// running process
|
||||||
|
func (m *Message) Abandon() {
|
||||||
|
m.Done <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetAbandonRequest() ldap.AbandonRequest {
|
||||||
|
return m.ProtocolOp().(ldap.AbandonRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetSearchRequest() ldap.SearchRequest {
|
||||||
|
return m.ProtocolOp().(ldap.SearchRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetBindRequest() ldap.BindRequest {
|
||||||
|
return m.ProtocolOp().(ldap.BindRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetAddRequest() ldap.AddRequest {
|
||||||
|
return m.ProtocolOp().(ldap.AddRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetDeleteRequest() ldap.DelRequest {
|
||||||
|
return m.ProtocolOp().(ldap.DelRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetModifyRequest() ldap.ModifyRequest {
|
||||||
|
return m.ProtocolOp().(ldap.ModifyRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetCompareRequest() ldap.CompareRequest {
|
||||||
|
return m.ProtocolOp().(ldap.CompareRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetExtendedRequest() ldap.ExtendedRequest {
|
||||||
|
return m.ProtocolOp().(ldap.ExtendedRequest)
|
||||||
|
}
|
148
ldapserver/packet.go
Normal file
148
ldapserver/packet.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ldap "github.com/vjeantet/goldap/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
type messagePacket struct {
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMessagePacket(br *bufio.Reader) (*messagePacket, error) {
|
||||||
|
var err error
|
||||||
|
var bytes *[]byte
|
||||||
|
bytes, err = readLdapMessageBytes(br)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
messagePacket := &messagePacket{bytes: *bytes}
|
||||||
|
return messagePacket, err
|
||||||
|
}
|
||||||
|
return &messagePacket{}, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *messagePacket) readMessage() (m ldap.LDAPMessage, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("invalid packet received hex=%x, %#v", msg.bytes, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return decodeMessage(msg.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMessage(bytes []byte) (ret ldap.LDAPMessage, err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = errors.New(fmt.Sprintf("%s", e))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
zero := 0
|
||||||
|
ret, err = ldap.ReadLDAPMessage(ldap.NewBytes(zero, bytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BELLOW SHOULD BE IN ROOX PACKAGE
|
||||||
|
|
||||||
|
func readLdapMessageBytes(br *bufio.Reader) (ret *[]byte, err error) {
|
||||||
|
var bytes []byte
|
||||||
|
var tagAndLength ldap.TagAndLength
|
||||||
|
tagAndLength, err = readTagAndLength(br, &bytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
readBytes(br, &bytes, tagAndLength.Length)
|
||||||
|
return &bytes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTagAndLength parses an ASN.1 tag and length pair from a live connection
|
||||||
|
// into a byte slice. It returns the parsed data and the new offset. SET and
|
||||||
|
// SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we
|
||||||
|
// don't distinguish between ordered and unordered objects in this code.
|
||||||
|
func readTagAndLength(conn *bufio.Reader, bytes *[]byte) (ret ldap.TagAndLength, err error) {
|
||||||
|
// offset = initOffset
|
||||||
|
//b := bytes[offset]
|
||||||
|
//offset++
|
||||||
|
var b byte
|
||||||
|
b, err = readBytes(conn, bytes, 1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.Class = int(b >> 6)
|
||||||
|
ret.IsCompound = b&0x20 == 0x20
|
||||||
|
ret.Tag = int(b & 0x1f)
|
||||||
|
|
||||||
|
// // If the bottom five bits are set, then the tag number is actually base 128
|
||||||
|
// // encoded afterwards
|
||||||
|
// if ret.tag == 0x1f {
|
||||||
|
// ret.tag, err = parseBase128Int(conn, bytes)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// We are expecting the LDAP sequence tag 0x30 as first byte
|
||||||
|
if b != 0x30 {
|
||||||
|
panic(fmt.Sprintf("Expecting 0x30 as first byte, but got %#x instead", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = readBytes(conn, bytes, 1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
// The length is encoded in the bottom 7 bits.
|
||||||
|
ret.Length = int(b & 0x7f)
|
||||||
|
} else {
|
||||||
|
// Bottom 7 bits give the number of length bytes to follow.
|
||||||
|
numBytes := int(b & 0x7f)
|
||||||
|
if numBytes == 0 {
|
||||||
|
err = ldap.SyntaxError{"indefinite length found (not DER)"}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.Length = 0
|
||||||
|
for i := 0; i < numBytes; i++ {
|
||||||
|
|
||||||
|
b, err = readBytes(conn, bytes, 1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ret.Length >= 1<<23 {
|
||||||
|
// We can't shift ret.length up without
|
||||||
|
// overflowing.
|
||||||
|
err = ldap.StructuralError{"length too large"}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.Length <<= 8
|
||||||
|
ret.Length |= int(b)
|
||||||
|
// Compat some lib which use go-ldap or someone else,
|
||||||
|
// they encode int may have leading zeros when it's greater then 127
|
||||||
|
// if ret.Length == 0 {
|
||||||
|
// // DER requires that lengths be minimal.
|
||||||
|
// err = ldap.StructuralError{"superfluous leading zeros in length"}
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read "length" bytes from the connection
|
||||||
|
// Append the read bytes to "bytes"
|
||||||
|
// Return the last read byte
|
||||||
|
func readBytes(conn *bufio.Reader, bytes *[]byte, length int) (b byte, err error) {
|
||||||
|
newbytes := make([]byte, length)
|
||||||
|
n, err := conn.Read(newbytes)
|
||||||
|
if n != length {
|
||||||
|
fmt.Errorf("%d bytes read instead of %d", n, length)
|
||||||
|
} else if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*bytes = append(*bytes, newbytes...)
|
||||||
|
b = (*bytes)[len(*bytes)-1]
|
||||||
|
return
|
||||||
|
}
|
57
ldapserver/responsemessage.go
Normal file
57
ldapserver/responsemessage.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import ldap "github.com/vjeantet/goldap/message"
|
||||||
|
|
||||||
|
func NewBindResponse(resultCode int) ldap.BindResponse {
|
||||||
|
r := ldap.BindResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponse(resultCode int) ldap.LDAPResult {
|
||||||
|
r := ldap.LDAPResult{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExtendedResponse(resultCode int) ldap.ExtendedResponse {
|
||||||
|
r := ldap.ExtendedResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompareResponse(resultCode int) ldap.CompareResponse {
|
||||||
|
r := ldap.CompareResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModifyResponse(resultCode int) ldap.ModifyResponse {
|
||||||
|
r := ldap.ModifyResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteResponse(resultCode int) ldap.DelResponse {
|
||||||
|
r := ldap.DelResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddResponse(resultCode int) ldap.AddResponse {
|
||||||
|
r := ldap.AddResponse{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchResultDoneResponse(resultCode int) ldap.SearchResultDone {
|
||||||
|
r := ldap.SearchResultDone{}
|
||||||
|
r.SetResultCode(resultCode)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchResultEntry(objectname string) ldap.SearchResultEntry {
|
||||||
|
r := ldap.SearchResultEntry{}
|
||||||
|
r.SetObjectName(objectname)
|
||||||
|
return r
|
||||||
|
}
|
255
ldapserver/route.go
Normal file
255
ldapserver/route.go
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ldap "github.com/vjeantet/goldap/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constant to LDAP Request protocol Type names
|
||||||
|
const (
|
||||||
|
SEARCH = "SearchRequest"
|
||||||
|
BIND = "BindRequest"
|
||||||
|
COMPARE = "CompareRequest"
|
||||||
|
ADD = "AddRequest"
|
||||||
|
MODIFY = "ModifyRequest"
|
||||||
|
DELETE = "DelRequest"
|
||||||
|
EXTENDED = "ExtendedRequest"
|
||||||
|
ABANDON = "AbandonRequest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerFunc type is an adapter to allow the use of
|
||||||
|
// ordinary functions as LDAP handlers. If f is a function
|
||||||
|
// with the appropriate signature, HandlerFunc(f) is a
|
||||||
|
// Handler object that calls f.
|
||||||
|
type HandlerFunc func(UserState, ResponseWriter, *Message)
|
||||||
|
|
||||||
|
// RouteMux manages all routes
|
||||||
|
type RouteMux struct {
|
||||||
|
routes []*route
|
||||||
|
notFoundRoute *route
|
||||||
|
}
|
||||||
|
|
||||||
|
type route struct {
|
||||||
|
label string
|
||||||
|
operation string
|
||||||
|
handler HandlerFunc
|
||||||
|
exoName string
|
||||||
|
sBasedn string
|
||||||
|
uBasedn bool
|
||||||
|
sFilter string
|
||||||
|
uFilter bool
|
||||||
|
sScope int
|
||||||
|
uScope bool
|
||||||
|
sAuthChoice string
|
||||||
|
uAuthChoice bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match return true when the *Message matches the route
|
||||||
|
// conditions
|
||||||
|
func (r *route) Match(m *Message) bool {
|
||||||
|
if m.ProtocolOpName() != r.operation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := m.ProtocolOp().(type) {
|
||||||
|
case ldap.BindRequest:
|
||||||
|
if r.uAuthChoice == true {
|
||||||
|
if strings.ToLower(v.AuthenticationChoice()) != r.sAuthChoice {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case ldap.ExtendedRequest:
|
||||||
|
if string(v.RequestName()) != r.exoName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case ldap.SearchRequest:
|
||||||
|
if r.uBasedn == true {
|
||||||
|
if strings.ToLower(string(v.BaseObject())) != r.sBasedn {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.uFilter == true {
|
||||||
|
if strings.ToLower(v.FilterString()) != r.sFilter {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.uScope == true {
|
||||||
|
if int(v.Scope()) != r.sScope {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) Label(label string) *route {
|
||||||
|
r.label = label
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) BaseDn(dn string) *route {
|
||||||
|
r.sBasedn = strings.ToLower(dn)
|
||||||
|
r.uBasedn = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) AuthenticationChoice(choice string) *route {
|
||||||
|
r.sAuthChoice = strings.ToLower(choice)
|
||||||
|
r.uAuthChoice = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) Filter(pattern string) *route {
|
||||||
|
r.sFilter = strings.ToLower(pattern)
|
||||||
|
r.uFilter = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) Scope(scope int) *route {
|
||||||
|
r.sScope = scope
|
||||||
|
r.uScope = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *route) RequestName(name ldap.LDAPOID) *route {
|
||||||
|
r.exoName = string(name)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouteMux returns a new *RouteMux
|
||||||
|
// RouteMux implements ldapserver.Handler
|
||||||
|
func NewRouteMux() *RouteMux {
|
||||||
|
return &RouteMux{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler interface used to serve a LDAP Request message
|
||||||
|
type Handler interface {
|
||||||
|
ServeLDAP(s UserState, w ResponseWriter, r *Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeLDAP dispatches the request to the handler whose
|
||||||
|
// pattern most closely matches the request request Message.
|
||||||
|
func (h *RouteMux) ServeLDAP(s UserState, w ResponseWriter, r *Message) {
|
||||||
|
|
||||||
|
//find a matching Route
|
||||||
|
for _, route := range h.routes {
|
||||||
|
|
||||||
|
//if the route don't match, skip it
|
||||||
|
if route.Match(r) == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if route.label != "" {
|
||||||
|
Logger.Printf("")
|
||||||
|
Logger.Printf(" ROUTE MATCH ; %s", route.label)
|
||||||
|
Logger.Printf("")
|
||||||
|
// Logger.Printf(" ROUTE MATCH ; %s", runtime.FuncForPC(reflect.ValueOf(route.handler).Pointer()).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
route.handler(s, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch a AbandonRequest not handled by user
|
||||||
|
switch v := r.ProtocolOp().(type) {
|
||||||
|
case ldap.AbandonRequest:
|
||||||
|
// retreive the request to abandon, and send a abort signal to it
|
||||||
|
if requestToAbandon, ok := r.Client.GetMessageByID(int(v)); ok {
|
||||||
|
requestToAbandon.Abandon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.notFoundRoute != nil {
|
||||||
|
h.notFoundRoute.handler(s, w, r)
|
||||||
|
} else {
|
||||||
|
res := NewResponse(LDAPResultUnwillingToPerform)
|
||||||
|
res.SetDiagnosticMessage("Operation not implemented by server")
|
||||||
|
w.Write(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a new Route to the Handler
|
||||||
|
func (h *RouteMux) addRoute(r *route) {
|
||||||
|
//and finally append to the list of Routes
|
||||||
|
//create the Route
|
||||||
|
h.routes = append(h.routes, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) NotFound(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.handler = handler
|
||||||
|
h.notFoundRoute = route
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Bind(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = BIND
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Search(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = SEARCH
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Add(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = ADD
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Delete(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = DELETE
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Modify(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = MODIFY
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Compare(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = COMPARE
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Extended(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = EXTENDED
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RouteMux) Abandon(handler HandlerFunc) *route {
|
||||||
|
route := &route{}
|
||||||
|
route.operation = ABANDON
|
||||||
|
route.handler = handler
|
||||||
|
h.addRoute(route)
|
||||||
|
return route
|
||||||
|
}
|
145
ldapserver/server.go
Normal file
145
ldapserver/server.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package ldapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is an LDAP server.
|
||||||
|
type Server struct {
|
||||||
|
Listener net.Listener
|
||||||
|
ReadTimeout time.Duration // optional read timeout
|
||||||
|
WriteTimeout time.Duration // optional write timeout
|
||||||
|
wg sync.WaitGroup // group of goroutines (1 by client)
|
||||||
|
chDone chan bool // Channel Done, value => shutdown
|
||||||
|
|
||||||
|
// OnNewConnection, if non-nil, is called on new connections.
|
||||||
|
// If it returns non-nil, the connection is closed.
|
||||||
|
OnNewConnection func(c net.Conn) error
|
||||||
|
|
||||||
|
// Handler handles ldap message received from client
|
||||||
|
// it SHOULD "implement" RequestHandler interface
|
||||||
|
Handler Handler
|
||||||
|
NewUserState func() UserState
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServer return a LDAP Server
|
||||||
|
func NewServer() *Server {
|
||||||
|
return &Server{
|
||||||
|
chDone: make(chan bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers the handler for the server.
|
||||||
|
// If a handler already exists for pattern, Handle panics
|
||||||
|
func (s *Server) Handle(h Handler) {
|
||||||
|
if s.Handler != nil {
|
||||||
|
panic("LDAP: multiple Handler registrations")
|
||||||
|
}
|
||||||
|
s.Handler = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on the TCP network address s.Addr and then
|
||||||
|
// calls Serve to handle requests on incoming connections. If
|
||||||
|
// s.Addr is blank, ":389" is used.
|
||||||
|
func (s *Server) ListenAndServe(addr string, options ...func(*Server)) error {
|
||||||
|
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":389"
|
||||||
|
}
|
||||||
|
|
||||||
|
var e error
|
||||||
|
s.Listener, e = net.Listen("tcp", addr)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
Logger.Printf("Listening on %s\n", addr)
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle requests messages on the ln listener
|
||||||
|
func (s *Server) serve() error {
|
||||||
|
defer s.Listener.Close()
|
||||||
|
|
||||||
|
if s.Handler == nil {
|
||||||
|
Logger.Panicln("No LDAP Request Handler defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.chDone:
|
||||||
|
Logger.Print("Stopping server")
|
||||||
|
s.Listener.Close()
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
rw, err := s.Listener.Accept()
|
||||||
|
|
||||||
|
if s.ReadTimeout != 0 {
|
||||||
|
rw.SetReadDeadline(time.Now().Add(s.ReadTimeout))
|
||||||
|
}
|
||||||
|
if s.WriteTimeout != 0 {
|
||||||
|
rw.SetWriteDeadline(time.Now().Add(s.WriteTimeout))
|
||||||
|
}
|
||||||
|
if nil != err {
|
||||||
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Logger.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli, err := s.newClient(rw)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
cli.Numero = i
|
||||||
|
Logger.Printf("Connection client [%d] from %s accepted", cli.Numero, cli.rwc.RemoteAddr().String())
|
||||||
|
s.wg.Add(1)
|
||||||
|
go cli.serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new session with the connection
|
||||||
|
// client has a writer and reader buffer
|
||||||
|
func (s *Server) newClient(rwc net.Conn) (c *client, err error) {
|
||||||
|
c = &client{
|
||||||
|
srv: s,
|
||||||
|
rwc: rwc,
|
||||||
|
br: bufio.NewReader(rwc),
|
||||||
|
bw: bufio.NewWriter(rwc),
|
||||||
|
userState: s.NewUserState(),
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Termination of the LDAP session is initiated by the server sending a
|
||||||
|
// Notice of Disconnection. In this case, each
|
||||||
|
// protocol peer gracefully terminates the LDAP session by ceasing
|
||||||
|
// exchanges at the LDAP message layer, tearing down any SASL layer,
|
||||||
|
// tearing down any TLS layer, and closing the transport connection.
|
||||||
|
// A protocol peer may determine that the continuation of any
|
||||||
|
// communication would be pernicious, and in this case, it may abruptly
|
||||||
|
// terminate the session by ceasing communication and closing the
|
||||||
|
// transport connection.
|
||||||
|
// In either case, when the LDAP session is terminated.
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
close(s.chDone)
|
||||||
|
Logger.Print("gracefully closing client connections...")
|
||||||
|
s.wg.Wait()
|
||||||
|
Logger.Print("all clients connection closed")
|
||||||
|
}
|
31
main.go
31
main.go
|
@ -1,18 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
|
||||||
"strings"
|
"strings"
|
||||||
"encoding/json"
|
"syscall"
|
||||||
"encoding/base64"
|
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
|
ldap "./ldapserver"
|
||||||
consul "github.com/hashicorp/consul/api"
|
consul "github.com/hashicorp/consul/api"
|
||||||
ldap "github.com/vjeantet/ldapserver"
|
|
||||||
message "github.com/vjeantet/goldap/message"
|
message "github.com/vjeantet/goldap/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,6 +59,10 @@ type Server struct {
|
||||||
kv *consul.KV
|
kv *consul.KV
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
bindDn string
|
||||||
|
}
|
||||||
|
|
||||||
type Attributes map[string]interface{}
|
type Attributes map[string]interface{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -73,7 +77,7 @@ func main() {
|
||||||
kv := client.KV()
|
kv := client.KV()
|
||||||
|
|
||||||
// TODO read config from somewhere
|
// TODO read config from somewhere
|
||||||
config := Config {
|
config := Config{
|
||||||
Suffix: "dc=gobottin,dc=eu",
|
Suffix: "dc=gobottin,dc=eu",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +89,9 @@ func main() {
|
||||||
|
|
||||||
//Create a new LDAP Server
|
//Create a new LDAP Server
|
||||||
ldapserver := ldap.NewServer()
|
ldapserver := ldap.NewServer()
|
||||||
|
ldapserver.NewUserState = func() ldap.UserState {
|
||||||
|
return &State{}
|
||||||
|
}
|
||||||
|
|
||||||
routes := ldap.NewRouteMux()
|
routes := ldap.NewRouteMux()
|
||||||
routes.Bind(gobottin.handleBind)
|
routes.Bind(gobottin.handleBind)
|
||||||
|
@ -104,7 +111,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) init() error {
|
func (server *Server) init() error {
|
||||||
pair, _, err := server.kv.Get(dnToConsul(server.config.Suffix) + "/attribute=objectClass", nil)
|
pair, _, err := server.kv.Get(dnToConsul(server.config.Suffix)+"/attribute=objectClass", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,10 +180,11 @@ func (server *Server) addElements(dn string, attrs Attributes) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
func (server *Server) handleBind(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
|
state := s.(*State)
|
||||||
r := m.GetBindRequest()
|
r := m.GetBindRequest()
|
||||||
|
|
||||||
result_code, err := server.handleBindInternal(w, r)
|
result_code, err := server.handleBindInternal(state, w, r)
|
||||||
|
|
||||||
res := ldap.NewBindResponse(result_code)
|
res := ldap.NewBindResponse(result_code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -186,9 +194,9 @@ func (server *Server) handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) handleBindInternal(w ldap.ResponseWriter, r message.BindRequest) (int, error) {
|
func (server *Server) handleBindInternal(state *State, w ldap.ResponseWriter, r message.BindRequest) (int, error) {
|
||||||
|
|
||||||
pair, _, err := server.kv.Get(dnToConsul(string(r.Name())) + "/attribute=userpassword", nil)
|
pair, _, err := server.kv.Get(dnToConsul(string(r.Name()))+"/attribute=userpassword", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -205,6 +213,7 @@ func (server *Server) handleBindInternal(w ldap.ResponseWriter, r message.BindRe
|
||||||
|
|
||||||
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
|
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
|
||||||
if valid {
|
if valid {
|
||||||
|
state.bindDn = string(r.Name())
|
||||||
return ldap.LDAPResultSuccess, nil
|
return ldap.LDAPResultSuccess, nil
|
||||||
} else {
|
} else {
|
||||||
return ldap.LDAPResultInvalidCredentials, nil
|
return ldap.LDAPResultInvalidCredentials, nil
|
||||||
|
|
6
ssha.go
6
ssha.go
|
@ -1,11 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encode encodes the []byte of raw password
|
// Encode encodes the []byte of raw password
|
||||||
|
|
Loading…
Reference in a new issue