Skip to content

Commit

Permalink
Add support for ModifyDN
Browse files Browse the repository at this point in the history
  • Loading branch information
rhafer committed Feb 21, 2023
1 parent 26162eb commit e07caee
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 1 deletion.
83 changes: 83 additions & 0 deletions pkg/ldapserver/modifydn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ldapserver

import (
"errors"
"fmt"
"net"

ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)

func HandleModifyDNRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
if boundDN == "" {
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
}

modDNReq, err := parseModifyDNRequest(req)
if err != nil {
return err
}

fnNames := []string{}
for k := range server.ModifyDNFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(modDNReq.DN, fnNames)
var rename Renamer
if rename = server.ModifyDNFns[fn]; rename == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", modDNReq.DN)
} else {
err = fmt.Errorf("handler '%s' does not support rename", fn)
}
return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := rename.ModifyDN(boundDN, modDNReq, conn)
return ldap.NewError(uint16(code), err)
return ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid ModifyDN request"))
}

func parseModifyDNRequest(req *ber.Packet) (*ldap.ModifyDNRequest, error) {
modDNReq := ldap.ModifyDNRequest{}
// LDAP ModifyDN requests have up to 4 Elements (DN, newRDN, deleteOld flag and new superior)
if len(req.Children) < 3 || len(req.Children) > 4 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid ModifyDN request"))
}

dn, ok := req.Children[0].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry DN"))
}

_, err := ldap.ParseDN(dn)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
modDNReq.DN = dn

newRDN, ok := req.Children[1].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry new RDN"))
}

_, err = ldap.ParseDN(newRDN)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
modDNReq.NewRDN = newRDN

removeOld, ok := req.Children[2].Value.(bool)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding 'deleteOldRDN` flag"))
}

modDNReq.DeleteOldRDN = removeOld

// moving to a new subtree is not yet supported
if len(req.Children) == 4 {
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, errors.New("moving to 'newSuperior' is not implemented"))
}

return &modDNReq, nil
}
27 changes: 26 additions & 1 deletion pkg/ldapserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type PasswordUpdater interface {
ModifyPasswordExop(boundDN string, req *ldap.PasswordModifyRequest, conn net.Conn) (LDAPResultCode, error)
}

type Renamer interface {
ModifyDN(boundDN string, req *ldap.ModifyDNRequest, conn net.Conn) (LDAPResultCode, error)
}

type Searcher interface {
Search(boundDN string, req *ldap.SearchRequest, conn net.Conn) (ServerSearchResult, error)
}
Expand All @@ -56,6 +60,7 @@ type Server struct {
BindFns map[string]Binder
DeleteFns map[string]Deleter
ModifyFns map[string]Modifier
ModifyDNFns map[string]Renamer
PasswordExOpFns map[string]PasswordUpdater
SearchFns map[string]Searcher
CloseFns map[string]Closer
Expand All @@ -81,6 +86,7 @@ func NewServer() *Server {
s.BindFns = make(map[string]Binder)
s.DeleteFns = make(map[string]Deleter)
s.ModifyFns = make(map[string]Modifier)
s.ModifyDNFns = make(map[string]Renamer)
s.PasswordExOpFns = make(map[string]PasswordUpdater)
s.SearchFns = make(map[string]Searcher)
s.CloseFns = make(map[string]Closer)
Expand Down Expand Up @@ -112,6 +118,10 @@ func (server *Server) ModifyFunc(baseDN string, f Modifier) {
server.ModifyFns[baseDN] = f
}

func (server *Server) ModifyDNFunc(baseDN string, f Renamer) {
server.ModifyDNFns[baseDN] = f
}

func (server *Server) PasswordExOpFunc(baseDN string, f PasswordUpdater) {
server.PasswordExOpFns[baseDN] = f
}
Expand Down Expand Up @@ -354,7 +364,22 @@ handler:
}

case ldap.ApplicationModifyDNRequest:
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationModifyDNResponse, ldap.LDAPResultOperationsError, "Unsupported operation: modify DN")
server.Stats.countModifyDNs(1)
resultCode := uint16(ldap.LDAPResultSuccess)
resultMsg := ""
if err = HandleModifyDNRequest(req, boundDN, server, conn); err != nil {
var lErr *ldap.Error
if errors.As(err, &lErr) {
resultCode = lErr.ResultCode
if lErr.Err != nil {
resultMsg = lErr.Err.Error()
}
} else {
resultCode = ldap.LDAPResultOperationsError
resultMsg = err.Error()
}
}
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationModifyDNResponse, LDAPResultCode(resultCode), resultMsg)
if err = sendPacket(conn, responsePacket); err != nil {
logger.Error(err, "sendPacket error")
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/ldapserver/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Stats struct {
Adds uint64
Binds uint64
Deletes uint64
ModifyDNs uint64
Modifies uint64
Unbinds uint64
Searches uint64
Expand Down Expand Up @@ -66,6 +67,14 @@ func (stats *Stats) countDeletes(delta uint64) {
}
}

func (stats *Stats) countModifyDNs(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.ModifyDNs += delta
stats.statsMutex.Unlock()
}
}

