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")
|
||||
}
|
49
main.go
49
main.go
|
@ -1,18 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"syscall"
|
||||
|
||||
ldap "./ldapserver"
|
||||
consul "github.com/hashicorp/consul/api"
|
||||
ldap "github.com/vjeantet/ldapserver"
|
||||
message "github.com/vjeantet/goldap/message"
|
||||
)
|
||||
|
||||
|
@ -28,7 +28,7 @@ func dnToConsul(dn string) string {
|
|||
}
|
||||
|
||||
type DNComponent struct {
|
||||
Type string
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ func parseDN(dn string) ([]DNComponent, error) {
|
|||
return nil, fmt.Errorf("Wrong DN component: %s (expected type=value)", rdn)
|
||||
}
|
||||
ret = append(ret, DNComponent{
|
||||
Type: splits[0],
|
||||
Type: splits[0],
|
||||
Value: splits[1],
|
||||
})
|
||||
}
|
||||
|
@ -56,7 +56,11 @@ type Config struct {
|
|||
|
||||
type Server struct {
|
||||
config Config
|
||||
kv *consul.KV
|
||||
kv *consul.KV
|
||||
}
|
||||
|
||||
type State struct {
|
||||
bindDn string
|
||||
}
|
||||
|
||||
type Attributes map[string]interface{}
|
||||
|
@ -73,7 +77,7 @@ func main() {
|
|||
kv := client.KV()
|
||||
|
||||
// TODO read config from somewhere
|
||||
config := Config {
|
||||
config := Config{
|
||||
Suffix: "dc=gobottin,dc=eu",
|
||||
}
|
||||
|
||||
|
@ -85,6 +89,9 @@ func main() {
|
|||
|
||||
//Create a new LDAP Server
|
||||
ldapserver := ldap.NewServer()
|
||||
ldapserver.NewUserState = func() ldap.UserState {
|
||||
return &State{}
|
||||
}
|
||||
|
||||
routes := ldap.NewRouteMux()
|
||||
routes.Bind(gobottin.handleBind)
|
||||
|
@ -104,7 +111,7 @@ func main() {
|
|||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -114,7 +121,7 @@ func (server *Server) init() error {
|
|||
}
|
||||
|
||||
base_attributes := Attributes{
|
||||
"objectClass": []string{"top", "dcObject", "organization"},
|
||||
"objectClass": []string{"top", "dcObject", "organization"},
|
||||
"structuralObjectClass": "Organization",
|
||||
}
|
||||
suffix_dn, err := parseDN(server.config.Suffix)
|
||||
|
@ -135,12 +142,12 @@ func (server *Server) init() error {
|
|||
|
||||
admin_dn := "cn=admin," + server.config.Suffix
|
||||
admin_attributes := Attributes{
|
||||
"objectClass": []string{"simpleSecurityObject", "organizationalRole"},
|
||||
"description": "LDAP administrator",
|
||||
"cn": "admin",
|
||||
"userpassword": admin_pass_hash,
|
||||
"objectClass": []string{"simpleSecurityObject", "organizationalRole"},
|
||||
"description": "LDAP administrator",
|
||||
"cn": "admin",
|
||||
"userpassword": admin_pass_hash,
|
||||
"structuralObjectClass": "organizationalRole",
|
||||
"permissions": []string{"read", "write"},
|
||||
"permissions": []string{"read", "write"},
|
||||
}
|
||||
|
||||
err = server.addElements(admin_dn, admin_attributes)
|
||||
|
@ -173,10 +180,11 @@ func (server *Server) addElements(dn string, attrs Attributes) error {
|
|||
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()
|
||||
|
||||
result_code, err := server.handleBindInternal(w, r)
|
||||
result_code, err := server.handleBindInternal(state, w, r)
|
||||
|
||||
res := ldap.NewBindResponse(result_code)
|
||||
if err != nil {
|
||||
|
@ -186,9 +194,9 @@ func (server *Server) handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
|||
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 {
|
||||
return ldap.LDAPResultOperationsError, err
|
||||
}
|
||||
|
@ -205,6 +213,7 @@ func (server *Server) handleBindInternal(w ldap.ResponseWriter, r message.BindRe
|
|||
|
||||
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
|
||||
if valid {
|
||||
state.bindDn = string(r.Name())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
} else {
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
|
|
6
ssha.go
6
ssha.go
|
@ -1,11 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"encoding/base64"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// Encode encodes the []byte of raw password
|
||||
|
|
Loading…
Reference in a new issue