util.go 2.78 KB
Newer Older
1 2 3 4
package backend

import (
	"os"
5 6 7
	"strconv"
	"strings"
	"time"
8 9 10 11

	"gopkg.in/ldap.v2"
)

12 13 14 15 16 17
// queryTemplate is the template for a single parametrized LDAP query.
type queryTemplate struct {
	Base   string
	Filter string
	Scope  int
	Attrs  []string
18 19
}

ale's avatar
ale committed
20 21 22 23 24 25 26
// A map of variables to be replaced in a LDAP filter string.
type templateVars map[string]interface{}

// A rawVariable is a string that won't be escaped.
type rawVariable string

func (q *queryTemplate) query(vars templateVars) *ldap.SearchRequest {
27 28 29
	filter := q.Filter
	if filter == "" {
		filter = "(objectClass=*)"
30 31 32 33
	}

	return ldap.NewSearchRequest(
		replaceVars(q.Base, vars),
34
		q.Scope,
35 36 37 38
		ldap.NeverDerefAliases,
		0,
		0,
		false,
39 40
		replaceVars(filter, vars),
		q.Attrs,
41 42 43 44
		nil,
	)
}

ale's avatar
ale committed
45
func replaceVars(s string, vars templateVars) string {
46
	return os.Expand(s, func(k string) string {
ale's avatar
ale committed
47 48 49 50 51 52 53 54 55 56 57 58
		i, ok := vars[k]
		if !ok {
			return ""
		}
		switch v := i.(type) {
		case rawVariable:
			return string(v)
		case string:
			return ldap.EscapeFilter(v)
		default:
			return ""
		}
59 60 61
	})
}

62 63 64
// LDAP string to boolean value. There is no schema defined for this,
// we just match a bunch of plausible text truth values ("yes", "on",
// "true", etc).
65 66 67 68 69 70 71 72 73
func s2b(s string) bool {
	switch s {
	case "yes", "y", "on", "enabled", "true":
		return true
	default:
		return false
	}
}

74
// Bool to LDAP value. Encoded as yes/no.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
func b2s(b bool) string {
	if b {
		return "yes"
	}
	return "no"
}

// Convert a string to a []string with a single item, or nil if the
// string is empty. Useful for optional single-valued LDAP attributes.
func s2l(s string) []string {
	if s == "" {
		return nil
	}
	return []string{s}
}

91
// Returns true if a LDAP object has the specified objectClass.
92 93 94 95 96 97 98 99 100
func isObjectClass(entry *ldap.Entry, class string) bool {
	classes := entry.GetAttributeValues("objectClass")
	for _, c := range classes {
		if c == class {
			return true
		}
	}
	return false
}
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

// Join some RDNs.
func joinDN(parts ...string) string {
	return strings.Join(parts, ",")
}

// Find the uid= in the DN. Return an empty string on failure.
func ownerFromDN(dn string) string {
	parsed, err := ldap.ParseDN(dn)
	if err != nil {
		return ""
	}
	// Start looking for uid from the right. The strict
	// greater-than clause ignores the first RDN (so that the
	// owner of a user is not itself).
	for i := len(parsed.RDNs) - 1; i > 0; i-- {
		attr := parsed.RDNs[i].Attributes[0]
		if attr.Type == "uid" {
			return attr.Value
		}
	}
	return ""
}

// Functions to encode/decode shadow(5) timestamps (number of days
// since the epoch).
const oneDay = 86400

func encodeShadowTimestamp(t time.Time) string {
	d := t.UTC().Unix() / oneDay
	return strconv.FormatInt(d, 10)
}

func decodeShadowTimestamp(s string) (t time.Time) {
	if i, err := strconv.ParseInt(s, 10, 64); err == nil {
		t = time.Unix(i*oneDay, 0).UTC()
	}
	return
}