Initial QSfera import
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.11
|
||||
- 1.13
|
||||
- 1.14
|
||||
- tip
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
go_import_path: github.com/go-ldap/ldif
|
||||
install:
|
||||
- go get github.com/go-ldap/ldap/v3
|
||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
||||
- go get golang.org/x/lint/golint || true
|
||||
- go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
||||
- go build -v ./...
|
||||
script:
|
||||
- make test
|
||||
- make fmt
|
||||
- make vet
|
||||
- if [ "${TRAVIS_GO_VERSION}" != "1.6" ]; then make lint; fi
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 go-ldap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
.PHONY: default install build test quicktest fmt vet lint
|
||||
|
||||
default: fmt vet lint build quicktest
|
||||
|
||||
install:
|
||||
go get -t -v ./...
|
||||
|
||||
build:
|
||||
go build -v ./...
|
||||
|
||||
test:
|
||||
go test -v -cover ./...
|
||||
|
||||
quicktest:
|
||||
go test ./...
|
||||
|
||||
# Capture output and force failure when there is non-empty output
|
||||
fmt:
|
||||
@echo gofmt -l .
|
||||
@OUTPUT=`gofmt -l . 2>&1`; \
|
||||
if [ "$$OUTPUT" ]; then \
|
||||
echo "gofmt must be run on the following files:"; \
|
||||
echo "$$OUTPUT"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Only run on go1.5+
|
||||
vet:
|
||||
go vet -vettool=$(which shadow) -atomic -bool -copylocks -nilfunc -printf -rangeloops -unreachable -unsafeptr -unusedresult .
|
||||
|
||||
# https://github.com/golang/lint
|
||||
# go get github.com/golang/lint/golint
|
||||
# Capture output and force failure when there is non-empty output
|
||||
lint:
|
||||
@echo golint ./...
|
||||
@OUTPUT=`golint ./... 2>&1`; \
|
||||
if [ "$$OUTPUT" ]; then \
|
||||
echo "golint errors:"; \
|
||||
echo "$$OUTPUT"; \
|
||||
exit 1; \
|
||||
fi
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# ldif
|
||||
|
||||
Utilities for working with ldif data. This implements most of RFC 2849.
|
||||
|
||||
## Change Entries
|
||||
|
||||
Support for moddn / modrdn changes is missing (in Unmarshal and
|
||||
Marshal) - github.com/go-ldap/ldap/v3 does not support it currently
|
||||
|
||||
## Controls
|
||||
|
||||
Only simple controls without control value are supported, currently
|
||||
just
|
||||
Manage DSA IT - oid: 2.16.840.1.113730.3.4.2
|
||||
|
||||
## URLs
|
||||
|
||||
URL schemes in an LDIF like
|
||||
jpegPhoto;binary:< file:///usr/share/photos/someone.jpg
|
||||
are only supported for the "file" scheme like in the example above
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package ldif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
// Apply sends the LDIF entries to the server and does the changes as
|
||||
// given by the entries.
|
||||
//
|
||||
// All *ldap.Entry are converted to an *ldap.AddRequest.
|
||||
//
|
||||
// By default, it returns on the first error. To continue with applying the
|
||||
// LDIF, set the continueOnErr argument to true - in this case the errors
|
||||
// are logged with log.Printf()
|
||||
func (l *LDIF) Apply(conn ldap.Client, continueOnErr bool) error {
|
||||
for _, entry := range l.Entries {
|
||||
switch {
|
||||
case entry.Entry != nil:
|
||||
add := ldap.NewAddRequest(entry.Entry.DN, entry.Add.Controls)
|
||||
for _, attr := range entry.Entry.Attributes {
|
||||
add.Attribute(attr.Name, attr.Values)
|
||||
}
|
||||
entry.Add = add
|
||||
fallthrough
|
||||
case entry.Add != nil:
|
||||
if err := conn.Add(entry.Add); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to add %s: %s", entry.Add.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to add %s: %s", entry.Add.DN, err)
|
||||
}
|
||||
|
||||
case entry.Del != nil:
|
||||
if err := conn.Del(entry.Del); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to delete %s: %s", entry.Del.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to delete %s: %s", entry.Del.DN, err)
|
||||
}
|
||||
|
||||
case entry.Modify != nil:
|
||||
if err := conn.Modify(entry.Modify); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to modify %s: %s", entry.Modify.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to modify %s: %s", entry.Modify.DN, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package ldif contains utilities for working with ldif data
|
||||
package ldif
|
||||
+534
@@ -0,0 +1,534 @@
|
||||
// Package ldif contains an LDIF parser and marshaller (RFC 2849).
|
||||
package ldif
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
// Entry is one entry in the LDIF
|
||||
type Entry struct {
|
||||
Entry *ldap.Entry
|
||||
Add *ldap.AddRequest
|
||||
Del *ldap.DelRequest
|
||||
Modify *ldap.ModifyRequest
|
||||
}
|
||||
|
||||
// The LDIF struct is used for parsing an LDIF. The Controls
|
||||
// is used to tell the parser to ignore any controls found
|
||||
// when parsing (default: false to ignore the controls).
|
||||
// FoldWidth is used for the line lenght when marshalling.
|
||||
type LDIF struct {
|
||||
Entries []*Entry
|
||||
Version int
|
||||
changeType string
|
||||
FoldWidth int
|
||||
Controls bool
|
||||
firstEntry bool
|
||||
}
|
||||
|
||||
// The ParseError holds the error message and the line in the ldif
|
||||
// where the error occurred.
|
||||
type ParseError struct {
|
||||
Line int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *ParseError) Error() string {
|
||||
return fmt.Sprintf("Error in line %d: %s", e.Line, e.Message)
|
||||
}
|
||||
|
||||
var cr byte = '\x0D'
|
||||
var lf byte = '\x0A'
|
||||
var sep = string([]byte{cr, lf})
|
||||
var comment byte = '#'
|
||||
var space byte = ' '
|
||||
var spaces = string(space)
|
||||
|
||||
// Parse wraps Unmarshal to parse an LDIF from a string
|
||||
func Parse(str string) (l *LDIF, err error) {
|
||||
buf := bytes.NewBuffer([]byte(str))
|
||||
l = &LDIF{}
|
||||
err = Unmarshal(buf, l)
|
||||
return
|
||||
}
|
||||
|
||||
// ParseWithControls wraps Unmarshal to parse an LDIF from
|
||||
// a string, controls are added to change records
|
||||
func ParseWithControls(str string) (l *LDIF, err error) {
|
||||
buf := bytes.NewBuffer([]byte(str))
|
||||
l = &LDIF{Controls: true}
|
||||
err = Unmarshal(buf, l)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal parses the LDIF from the given io.Reader into the LDIF struct.
|
||||
// The caller is responsible for closing the io.Reader if that is
|
||||
// needed.
|
||||
func Unmarshal(r io.Reader, l *LDIF) (err error) {
|
||||
if r == nil {
|
||||
return &ParseError{Line: 0, Message: "No reader present"}
|
||||
}
|
||||
curLine := 0
|
||||
l.Version = 0
|
||||
l.changeType = ""
|
||||
isComment := false
|
||||
|
||||
reader := bufio.NewReader(r)
|
||||
|
||||
var lines []string
|
||||
var line, nextLine string
|
||||
l.firstEntry = true
|
||||
|
||||
for {
|
||||
curLine++
|
||||
nextLine, err = reader.ReadString(lf)
|
||||
nextLine = strings.TrimRight(nextLine, sep)
|
||||
|
||||
switch err {
|
||||
case nil, io.EOF:
|
||||
switch len(nextLine) {
|
||||
case 0:
|
||||
if len(line) == 0 && err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if len(line) == 0 && len(lines) == 0 {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
entry, perr := l.parseEntry(lines)
|
||||
if perr != nil {
|
||||
return &ParseError{Line: curLine, Message: perr.Error()}
|
||||
}
|
||||
l.Entries = append(l.Entries, entry)
|
||||
line = ""
|
||||
lines = []string{}
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
switch nextLine[0] {
|
||||
case comment:
|
||||
isComment = true
|
||||
continue
|
||||
|
||||
case space:
|
||||
if isComment {
|
||||
continue
|
||||
}
|
||||
line += nextLine[1:]
|
||||
continue
|
||||
|
||||
default:
|
||||
isComment = false
|
||||
if len(line) != 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
line = nextLine
|
||||
continue
|
||||
}
|
||||
}
|
||||
default:
|
||||
return &ParseError{Line: curLine, Message: err.Error()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LDIF) parseEntry(lines []string) (entry *Entry, err error) {
|
||||
if len(lines) == 0 {
|
||||
return nil, errors.New("empty entry?")
|
||||
}
|
||||
|
||||
if l.firstEntry && strings.HasPrefix(lines[0], "version:") {
|
||||
l.firstEntry = false
|
||||
line := strings.TrimLeft(lines[0][8:], spaces)
|
||||
if l.Version, err = strconv.Atoi(line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l.Version != 1 {
|
||||
return nil, errors.New("Invalid version spec " + string(line))
|
||||
}
|
||||
|
||||
l.Version = 1
|
||||
if len(lines) == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
lines = lines[1:]
|
||||
}
|
||||
l.firstEntry = false
|
||||
|
||||
if len(lines) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(lines[0], "dn:") {
|
||||
return nil, errors.New("missing 'dn:'")
|
||||
}
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dn := val
|
||||
|
||||
if len(lines) == 1 {
|
||||
return nil, errors.New("only a dn: line")
|
||||
}
|
||||
lines = lines[1:]
|
||||
|
||||
var controls []ldap.Control
|
||||
controls, lines, err = l.parseControls(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lines[0], "changetype:") {
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.changeType = val
|
||||
if len(lines) > 1 {
|
||||
lines = lines[1:]
|
||||
}
|
||||
}
|
||||
switch l.changeType {
|
||||
case "":
|
||||
if len(controls) != 0 {
|
||||
return nil, errors.New("controls found without changetype")
|
||||
}
|
||||
attrs, err := l.parseAttrs(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Entry{Entry: ldap.NewEntry(dn, attrs)}, nil
|
||||
|
||||
case "add":
|
||||
attrs, err := l.parseAttrs(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// FIXME: controls for add - see https://github.com/go-ldap/ldap/issues/81
|
||||
add := ldap.NewAddRequest(dn, controls)
|
||||
for attr, vals := range attrs {
|
||||
add.Attribute(attr, vals)
|
||||
}
|
||||
return &Entry{Add: add}, nil
|
||||
|
||||
case "delete":
|
||||
if len(lines) > 1 {
|
||||
return nil, errors.New("no attributes allowed for changetype delete")
|
||||
}
|
||||
return &Entry{Del: ldap.NewDelRequest(dn, controls)}, nil
|
||||
|
||||
case "modify":
|
||||
// FIXME: controls for modify - see https://github.com/go-ldap/ldap/issues/81
|
||||
mod := ldap.NewModifyRequest(dn, controls)
|
||||
var op, attribute string
|
||||
var values []string
|
||||
if lines[len(lines)-1] != "-" {
|
||||
return nil, errors.New("modify request does not close with a single dash")
|
||||
}
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if lines[i] == "-" {
|
||||
switch op {
|
||||
case "":
|
||||
return nil, fmt.Errorf("empty operation")
|
||||
case "add":
|
||||
mod.Add(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
case "replace":
|
||||
mod.Replace(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
case "delete":
|
||||
mod.Delete(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation %s in modify request", op)
|
||||
}
|
||||
continue
|
||||
}
|
||||
attr, val, err := l.parseLine(lines[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if op == "" {
|
||||
op = attr
|
||||
attribute = val
|
||||
} else {
|
||||
if attr != attribute {
|
||||
return nil, fmt.Errorf("invalid attribute %s in %s request for %s", attr, op, attribute)
|
||||
}
|
||||
values = append(values, val)
|
||||
}
|
||||
}
|
||||
return &Entry{Modify: mod}, nil
|
||||
|
||||
case "moddn", "modrdn":
|
||||
return nil, fmt.Errorf("unsupported changetype %s", l.changeType)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid changetype %s", l.changeType)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LDIF) parseAttrs(lines []string) (map[string][]string, error) {
|
||||
attrs := make(map[string][]string)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
attr, val, err := l.parseLine(lines[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs[attr] = append(attrs[attr], val)
|
||||
}
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func (l *LDIF) parseLine(line string) (attr, val string, err error) {
|
||||
off := 0
|
||||
for len(line) > off && line[off] != ':' {
|
||||
off++
|
||||
if off >= len(line) {
|
||||
err = fmt.Errorf("Missing : in line `%s`", line)
|
||||
return
|
||||
}
|
||||
}
|
||||
if off == len(line) {
|
||||
err = fmt.Errorf("Missing : in the line `%s`", line)
|
||||
return
|
||||
}
|
||||
|
||||
if off > len(line)-2 {
|
||||
err = errors.New("empty value")
|
||||
// FIXME: this is allowed for some attributes, e.g. seeAlso
|
||||
return
|
||||
}
|
||||
|
||||
attr = line[0:off]
|
||||
if err = validAttr(attr); err != nil {
|
||||
attr = ""
|
||||
val = ""
|
||||
return
|
||||
}
|
||||
|
||||
switch line[off+1] {
|
||||
case ':':
|
||||
val, err = decodeBase64(strings.TrimLeft(line[off+2:], spaces))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case '<':
|
||||
val, err = readURLValue(strings.TrimLeft(line[off+2:], spaces))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
val = strings.TrimLeft(line[off+1:], spaces)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LDIF) parseControls(lines []string) ([]ldap.Control, []string, error) {
|
||||
var controls []ldap.Control
|
||||
for {
|
||||
if !strings.HasPrefix(lines[0], "control:") {
|
||||
break
|
||||
}
|
||||
if !l.Controls {
|
||||
if len(lines) == 1 {
|
||||
return nil, nil, errors.New("only controls found")
|
||||
}
|
||||
lines = lines[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var oid, ctrlValue string
|
||||
criticality := false
|
||||
|
||||
parts := strings.SplitN(val, " ", 3)
|
||||
if err = validOID(parts[0]); err != nil {
|
||||
return nil, nil, fmt.Errorf("%s is not a valid oid: %s", oid, err)
|
||||
}
|
||||
oid = parts[0]
|
||||
|
||||
if len(parts) > 1 {
|
||||
switch parts[1] {
|
||||
case "true":
|
||||
criticality = true
|
||||
if len(parts) > 2 {
|
||||
parts[1] = parts[2]
|
||||
parts = parts[0:2]
|
||||
}
|
||||
case "false":
|
||||
criticality = false
|
||||
if len(parts) > 2 {
|
||||
parts[1] = parts[2]
|
||||
parts = parts[0:2]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
ctrlValue = parts[1]
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
switch oid {
|
||||
case ldap.ControlTypeManageDsaIT:
|
||||
controls = append(controls, &ldap.ControlManageDsaIT{Criticality: criticality})
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported control found: %s", oid)
|
||||
}
|
||||
} else {
|
||||
switch ctrlValue[0] { // where is this documented?
|
||||
case ':':
|
||||
if len(ctrlValue) == 1 {
|
||||
return nil, nil, errors.New("missing value for base64 encoded control value")
|
||||
}
|
||||
ctrlValue, err = decodeBase64(strings.TrimLeft(ctrlValue[1:], spaces))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
return nil, nil, errors.New("base64 decoded to empty value")
|
||||
}
|
||||
|
||||
case '<':
|
||||
if len(ctrlValue) == 1 {
|
||||
return nil, nil, errors.New("missing value for url control value")
|
||||
}
|
||||
ctrlValue, err = readURLValue(strings.TrimLeft(ctrlValue[1:], spaces))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
return nil, nil, errors.New("url resolved to an empty value")
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// convert ctrlValue to *ber.Packet and decode with something like
|
||||
// ctrl := ldap.DecodeControl()
|
||||
// ... FIXME: the controls need a Decode() interface
|
||||
// so we can just do a
|
||||
// ctrl := ldap.ControlByOID(oid) // returns an empty &ControlSomething{}
|
||||
// ctrl.Decode((*ber.Packet)(ctrlValue))
|
||||
// ctrl.Criticality = criticality
|
||||
// that should be usable in github.com/go-ldap/ldap/control.go also
|
||||
// to decode the incoming control
|
||||
// controls = append(controls, ctrl)
|
||||
return nil, nil, fmt.Errorf("controls with values are not supported, oid: %s", oid)
|
||||
}
|
||||
|
||||
if len(lines) == 1 {
|
||||
return nil, nil, errors.New("only controls found")
|
||||
}
|
||||
lines = lines[1:]
|
||||
}
|
||||
return controls, lines, nil
|
||||
}
|
||||
|
||||
func readURLValue(val string) (string, error) {
|
||||
u, err := url.Parse(val)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse URL: %s", err)
|
||||
}
|
||||
if u.Scheme != "file" {
|
||||
return "", fmt.Errorf("unsupported URL scheme %s", u.Scheme)
|
||||
}
|
||||
data, err := ioutil.ReadFile(toPath(u))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read %s: %s", u.Path, err)
|
||||
}
|
||||
val = string(data) // FIXME: safe?
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func decodeBase64(enc string) (string, error) {
|
||||
dec := make([]byte, base64.StdEncoding.DecodedLen(len([]byte(enc))))
|
||||
n, err := base64.StdEncoding.Decode(dec, []byte(enc))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(dec[:n]), nil
|
||||
}
|
||||
|
||||
func validOID(oid string) error {
|
||||
lastDot := true
|
||||
for _, c := range oid {
|
||||
switch {
|
||||
case c == '.' && lastDot:
|
||||
return errors.New("OID with at least 2 consecutive dots")
|
||||
case c == '.':
|
||||
lastDot = true
|
||||
case c >= '0' && c <= '9':
|
||||
lastDot = false
|
||||
default:
|
||||
return errors.New("Invalid character in OID")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validAttr(attr string) error {
|
||||
if len(attr) == 0 {
|
||||
return errors.New("empty attribute name")
|
||||
}
|
||||
switch {
|
||||
case attr[0] >= 'A' && attr[0] <= 'Z':
|
||||
// A-Z
|
||||
case attr[0] >= 'a' && attr[0] <= 'z':
|
||||
// a-z
|
||||
default:
|
||||
if attr[0] >= '0' && attr[0] <= '9' {
|
||||
return validOID(attr)
|
||||
}
|
||||
return errors.New("invalid first character in attribute")
|
||||
}
|
||||
for i := 1; i < len(attr); i++ {
|
||||
c := attr[i]
|
||||
switch {
|
||||
case c >= '0' && c <= '9':
|
||||
case c >= 'A' && c <= 'Z':
|
||||
case c >= 'a' && c <= 'z':
|
||||
case c == '-':
|
||||
case c == ';':
|
||||
default:
|
||||
return errors.New("invalid character in attribute name")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllEntries returns all *ldap.Entries in the LDIF
|
||||
func (l *LDIF) AllEntries() (entries []*ldap.Entry) {
|
||||
for _, entry := range l.Entries {
|
||||
if entry.Entry != nil {
|
||||
entries = append(entries, entry.Entry)
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
package ldif
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
var foldWidth = 76
|
||||
|
||||
// ErrMixed is the error, that we cannot mix change records and content
|
||||
// records in one LDIF
|
||||
var ErrMixed = errors.New("cannot mix change records and content records")
|
||||
|
||||
// Marshal returns an LDIF string from the given LDIF.
|
||||
//
|
||||
// The default line lenght is 76 characters. This can be changed by setting
|
||||
// the fw parameter to something else than 0.
|
||||
// For a fold width < 0, no folding will be done, with 0, the default is used.
|
||||
func Marshal(l *LDIF) (data string, err error) {
|
||||
hasEntry := false
|
||||
hasChange := false
|
||||
|
||||
if l.Version > 0 {
|
||||
data = "version: 1\n"
|
||||
}
|
||||
|
||||
fw := l.FoldWidth
|
||||
if fw == 0 {
|
||||
fw = foldWidth
|
||||
}
|
||||
|
||||
for _, e := range l.Entries {
|
||||
switch {
|
||||
case e.Add != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Add.DN, fw) + "\n"
|
||||
data += "changetype: add\n"
|
||||
for _, add := range e.Add.Attributes {
|
||||
if len(add.Vals) == 0 {
|
||||
return "", errors.New("changetype 'add' requires non empty value list")
|
||||
}
|
||||
for _, v := range add.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(add.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
case e.Del != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Del.DN, fw) + "\n"
|
||||
data += "changetype: delete\n"
|
||||
|
||||
case e.Modify != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Modify.DN, fw) + "\n"
|
||||
data += "changetype: modify\n"
|
||||
for _, mod := range e.Modify.Changes {
|
||||
switch mod.Operation {
|
||||
// add operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 0:
|
||||
if len(mod.Modification.Vals) == 0 {
|
||||
return "", errors.New("changetype 'modify', op 'add' requires non empty value list")
|
||||
}
|
||||
|
||||
data += "add: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
// delete operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 1:
|
||||
data += "delete: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
// replace operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 2:
|
||||
if len(mod.Modification.Vals) == 0 {
|
||||
return "", errors.New("changetype 'modify', op 'replace' requires non empty value list")
|
||||
}
|
||||
data += "replace: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type %s in modify request", mod.Modification.Type)
|
||||
}
|
||||
}
|
||||
default:
|
||||
hasEntry = true
|
||||
if hasChange {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Entry.DN, fw) + "\n"
|
||||
for _, av := range e.Entry.Attributes {
|
||||
for _, v := range av.Values {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(av.Name+col+ev, fw) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
data += "\n"
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func encodeValue(value string) (string, bool) {
|
||||
required := false
|
||||
for _, r := range value {
|
||||
if r < ' ' || r > '~' { // ~ = 0x7E, <DEL> = 0x7F
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
return value, false
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString([]byte(value)), true
|
||||
}
|
||||
|
||||
func foldLine(line string, fw int) (folded string) {
|
||||
if fw < 0 {
|
||||
return line
|
||||
}
|
||||
if len(line) <= fw {
|
||||
return line
|
||||
}
|
||||
|
||||
folded = line[:fw] + "\n"
|
||||
line = line[fw:]
|
||||
|
||||
for len(line) > fw-1 {
|
||||
folded += " " + line[:fw-1] + "\n"
|
||||
line = line[fw-1:]
|
||||
}
|
||||
|
||||
if len(line) > 0 {
|
||||
folded += " " + line
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dump writes the given entries to the io.Writer.
|
||||
//
|
||||
// The entries argument can be *ldap.Entry or a mix of *ldap.AddRequest,
|
||||
// *ldap.DelRequest, *ldap.ModifyRequest and *ldap.ModifyDNRequest or slices
|
||||
// of any of those.
|
||||
//
|
||||
// See Marshal() for the fw argument.
|
||||
func Dump(fh io.Writer, fw int, entries ...interface{}) error {
|
||||
l, err := ToLDIF(entries...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.FoldWidth = fw
|
||||
str, err := Marshal(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fh.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
// ToLDIF puts the given arguments in an LDIF struct and returns it.
|
||||
//
|
||||
// The entries argument can be *ldap.Entry or a mix of *ldap.AddRequest,
|
||||
// *ldap.DelRequest, *ldap.ModifyRequest and *ldap.ModifyDNRequest or slices
|
||||
// of any of those.
|
||||
func ToLDIF(entries ...interface{}) (*LDIF, error) {
|
||||
l := &LDIF{}
|
||||
for _, e := range entries {
|
||||
switch e.(type) {
|
||||
case []*ldap.Entry:
|
||||
for _, en := range e.([]*ldap.Entry) {
|
||||
l.Entries = append(l.Entries, &Entry{Entry: en})
|
||||
}
|
||||
|
||||
case *ldap.Entry:
|
||||
l.Entries = append(l.Entries, &Entry{Entry: e.(*ldap.Entry)})
|
||||
|
||||
case []*ldap.AddRequest:
|
||||
for _, en := range e.([]*ldap.AddRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Add: en})
|
||||
}
|
||||
|
||||
case *ldap.AddRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Add: e.(*ldap.AddRequest)})
|
||||
|
||||
case []*ldap.DelRequest:
|
||||
for _, en := range e.([]*ldap.DelRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Del: en})
|
||||
}
|
||||
|
||||
case *ldap.DelRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Del: e.(*ldap.DelRequest)})
|
||||
|
||||
case []*ldap.ModifyRequest:
|
||||
for _, en := range e.([]*ldap.ModifyRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Modify: en})
|
||||
}
|
||||
case *ldap.ModifyRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Modify: e.(*ldap.ModifyRequest)})
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %T", e)
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// +build !windows
|
||||
|
||||
package ldif
|
||||
|
||||
import "net/url"
|
||||
|
||||
func toPath(u *url.URL) string {
|
||||
return u.Path
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package ldif
|
||||
|
||||
import "net/url"
|
||||
import "strings"
|
||||
|
||||
// toPath get the file path
|
||||
// We use ioutil.ReadFile to read the content file.
|
||||
// On windows,
|
||||
// https://github.com/golang/go/blob/95a11c7381e01fdaaf34e25b82db0632081ab74e/src/net/url/url_test.go#L283-L292
|
||||
func toPath(u *url.URL) string {
|
||||
return strings.TrimPrefix(u.Path, "/")
|
||||
}
|
||||
Reference in New Issue
Block a user