func (stats *Stats) countModifies(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
Expand Down Expand Up @@ -100,6 +109,7 @@ func (stats *Stats) Clone() *Stats {
s2.Adds = stats.Adds
s2.Binds = stats.Binds
s2.Deletes = stats.Deletes
s2.ModifyDNs = stats.ModifyDNs
s2.Modifies = stats.Modifies
s2.Unbinds = stats.Unbinds
s2.Searches = stats.Searches
Expand Down
71 changes: 71 additions & 0 deletions pkg/ldbbolt/ldbbolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,77 @@ func (bdb *LdbBolt) entryModifyWithTxn(tx *bolt.Tx, id uint64, entry *ldap.Entry
return nil
}

func (bdb *LdbBolt) EntryModifyDN(req *ldap.ModifyDNRequest) error {
olddn, err := ldap.ParseDN(req.DN)
if err != nil {
return err
}

newrdn, err := ldap.ParseDN(req.NewRDN)
if err != nil {
return err
}

var newDN ldap.DN

newDN.RDNs = []*ldap.RelativeDN{newrdn.RDNs[0]}
newDN.RDNs = append(newDN.RDNs, olddn.RDNs[1:]...)

err = bdb.db.Update(func(tx *bolt.Tx) error {
flatNewDN := ldapdn.Normalize(&newDN)
flatOldDN := ldapdn.Normalize(olddn)

entry, id, innerErr := bdb.getEntryByDN(tx, flatOldDN)
if innerErr != nil {
return innerErr
}

// error out if there is an entry with the new name already
id = bdb.getIDByDN(tx, flatNewDN)
if id != 0 {
return ErrEntryAlreadyExists
}

// only allow renaming leaf entries
childIds := bdb.getChildrenIDs(tx, id)
if len(childIds) > 0 {
return ErrNonLeafEntry
}

entry.DN = flatNewDN

modReq := ldap.ModifyRequest{
DN: entry.DN,
}

// create modify operation for the change attribute values
if req.DeleteOldRDN {
oldRDN := olddn.RDNs[0]
for _, ava := range oldRDN.Attributes {
modReq.Delete(ava.Type, []string{ava.Value})
}
}
for _, ava := range newrdn.RDNs[0].Attributes {
modReq.Add(ava.Type, []string{ava.Value})
}
innerErr = bdb.entryModifyWithTxn(tx, id, entry, &modReq)
if innerErr != nil {
return innerErr
}

// update the dn2id index
dn2id := tx.Bucket([]byte("dn2id"))
if err := dn2id.Put([]byte(flatNewDN), idToBytes(id)); err != nil {
return err
}
if err := dn2id.Delete([]byte(flatOldDN)); err != nil {
return err
}
return nil
})
return err
}

func (bdb *LdbBolt) UpdatePassword(req *ldap.PasswordModifyRequest) error {
ndn, err := ldapdn.ParseNormalize(req.UserIdentity)
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions server/handler/boltdb/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,34 @@ func (h *boltdbHandler) Modify(boundDN string, req *ldap.ModifyRequest, conn net
return ldap.LDAPResultSuccess, nil
}

func (h *boltdbHandler) ModifyDN(boundDN string, req *ldap.ModifyDNRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "modifyDN",
"bind_dn": boundDN,
"remote_addr": conn.RemoteAddr().String(),
"entrydn": req.DN,
})

if !h.writeAllowed(boundDN) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
logger.Debug("Calling boltdb modify DN")
if err := h.bdb.EntryModifyDN(req); err != nil {
logger.WithError(err).Debug("ldap modifyDN failed")
if errors.Is(err, ldbbolt.ErrEntryAlreadyExists) {
return ldap.LDAPResultEntryAlreadyExists, nil
}
ldapError, ok := err.(*ldap.Error)
if !ok {
return ldap.LDAPResultUnwillingToPerform, err
}
return ldapserver.LDAPResultCode(ldapError.ResultCode), ldapError.Err
}
logger.Debug("modify DN succeeded")
return ldap.LDAPResultSuccess, nil
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}

func (h *boltdbHandler) ModifyPasswordExop(boundDN string, req *ldap.PasswordModifyRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "modpw_exop",
Expand Down
1 change: 1 addition & 0 deletions server/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Handler interface {
ldapserver.Deleter
ldapserver.Modifier
ldapserver.PasswordUpdater
ldapserver.Renamer
ldapserver.Searcher
ldapserver.Closer

Expand Down
4 changes: 4 additions & 0 deletions server/handler/ldif/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ func (h *ldifHandler) Modify(_ string, _ *ldap.ModifyRequest, _ net.Conn) (ldaps
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}

func (h *ldifHandler) ModifyDN(_ string, _ *ldap.ModifyDNRequest, _ net.Conn) (ldapserver.LDAPResultCode, error) {
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}

func (h *ldifHandler) ModifyPasswordExop(_ string, _ *ldap.PasswordModifyRequest, _ net.Conn) (ldapserver.LDAPResultCode, error) {
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}
Expand Down
4 changes: 4 additions & 0 deletions server/handler/ldif/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ func (h *ldifMiddleware) Modify(_ string, _ *ldap.ModifyRequest, _ net.Conn) (ld
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}

func (h *ldifMiddleware) ModifyDN(_ string, _ *ldap.ModifyDNRequest, _ net.Conn) (ldapserver.LDAPResultCode, error) {
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}

func (h *ldifMiddleware) ModifyPasswordExop(_ string, _ *ldap.PasswordModifyRequest, _ net.Conn) (ldapserver.LDAPResultCode, error) {
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func (s *Server) Serve(ctx context.Context) error {
s.LDAPServer.BindFunc("", ldapHandler)
s.LDAPServer.DeleteFunc("", ldapHandler)
s.LDAPServer.ModifyFunc("", ldapHandler)
s.LDAPServer.ModifyDNFunc("", ldapHandler)
s.LDAPServer.PasswordExOpFunc("", ldapHandler)
s.LDAPServer.SearchFunc("", ldapHandler)
s.LDAPServer.CloseFunc("", ldapHandler)
Expand Down

0 comments on commit e07caee

Please sign in to comment.