191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2021 The LibreGraph Authors.
|
|
*/
|
|
|
|
package ldif
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/go-ldap/ldap/v3"
|
|
"github.com/go-ldap/ldif"
|
|
"github.com/spacewander/go-suffix-tree"
|
|
)
|
|
|
|
// parseLDIFFile opens the named file for reading and parses it as LDIF.
|
|
func parseLDIFFile(fn string, options *Options) (*ldif.LDIF, error) {
|
|
f, err := os.Open(fn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var r io.Reader
|
|
|
|
if options.TemplateEngineDisabled {
|
|
r = f
|
|
} else {
|
|
r, err = parseLDIFTemplate(f, options, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return parseLDIF(r, options)
|
|
}
|
|
|
|
// parseLDIFDirectory opens all ldif files in the given path in sorted order,
|
|
// cats them all together and parses the result as LDIF.
|
|
func parseLDIFDirectory(pn string, options *Options) (*ldif.LDIF, []error, error) {
|
|
matches, err := filepath.Glob(filepath.Join(pn, "*.ldif"))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sort.Slice(matches, func(i, j int) bool {
|
|
return matches[i] < matches[j]
|
|
})
|
|
|
|
var buf bytes.Buffer
|
|
var matchErrors []error
|
|
for _, match := range matches {
|
|
err = func() error {
|
|
f, openErr := os.Open(match)
|
|
if openErr != nil {
|
|
matchErrors = append(matchErrors, fmt.Errorf("file read error: %w", openErr))
|
|
return nil
|
|
}
|
|
defer f.Close()
|
|
if options.TemplateEngineDisabled {
|
|
_, copyErr := io.Copy(&buf, f)
|
|
if copyErr != nil {
|
|
return fmt.Errorf("file read error: %w", copyErr)
|
|
}
|
|
} else {
|
|
p, parseErr := parseLDIFTemplate(f, options, nil)
|
|
if parseErr != nil {
|
|
matchErrors = append(matchErrors, fmt.Errorf("parse error in %s: %w", match, parseErr))
|
|
return nil
|
|
}
|
|
_, copyErr := io.Copy(&buf, p)
|
|
if copyErr != nil {
|
|
return fmt.Errorf("template read error: %w", copyErr)
|
|
}
|
|
}
|
|
buf.WriteString("\n\n")
|
|
return nil
|
|
}()
|
|
if err != nil {
|
|
return nil, matchErrors, err
|
|
}
|
|
}
|
|
|
|
l, err := parseLDIF(&buf, options)
|
|
return l, matchErrors, err
|
|
}
|
|
|
|
// parseLDIFTemplate exectues the provided text template and then parses the
|
|
// result as LDIF.
|
|
func parseLDIFTemplate(r io.Reader, options *Options, m map[string]interface{}) (io.Reader, error) {
|
|
scanner := bufio.NewScanner(r)
|
|
scanner.Split(bufio.ScanLines)
|
|
var text []string
|
|
for scanner.Scan() {
|
|
t := scanner.Text()
|
|
if t != "" && t[0] == '#' {
|
|
// Ignore commented lines.
|
|
continue
|
|
}
|
|
text = append(text, scanner.Text())
|
|
}
|
|
|
|
if m == nil {
|
|
m = make(map[string]interface{})
|
|
}
|
|
tpl, err := template.New("tpl").Funcs(TemplateFuncs(m, options)).Parse(strings.Join(text, "\n"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse LDIF template: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = tpl.Execute(&buf, m)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process LDIF template: %w", err)
|
|
}
|
|
|
|
if options.TemplateDebug {
|
|
fmt.Println("---\n", buf.String(), "\n----")
|
|
}
|
|
|
|
return &buf, nil
|
|
}
|
|
|
|
func parseLDIF(r io.Reader, options *Options) (*ldif.LDIF, error) {
|
|
l := &ldif.LDIF{}
|
|
err := ldif.Unmarshal(r, l)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
// treeFromLDIF makes a tree out of the provided LDIF and if index is not nil,
|
|
// also indexes each entry in the provided index.
|
|
func treeFromLDIF(l *ldif.LDIF, index Index, options *Options) (*suffix.Tree, error) {
|
|
t := suffix.NewTree()
|
|
|
|
// NOTE(longsleep): Create in memory tree records from LDIF data.
|
|
var entry *ldap.Entry
|
|
for _, entryRecord := range l.Entries {
|
|
if entryRecord == nil || entryRecord.Entry == nil {
|
|
// NOTE(longsleep): We don't use l.AllEntries as "nil" records can happen.
|
|
continue
|
|
}
|
|
entry = entryRecord.Entry
|
|
e := &ldifEntry{
|
|
Entry: &ldap.Entry{
|
|
DN: strings.ToLower(entry.DN),
|
|
},
|
|
}
|
|
for _, a := range entry.Attributes {
|
|
switch strings.ToLower(a.Name) {
|
|
case "userpassword":
|
|
// Don't include the password in the normal attributes.
|
|
e.UserPassword = &ldap.EntryAttribute{
|
|
Name: a.Name,
|
|
Values: a.Values,
|
|
}
|
|
default:
|
|
// Append it.
|
|
e.Entry.Attributes = append(e.Entry.Attributes, &ldap.EntryAttribute{
|
|
Name: a.Name,
|
|
Values: a.Values,
|
|
})
|
|
}
|
|
if index != nil {
|
|
// Index equalityMatch.
|
|
index.Add(a.Name, "eq", a.Values, e)
|
|
// Index present.
|
|
index.Add(a.Name, "pres", []string{""}, e)
|
|
// Index substrings.
|
|
index.Add(a.Name, "sub", a.Values, e)
|
|
}
|
|
}
|
|
v, ok := t.Insert([]byte(e.DN), e)
|
|
if !ok || v != nil {
|
|
return nil, fmt.Errorf("duplicate dn value: %s", e.DN)
|
|
}
|
|
}
|
|
|
|
return t, nil
|
|
}
|