Add ldapserver source in here & add support for client state

This commit is contained in:
Alex 2020-01-19 13:00:53 +01:00
parent bade33cf15
commit 67fa504e20
10 changed files with 1078 additions and 23 deletions

257
ldapserver/client.go Normal file
View 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
View 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
View 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
View 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
View 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
}

View 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
View 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
View 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
View file

@ -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

View file

@ -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