2020-01-19 11:49:49 +00:00
package main
2020-01-19 21:30:51 +00:00
// @FIXME: Implement a real permission system: limit read/write scope/attributes, possibly based on group membership
// @FIXME: Implement missing search filters (in applyFilter)
// @FIXME: Add an initial prefix to the consul key value
2020-01-26 17:42:04 +00:00
// @FIXME: Add TLS connections
2020-01-19 21:30:51 +00:00
2020-01-19 11:49:49 +00:00
import (
2020-01-19 12:00:53 +00:00
"encoding/base64"
"encoding/json"
2020-01-19 11:49:49 +00:00
"fmt"
"log"
2020-01-19 12:00:53 +00:00
"math/rand"
2020-01-19 11:49:49 +00:00
"os"
"os/signal"
"strings"
2020-01-19 12:00:53 +00:00
"syscall"
2020-01-19 11:49:49 +00:00
2020-01-19 12:00:53 +00:00
ldap "./ldapserver"
2020-01-19 11:49:49 +00:00
consul "github.com/hashicorp/consul/api"
message "github.com/vjeantet/goldap/message"
)
type Config struct {
Suffix string
2020-01-26 17:42:04 +00:00
Acl ACL
2020-01-19 11:49:49 +00:00
}
type Server struct {
config Config
2020-01-19 12:00:53 +00:00
kv * consul . KV
}
type State struct {
2020-01-26 17:42:04 +00:00
login Login
2020-01-19 11:49:49 +00:00
}
2020-01-19 17:24:21 +00:00
type Entry map [ string ] [ ] string
2020-01-19 11:49:49 +00:00
func main ( ) {
//ldap logger
ldap . Logger = log . New ( os . Stdout , "[server] " , log . LstdFlags )
// Connect to Consul
client , err := consul . NewClient ( consul . DefaultConfig ( ) )
if err != nil {
panic ( err )
}
kv := client . KV ( )
2020-01-26 17:42:04 +00:00
aclStr := [ ] string {
// Anybody (before binding) can bind to an entity under ou=users,dc=gobottin,dc=eu
"ANONYMOUS::bind:*,ou=users,dc=gobottin,dc=eu:" ,
// Anybody (before binding) can bind to the specific admin entity
"ANONYMOUS::bind:cn=admin,dc=gobottin,dc=eu:" ,
// Anybody who is logged in can read anything that is not a userpassword attribute
"*,dc=gobottin,dc=eu::read:*:* !userpassword" ,
// Anybody can read and modify anything from their own entry
"*::read modify:SELF:*" ,
// The admin can add, modify, delete anything
"cn=admin,dc=gobottin,dc=eu::add modify delete:*:*" ,
// Members of the admin group can add, modify, delete anything
"*:cn=admin,ou=groups,dc=gobottin,dc=eu:add modify delete:*:*" ,
}
acl , err := ParseACL ( aclStr )
if err != nil {
panic ( err )
}
2020-01-19 11:49:49 +00:00
// TODO read config from somewhere
2020-01-19 12:00:53 +00:00
config := Config {
2020-01-19 11:49:49 +00:00
Suffix : "dc=gobottin,dc=eu" ,
2020-01-26 17:42:04 +00:00
Acl : acl ,
2020-01-19 11:49:49 +00:00
}
gobottin := Server { config : config , kv : kv }
err = gobottin . init ( )
if err != nil {
panic ( err )
}
//Create a new LDAP Server
ldapserver := ldap . NewServer ( )
2020-01-19 12:00:53 +00:00
ldapserver . NewUserState = func ( ) ldap . UserState {
2020-01-26 17:42:04 +00:00
return & State {
login : Login {
user : "ANONYMOUS" ,
groups : [ ] string { } ,
} ,
}
2020-01-19 12:00:53 +00:00
}
2020-01-19 11:49:49 +00:00
routes := ldap . NewRouteMux ( )
routes . Bind ( gobottin . handleBind )
2020-01-19 16:55:25 +00:00
routes . Search ( gobottin . handleSearch )
2020-01-19 17:24:21 +00:00
routes . Add ( gobottin . handleAdd )
2020-01-19 20:48:14 +00:00
routes . Compare ( gobottin . handleCompare )
routes . Delete ( gobottin . handleDelete )
2020-01-19 21:21:05 +00:00
routes . Modify ( gobottin . handleModify )
2020-01-19 11:49:49 +00:00
ldapserver . Handle ( routes )
// listen on 10389
go ldapserver . ListenAndServe ( "127.0.0.1:10389" )
// When CTRL+C, SIGINT and SIGTERM signal occurs
// Then stop server gracefully
ch := make ( chan os . Signal )
signal . Notify ( ch , syscall . SIGINT , syscall . SIGTERM )
<- ch
close ( ch )
ldapserver . Stop ( )
}
func ( server * Server ) init ( ) error {
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( server . config . Suffix )
if err != nil {
return err
}
pair , _ , err := server . kv . Get ( path + "/attribute=objectClass" , nil )
2020-01-19 11:49:49 +00:00
if err != nil {
return err
}
if pair != nil {
return nil
}
2020-01-19 16:55:25 +00:00
base_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "top" , "dcObject" , "organization" } ,
2020-01-19 17:24:21 +00:00
"structuralObjectClass" : [ ] string { "Organization" } ,
2020-01-19 11:49:49 +00:00
}
suffix_dn , err := parseDN ( server . config . Suffix )
if err != nil {
return err
}
2020-01-19 17:24:21 +00:00
base_attributes [ suffix_dn [ 0 ] . Type ] = [ ] string { suffix_dn [ 0 ] . Value }
2020-01-19 11:49:49 +00:00
err = server . addElements ( server . config . Suffix , base_attributes )
if err != nil {
return err
}
admin_pass := make ( [ ] byte , 8 )
rand . Read ( admin_pass )
admin_pass_str := base64 . RawURLEncoding . EncodeToString ( admin_pass )
admin_pass_hash := SSHAEncode ( [ ] byte ( admin_pass_str ) )
admin_dn := "cn=admin," + server . config . Suffix
2020-01-19 16:55:25 +00:00
admin_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "simpleSecurityObject" , "organizationalRole" } ,
2020-01-19 17:24:21 +00:00
"description" : [ ] string { "LDAP administrator" } ,
"cn" : [ ] string { "admin" } ,
"userpassword" : [ ] string { admin_pass_hash } ,
"structuralObjectClass" : [ ] string { "organizationalRole" } ,
2020-01-19 12:00:53 +00:00
"permissions" : [ ] string { "read" , "write" } ,
2020-01-19 11:49:49 +00:00
}
err = server . addElements ( admin_dn , admin_attributes )
if err != nil {
return err
}
log . Printf (
"It seems to be a new installation, we created a default user for you:\n\n dn: %s\n password: %s\n\nWe didn't use true random, you should replace it as soon as possible." ,
admin_dn ,
admin_pass_str ,
)
return nil
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) addElements ( dn string , attrs Entry ) error {
2020-01-19 21:27:54 +00:00
prefix , err := dnToConsul ( dn )
if err != nil {
return err
}
2020-01-19 11:49:49 +00:00
for k , v := range attrs {
2020-01-20 08:11:30 +00:00
path := prefix + "/attribute=" + k
if len ( v ) == 0 {
// If we have zero values, delete associated k/v pair
_ , err := server . kv . Delete ( path , nil )
if err != nil {
return err
}
} else {
json , err := json . Marshal ( v )
if err != nil {
return err
}
pair := & consul . KVPair { Key : path , Value : json }
_ , err = server . kv . Put ( pair , nil )
if err != nil {
return err
}
2020-01-19 11:49:49 +00:00
}
}
return nil
}
2020-01-19 18:10:38 +00:00
func ( server * Server ) getAttribute ( dn string , attr string ) ( [ ] string , error ) {
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( dn )
if err != nil {
return nil , err
}
2020-01-26 16:45:04 +00:00
pair , _ , err := server . kv . Get ( path + "/attribute=" + attr , nil )
2020-01-19 18:10:38 +00:00
if err != nil {
return nil , err
}
if pair == nil {
return nil , nil
}
return parseValue ( pair . Value )
}
func ( server * Server ) objectExists ( dn string ) ( bool , error ) {
2020-01-19 21:27:54 +00:00
prefix , err := dnToConsul ( dn )
if err != nil {
return false , err
}
2020-01-19 18:10:38 +00:00
2020-01-26 16:45:04 +00:00
data , _ , err := server . kv . List ( prefix + "/" , nil )
2020-01-19 18:10:38 +00:00
if err != nil {
return false , err
}
return len ( data ) > 0 , nil
}
2020-01-19 18:19:34 +00:00
func ( server * Server ) checkSuffix ( dn string , allow_extend bool ) ( string , error ) {
suffix := server . config . Suffix
if len ( dn ) < len ( suffix ) {
if dn != suffix [ - len ( dn ) : ] || ! allow_extend {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return suffix , nil
} else {
if dn [ len ( dn ) - len ( suffix ) : ] != suffix {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return dn , nil
}
}
2020-01-19 12:00:53 +00:00
func ( server * Server ) handleBind ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
2020-01-19 11:49:49 +00:00
r := m . GetBindRequest ( )
2020-01-19 17:24:21 +00:00
result_code , err := server . handleBindInternal ( state , & r )
2020-01-19 11:49:49 +00:00
res := ldap . NewBindResponse ( result_code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
log . Printf ( "Failed bind for %s: %s" , string ( r . Name ( ) ) , err . Error ( ) )
}
w . Write ( res )
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleBindInternal ( state * State , r * message . BindRequest ) ( int , error ) {
2020-01-26 17:42:04 +00:00
// Check permissions
if ! server . config . Acl . Check ( & state . login , "bind" , string ( r . Name ( ) ) , [ ] string { } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
// Try to retrieve password and check for match
2020-01-19 18:10:38 +00:00
passwd , err := server . getAttribute ( string ( r . Name ( ) ) , "userpassword" )
2020-01-19 11:49:49 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if passwd == nil {
2020-01-19 11:49:49 +00:00
return ldap . LDAPResultNoSuchObject , nil
}
2020-01-19 18:10:38 +00:00
for _ , hash := range passwd {
valid := SSHAMatches ( hash , [ ] byte ( r . AuthenticationSimple ( ) ) )
if valid {
2020-01-26 17:42:04 +00:00
groups , err := server . getAttribute ( string ( r . Name ( ) ) , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
state . login = Login {
user : string ( r . Name ( ) ) ,
groups : groups ,
}
2020-01-19 18:10:38 +00:00
return ldap . LDAPResultSuccess , nil
}
2020-01-19 11:49:49 +00:00
}
2020-01-19 18:10:38 +00:00
return ldap . LDAPResultInvalidCredentials , nil
2020-01-19 11:49:49 +00:00
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) handleSearch ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetSearchRequest ( )
code , err := server . handleSearchInternal ( state , w , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . SearchResultDone ( res ) )
}
func ( server * Server ) handleSearchInternal ( state * State , w ldap . ResponseWriter , r * message . SearchRequest ) ( int , error ) {
log . Printf ( "-- SEARCH REQUEST: --" )
log . Printf ( "Request BaseDn=%s" , r . BaseObject ( ) )
log . Printf ( "Request Filter=%s" , r . Filter ( ) )
log . Printf ( "Request FilterString=%s" , r . FilterString ( ) )
log . Printf ( "Request Attributes=%s" , r . Attributes ( ) )
log . Printf ( "Request TimeLimit=%d" , r . TimeLimit ( ) . Int ( ) )
2020-01-26 17:42:04 +00:00
if ! server . config . Acl . Check ( & state . login , "read" , string ( r . BaseObject ( ) ) , [ ] string { } ) {
return ldap . LDAPResultInsufficientAccessRights , fmt . Errorf ( "Please specify a base object on which you have read rights" )
}
2020-01-19 18:19:34 +00:00
baseObject , err := server . checkSuffix ( string ( r . BaseObject ( ) ) , true )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
2020-01-19 18:10:38 +00:00
}
2020-01-19 21:27:54 +00:00
basePath , err := dnToConsul ( baseObject )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-19 16:55:25 +00:00
2020-01-26 16:45:04 +00:00
data , _ , err := server . kv . List ( basePath + "/" , nil )
2020-01-19 16:55:25 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
entries , err := parseConsulResult ( data )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-26 16:45:04 +00:00
log . Printf ( "in %s: %#v" , basePath + "/" , data )
2020-01-19 16:55:25 +00:00
log . Printf ( "%#v" , entries )
for dn , entry := range entries {
2020-01-26 17:42:04 +00:00
// Filter out if we don't match requested filter
2020-01-19 16:55:25 +00:00
matched , err := applyFilter ( entry , r . Filter ( ) )
if err != nil {
2020-01-19 18:51:01 +00:00
return ldap . LDAPResultUnwillingToPerform , err
2020-01-19 16:55:25 +00:00
}
if ! matched {
continue
}
2020-01-26 17:42:04 +00:00
// Filter out if user is not allowed to read this
if ! server . config . Acl . Check ( & state . login , "read" , dn , [ ] string { } ) {
continue
}
2020-01-19 16:55:25 +00:00
e := ldap . NewSearchResultEntry ( dn )
for attr , val := range entry {
// If attribute is not in request, exclude it from returned entry
if len ( r . Attributes ( ) ) > 0 {
found := false
2020-01-19 17:04:42 +00:00
for _ , requested := range r . Attributes ( ) {
if strings . EqualFold ( string ( requested ) , attr ) {
2020-01-19 16:55:25 +00:00
found = true
break
}
}
if ! found {
continue
}
}
2020-01-26 17:42:04 +00:00
// If we are not allowed to read attribute, exclude it from returned entry
if ! server . config . Acl . Check ( & state . login , "read" , dn , [ ] string { attr } ) {
continue
}
2020-01-19 16:55:25 +00:00
// Send result
2020-01-19 17:24:21 +00:00
for _ , v := range val {
2020-01-19 16:55:25 +00:00
e . AddAttribute ( message . AttributeDescription ( attr ) ,
2020-01-19 17:24:21 +00:00
message . AttributeValue ( v ) )
2020-01-19 16:55:25 +00:00
}
}
w . Write ( e )
}
return ldap . LDAPResultSuccess , nil
}
func applyFilter ( entry Entry , filter message . Filter ) ( bool , error ) {
if fAnd , ok := filter . ( message . FilterAnd ) ; ok {
for _ , cond := range fAnd {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if ! res {
return false , nil
}
}
return true , nil
} else if fOr , ok := filter . ( message . FilterOr ) ; ok {
for _ , cond := range fOr {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if res {
return true , nil
}
}
return false , nil
} else if fNot , ok := filter . ( message . FilterNot ) ; ok {
res , err := applyFilter ( entry , fNot . Filter )
if err != nil {
return false , err
}
return ! res , nil
} else if fPresent , ok := filter . ( message . FilterPresent ) ; ok {
what := string ( fPresent )
2020-01-19 17:04:42 +00:00
// Case insensitive search
2020-01-20 08:11:30 +00:00
for desc , values := range entry {
2020-01-19 17:04:42 +00:00
if strings . EqualFold ( what , desc ) {
2020-01-20 08:11:30 +00:00
return len ( values ) > 0 , nil
2020-01-19 17:04:42 +00:00
}
2020-01-19 16:55:25 +00:00
}
return false , nil
} else if fEquality , ok := filter . ( message . FilterEqualityMatch ) ; ok {
desc := string ( fEquality . AttributeDesc ( ) )
target := string ( fEquality . AssertionValue ( ) )
2020-01-19 17:04:42 +00:00
// Case insensitive attribute search
for entry_desc , value := range entry {
if strings . EqualFold ( entry_desc , desc ) {
2020-01-19 17:24:21 +00:00
for _ , val := range value {
if val == target {
return true , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:24:21 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:04:42 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
} else {
return false , fmt . Errorf ( "Unsupported filter: %#v %T" , filter , filter )
}
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleAdd ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetAddRequest ( )
code , err := server . handleAddInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . AddResponse ( res ) )
}
func ( server * Server ) handleAddInternal ( state * State , r * message . AddRequest ) ( int , error ) {
dn := string ( r . Entry ( ) )
2020-01-19 18:19:34 +00:00
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 17:42:04 +00:00
// Check permissions
attrListStr := [ ] string { }
for _ , attribute := range r . Attributes ( ) {
attrListStr = append ( attrListStr , string ( attribute . Type_ ( ) ) )
}
if ! server . config . Acl . Check ( & state . login , "add" , dn , attrListStr ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
// Check that object does not already exist
2020-01-19 18:10:38 +00:00
exists , err := server . objectExists ( dn )
2020-01-19 17:24:21 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if exists {
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultEntryAlreadyExists , nil
}
2020-01-26 17:42:04 +00:00
// Add object
2020-01-19 20:26:44 +00:00
2020-01-26 17:42:04 +00:00
// If adding a group, track of who the members will be so that their memberOf field can be updated later
2020-01-19 18:10:38 +00:00
var members [ ] string = nil
2020-01-19 17:24:21 +00:00
entry := Entry { }
for _ , attribute := range r . Attributes ( ) {
key := string ( attribute . Type_ ( ) )
vals_str := [ ] string { }
for _ , val := range attribute . Vals ( ) {
vals_str = append ( vals_str , string ( val ) )
}
2020-01-19 20:26:44 +00:00
// Fail if they are trying to write memberOf, we manage this ourselves
if strings . EqualFold ( key , "memberOf" ) {
return ldap . LDAPResultObjectClassViolation , fmt . Errorf (
"memberOf cannot be defined directly, membership must be specified in the group itself" )
}
// If they are writing a member key, we have to check they are adding valid members
2020-01-19 18:10:38 +00:00
if strings . EqualFold ( key , "member" ) {
members = vals_str
for _ , member := range members {
2020-01-19 18:19:34 +00:00
_ , err := server . checkSuffix ( member , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-19 18:10:38 +00:00
exists , err = server . objectExists ( member )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf (
"Cannot add %s to members, it does not exist!" ,
member )
}
}
}
2020-01-19 17:24:21 +00:00
entry [ key ] = vals_str
}
err = server . addElements ( dn , entry )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if members != nil {
for _ , member := range members {
memberGroups , err := server . getAttribute ( member , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberGroups == nil {
memberGroups = [ ] string { }
}
2020-01-19 20:26:44 +00:00
alreadyMember := false
for _ , mb := range memberGroups {
if mb == dn {
alreadyMember = true
log . Printf ( "Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!" ,
member , dn )
break
}
}
if ! alreadyMember {
memberGroups = append ( memberGroups , dn )
err = server . addElements ( member , Entry {
"memberOf" : memberGroups ,
} )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
}
}
}
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultSuccess , nil
}
2020-01-19 20:48:14 +00:00
func ( server * Server ) handleCompare ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetCompareRequest ( )
code , err := server . handleCompareInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . CompareResponse ( res ) )
}
func ( server * Server ) handleCompareInternal ( state * State , r * message . CompareRequest ) ( int , error ) {
dn := string ( r . Entry ( ) )
attr := string ( r . Ava ( ) . AttributeDesc ( ) )
expected := string ( r . Ava ( ) . AssertionValue ( ) )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 17:42:04 +00:00
// Check permissions
if ! server . config . Acl . Check ( & state . login , dn , "read" , [ ] string { attr } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
2020-01-19 20:48:14 +00:00
2020-01-26 17:42:04 +00:00
// Do query
2020-01-19 20:48:14 +00:00
exists , err := server . objectExists ( dn )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
values , err := server . getAttribute ( dn , attr )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
for _ , v := range values {
if v == expected {
return ldap . LDAPResultCompareTrue , nil
}
}
return ldap . LDAPResultCompareFalse , nil
}
func ( server * Server ) handleDelete ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetDeleteRequest ( )
code , err := server . handleDeleteInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . DelResponse ( res ) )
}
func ( server * Server ) handleDeleteInternal ( state * State , r * message . DelRequest ) ( int , error ) {
dn := string ( * r )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 17:42:04 +00:00
// Check for delete permission
if ! server . config . Acl . Check ( & state . login , "delete" , dn , [ ] string { } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
2020-01-19 20:48:14 +00:00
// Check that this LDAP entry exists and has no children
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( dn )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 16:45:04 +00:00
items , _ , err := server . kv . List ( path + "/" , nil )
2020-01-19 20:48:14 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if len ( items ) == 0 {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
for _ , item := range items {
2020-01-26 16:45:04 +00:00
itemDN , _ := consulToDN ( item . Key )
2020-01-19 20:48:14 +00:00
if itemDN != dn {
return ldap . LDAPResultNotAllowedOnNonLeaf , fmt . Errorf (
"Cannot delete %d as it has children" , dn )
}
}
// Retrieve group membership before we delete everything
memberOf , err := server . getAttribute ( dn , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
// Delete the LDAP entry
2020-01-26 16:45:04 +00:00
_ , err = server . kv . DeleteTree ( path + "/" , nil )
2020-01-19 20:48:14 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
// Delete it from the member list of all the groups it was a member of
if memberOf != nil {
for _ , group := range memberOf {
2020-01-26 16:45:04 +00:00
groupMembers , err := server . getAttribute ( dn , "member" )
2020-01-19 20:48:14 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
newMembers := [ ] string { }
for _ , memb := range groupMembers {
if memb != dn {
newMembers = append ( newMembers , memb )
}
}
err = server . addElements ( group , Entry {
"member" : newMembers ,
} )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
}
return ldap . LDAPResultSuccess , nil
}
2020-01-19 21:21:05 +00:00
func ( server * Server ) handleModify ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetModifyRequest ( )
code , err := server . handleModifyInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . ModifyResponse ( res ) )
}
func ( server * Server ) handleModifyInternal ( state * State , r * message . ModifyRequest ) ( int , error ) {
dn := string ( r . Object ( ) )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 17:42:04 +00:00
// First permission check with no particular attributes
if ! server . config . Acl . Check ( & state . login , "modify" , dn , [ ] string { } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
2020-01-19 21:21:05 +00:00
// Retrieve previous values (by the way, check object exists)
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( dn )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-26 16:45:04 +00:00
items , _ , err := server . kv . List ( path + "/attribute=" , nil )
2020-01-19 21:21:05 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if len ( items ) == 0 {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
prevEntry := Entry { }
for _ , item := range items {
2020-01-26 16:45:04 +00:00
itemDN , attr := consulToDN ( item . Key )
2020-01-19 21:21:05 +00:00
if itemDN != dn {
panic ( "itemDN != dn in handleModifyInternal" )
}
2020-01-26 16:45:04 +00:00
vals , err := parseValue ( item . Value )
2020-01-19 21:21:05 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
prevEntry [ attr ] = vals
}
// Keep track of group members added/deleted
addMembers , delMembers := [ ] string { } , [ ] string { }
// Produce new entry values to be saved
newEntry := Entry { }
for _ , change := range r . Changes ( ) {
attr := string ( change . Modification ( ) . Type_ ( ) )
values := change . Modification ( ) . Vals ( )
2020-01-26 17:42:04 +00:00
// Check for permission to modify this attribute
if ! server . config . Acl . Check ( & state . login , "modify" , dn , [ ] string { attr } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
2020-01-19 21:21:05 +00:00
if change . Operation ( ) == ldap . ModifyRequestChangeOperationAdd {
newEntry [ attr ] = prevEntry [ attr ]
for _ , val := range values {
present := false
for _ , prevVal := range newEntry [ attr ] {
if prevVal == string ( val ) {
present = true
break
}
}
if ! present {
newEntry [ attr ] = append ( newEntry [ attr ] , string ( val ) )
if strings . EqualFold ( attr , "member" ) {
addMembers = append ( addMembers , string ( val ) )
}
}
}
} else if change . Operation ( ) == ldap . ModifyRequestChangeOperationDelete {
if len ( values ) == 0 {
// Delete everything
newEntry [ attr ] = [ ] string { }
if strings . EqualFold ( attr , "member" ) {
delMembers = append ( delMembers , prevEntry [ attr ] ... )
}
} else {
// Delete only those specified
newEntry [ attr ] = [ ] string { }
for _ , prevVal := range prevEntry [ attr ] {
keep := true
for _ , delVal := range values {
if string ( delVal ) == prevVal {
keep = false
break
}
}
if keep {
newEntry [ attr ] = append ( newEntry [ attr ] , prevVal )
} else {
if strings . EqualFold ( attr , "member" ) {
delMembers = append ( delMembers , prevVal )
}
}
}
}
} else if change . Operation ( ) == ldap . ModifyRequestChangeOperationReplace {
newEntry [ attr ] = [ ] string { }
for _ , newVal := range values {
newEntry [ attr ] = append ( newEntry [ attr ] , string ( newVal ) )
}
if strings . EqualFold ( attr , "member" ) {
for _ , newMem := range newEntry [ attr ] {
mustAdd := true
for _ , prevMem := range prevEntry [ attr ] {
if prevMem == newMem {
mustAdd = false
break
}
}
if mustAdd {
addMembers = append ( addMembers , newMem )
}
}
for _ , prevMem := range prevEntry [ attr ] {
mustDel := true
for _ , newMem := range newEntry [ attr ] {
if newMem == prevMem {
mustDel = false
break
}
}
if mustDel {
delMembers = append ( delMembers , prevMem )
}
}
}
}
}
// Check that added members actually exist
for _ , addMem := range addMembers {
exists , err := server . objectExists ( addMem )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf (
"Cannot add member %s, it does not exist" , addMem )
}
}
// Save the edited values
server . addElements ( dn , newEntry )
// Update memberOf for added members and deleted members
for _ , addMem := range addMembers {
memberOf , err := server . getAttribute ( addMem , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberOf == nil {
memberOf = [ ] string { }
}
memberOf = append ( memberOf , dn )
err = server . addElements ( addMem , Entry { "memberOf" : memberOf } )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
for _ , delMem := range delMembers {
memberOf , err := server . getAttribute ( delMem , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberOf == nil {
memberOf = [ ] string { }
}
newMemberOf := [ ] string { }
for _ , g := range memberOf {
if g != dn {
newMemberOf = append ( newMemberOf , g )
}
}
err = server . addElements ( delMem , Entry { "memberOf" : newMemberOf } )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
return ldap . LDAPResultSuccess , nil
}