├── vendor
├── github.com
│ └── go-asn1-ber
│ │ └── asn1-ber
│ │ ├── go.mod
│ │ ├── content_int.go
│ │ ├── util.go
│ │ ├── README.md
│ │ ├── header.go
│ │ ├── LICENSE
│ │ ├── length.go
│ │ ├── identifier.go
│ │ └── ber.go
├── gopkg.in
│ └── ldap.v3
│ │ ├── doc.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── CONTRIBUTING.md
│ │ ├── debug.go
│ │ ├── client.go
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── del.go
│ │ ├── request.go
│ │ ├── Makefile
│ │ ├── compare.go
│ │ ├── moddn.go
│ │ ├── add.go
│ │ ├── passwdmodify.go
│ │ ├── modify.go
│ │ ├── bind.go
│ │ ├── dn.go
│ │ ├── ldap.go
│ │ ├── error.go
│ │ ├── search.go
│ │ └── conn.go
└── vendor.json
├── main.go
├── scripts
├── gogetcookie.sh
├── gofmtcheck.sh
├── errcheck.sh
└── changelog-links.sh
├── .gitignore
├── ad
├── active_directory_add_to_group_helper.go
├── active_directory_ou_helper.go
├── active_directory_computer_helper.go
├── provider_test.go
├── config.go
├── provider.go
├── resource_active_directory_organizational_unit_test.go
├── resource_active_directory_organizational_unit.go
├── resource_active_directory_user_test.go
├── resource_active_directory_computer_to_ou_test.go
├── resource_active_directory_computer_test.go
├── resource_active_directory_computer_to_ou.go
├── active_directory_group_helper.go
├── resource_active_directory_computer.go
├── resource_active_directory_add_to_group_test.go
├── resource_active_directory_group_to_ou_test.go
├── resource_active_directory_add_to_group.go
├── resource_active_directory_user.go
└── resource_active_directory_group_to_ou.go
├── website
├── docs
│ ├── r
│ │ ├── computer.html.markdown
│ │ ├── organizational_unit.html.markdown
│ │ ├── computer_to_ou.html.markdown
│ │ ├── add_to_group.html.markdown
│ │ └── group_to_ou.html.markdown
│ └── index.html.markdown
└── ad.erb
├── GNUmakefile
├── tf-ad-devrc.mk.example
└── README.md
/vendor/github.com/go-asn1-ber/asn1-ber/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-asn1-ber/asn1-ber
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package ldap provides basic LDAP v3 functionality.
3 | */
4 | package ldap
5 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-ldap/ldap/v3
2 |
3 | require github.com/go-asn1-ber/asn1-ber v1.3.1
4 |
5 | go 1.13
6 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
2 | github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
3 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/GSLabDev/terraform-provider-ad/ad"
5 | "github.com/hashicorp/terraform/plugin"
6 | )
7 |
8 | func main() {
9 | plugin.Serve(&plugin.ServeOpts{
10 | ProviderFunc: ad.Provider})
11 | }
12 |
--------------------------------------------------------------------------------
/scripts/gogetcookie.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | touch ~/.gitcookies
4 | chmod 0600 ~/.gitcookies
5 |
6 | git config --global http.cookiefile ~/.gitcookies
7 |
8 | tr , \\t <<\__END__ >>~/.gitcookies
9 | .googlesource.com,TRUE,/,TRUE,2147483647,o,git-paul.hashicorp.com=1/z7s05EYPudQ9qoe6dMVfmAVwgZopEkZBb1a2mA5QtHE
10 | __END__
11 |
--------------------------------------------------------------------------------
/scripts/gofmtcheck.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Check gofmt
4 | echo "==> Checking that code complies with gofmt requirements..."
5 | gofmt_files=$(gofmt -l `find . -name '*.go' | grep -v vendor`)
6 | if [[ -n ${gofmt_files} ]]; then
7 | echo 'gofmt needs running on the following files:'
8 | echo "${gofmt_files}"
9 | echo "You can use the command: \`make fmt\` to reformat code."
10 | exit 1
11 | fi
12 |
13 | exit 0
14 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/content_int.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | func encodeUnsignedInteger(i uint64) []byte {
4 | n := uint64Length(i)
5 | out := make([]byte, n)
6 |
7 | var j int
8 | for ; n > 0; n-- {
9 | out[j] = (byte(i >> uint((n-1)*8)))
10 | j++
11 | }
12 |
13 | return out
14 | }
15 |
16 | func uint64Length(i uint64) (numBytes int) {
17 | numBytes = 1
18 |
19 | for i > 255 {
20 | numBytes++
21 | i >>= 8
22 | }
23 |
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.dll
2 | *.exe
3 | .DS_Store
4 | example.tf
5 | terraform.tfplan
6 | terraform.tfstate
7 | bin/
8 | modules-dev/
9 | /pkg/
10 | website/.vagrant
11 | website/.bundle
12 | website/build
13 | website/node_modules
14 | .vagrant/
15 | *.backup
16 | ./*.tfstate
17 | .terraform/
18 | *.log
19 | *.bak
20 | *~
21 | .*.swp
22 | .idea
23 | *.iml
24 | *.test
25 | *.iml
26 |
27 | website/vendor
28 |
29 | # Test exclusions
30 | !command/test-fixtures/**/*.tfstate
31 | !command/test-fixtures/**/.terraform/
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | We welcome contribution and improvements.
4 |
5 | ## Guiding Principles
6 |
7 | To begin with here is a draft from an email exchange:
8 |
9 | * take compatibility seriously (our semvers, compatibility with older go versions, etc)
10 | * don't tag untested code for release
11 | * beware of baking in implicit behavior based on other libraries/tools choices
12 | * be as high-fidelity as possible in plumbing through LDAP data (don't mask errors or reduce power of someone using the library)
13 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/util.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | import "io"
4 |
5 | func readByte(reader io.Reader) (byte, error) {
6 | bytes := make([]byte, 1, 1)
7 | _, err := io.ReadFull(reader, bytes)
8 | if err != nil {
9 | if err == io.EOF {
10 | return 0, io.ErrUnexpectedEOF
11 | }
12 | return 0, err
13 | }
14 | return bytes[0], nil
15 | }
16 |
17 | func isEOCPacket(p *Packet) bool {
18 | return p != nil &&
19 | p.Tag == TagEOC &&
20 | p.ClassType == ClassUniversal &&
21 | p.TagType == TypePrimitive &&
22 | len(p.ByteValue) == 0 &&
23 | len(p.Children) == 0
24 | }
25 |
--------------------------------------------------------------------------------
/vendor/vendor.json:
--------------------------------------------------------------------------------
1 | {
2 | "comment": "",
3 | "ignore": "test",
4 | "package": [
5 | {
6 | "checksumSHA1": "wHbKAXHD1CbepwyvV08uHQOi/FM=",
7 | "path": "github.com/go-asn1-ber/asn1-ber",
8 | "revision": "29be175fc3a3b1787c008093b829fbfee40decd6",
9 | "revisionTime": "2019-11-21T21:21:51Z"
10 | },
11 | {
12 | "checksumSHA1": "vKQiq7vUkGDNBJjHcqDoZqfaHCk=",
13 | "path": "gopkg.in/ldap.v3",
14 | "revision": "3c7ebc5837612e50ae3a60b8e9933837c9193f5f",
15 | "revisionTime": "2019-11-07T01:18:53Z"
16 | }
17 | ],
18 | "rootPath": "github.com/GSLabDev/terraform-provider-ad"
19 | }
20 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/debug.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "log"
5 |
6 | ber "github.com/go-asn1-ber/asn1-ber"
7 | )
8 |
9 | // debugging type
10 | // - has a Printf method to write the debug output
11 | type debugging bool
12 |
13 | // Enable controls debugging mode.
14 | func (debug *debugging) Enable(b bool) {
15 | *debug = debugging(b)
16 | }
17 |
18 | // Printf writes debug output.
19 | func (debug debugging) Printf(format string, args ...interface{}) {
20 | if debug {
21 | log.Printf(format, args...)
22 | }
23 | }
24 |
25 | // PrintPacket dumps a packet.
26 | func (debug debugging) PrintPacket(packet *ber.Packet) {
27 | if debug {
28 | ber.PrintPacket(packet)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ad/active_directory_add_to_group_helper.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import ldap "gopkg.in/ldap.v3"
4 |
5 | func addToGroup(groupToAddName string, targetGroupName string, adConn *ldap.Conn) error {
6 | modifyRequest := ldap.NewModifyRequest(targetGroupName, nil)
7 | modifyRequest.Add("member", []string{groupToAddName})
8 |
9 | err := adConn.Modify(modifyRequest)
10 | if err != nil {
11 | return err
12 | }
13 | return nil
14 | }
15 |
16 | func removeFromGroup(groupToRemoveName string, targetGroupName string, adConn *ldap.Conn) error {
17 | modifyRequest := ldap.NewModifyRequest(targetGroupName, nil)
18 | modifyRequest.Delete("member", []string{groupToRemoveName})
19 |
20 | err := adConn.Modify(modifyRequest)
21 | if err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/errcheck.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Check gofmt
4 | echo "==> Checking for unchecked errors..."
5 |
6 | if ! which errcheck > /dev/null; then
7 | echo "==> Installing errcheck..."
8 | go get -u github.com/kisielk/errcheck
9 | fi
10 |
11 | err_files=$(errcheck -ignoretests \
12 | -ignore 'github.com/hashicorp/terraform/helper/schema:Set' \
13 | -ignore 'bytes:.*' \
14 | -ignore 'io:Close|Write' \
15 | $(go list ./...| grep -v /vendor/))
16 |
17 | if [[ -n ${err_files} ]]; then
18 | echo 'Unchecked errors found in the following places:'
19 | echo "${err_files}"
20 | echo "Please handle returned errors. You can check directly with \`make errcheck\`"
21 | exit 1
22 | fi
23 |
24 | exit 0
25 |
--------------------------------------------------------------------------------
/ad/active_directory_ou_helper.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import ldap "gopkg.in/ldap.v3"
4 |
5 | func addOU(OUname string, dnOfOU string, adConn *ldap.Conn, desc string) error {
6 | addRequest := ldap.NewAddRequest(dnOfOU, nil) //returns Addrequest for the given DN,no attributes
7 | addRequest.Attribute("objectClass", []string{"OrganizationalUnit"})
8 | addRequest.Attribute("name", []string{OUname}) //login name
9 | if desc != "" {
10 | addRequest.Attribute("description", []string{desc})
11 | }
12 |
13 | err := adConn.Add(addRequest)
14 | if err != nil {
15 | return err
16 | }
17 | return nil
18 | }
19 |
20 | func deleteOU(dnOfOU string, adConn *ldap.Conn) error {
21 | delRequest := ldap.NewDelRequest(dnOfOU, nil) //creates a delete request for the given DN
22 | err := adConn.Del(delRequest)
23 | if err != nil {
24 | return err
25 | }
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/client.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "crypto/tls"
5 | "time"
6 | )
7 |
8 | // Client knows how to interact with an LDAP server
9 | type Client interface {
10 | Start()
11 | StartTLS(*tls.Config) error
12 | Close()
13 | SetTimeout(time.Duration)
14 |
15 | Bind(username, password string) error
16 | UnauthenticatedBind(username string) error
17 | SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error)
18 | ExternalBind() error
19 |
20 | Add(*AddRequest) error
21 | Del(*DelRequest) error
22 | Modify(*ModifyRequest) error
23 | ModifyDN(*ModifyDNRequest) error
24 |
25 | Compare(dn, attribute, value string) (bool, error)
26 | PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
27 |
28 | Search(*SearchRequest) (*SearchResult, error)
29 | SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
30 | }
31 |
--------------------------------------------------------------------------------
/ad/active_directory_computer_helper.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import ldap "gopkg.in/ldap.v3"
4 |
5 | func addComputerToAD(computerName string, dnName string, adConn *ldap.Conn, desc string) error {
6 | addRequest := ldap.NewAddRequest(dnName, nil)
7 | addRequest.Attribute("objectClass", []string{"computer"})
8 | addRequest.Attribute("name", []string{computerName})
9 | addRequest.Attribute("sAMAccountName", []string{computerName + "$"})
10 | addRequest.Attribute("userAccountControl", []string{"4096"})
11 | if desc != "" {
12 | addRequest.Attribute("description", []string{desc})
13 | }
14 | err := adConn.Add(addRequest)
15 | if err != nil {
16 | return err
17 | }
18 | return nil
19 | }
20 |
21 | func deleteComputerFromAD(dnName string, adConn *ldap.Conn) error {
22 | delRequest := ldap.NewDelRequest(dnName, nil)
23 | err := adConn.Del(delRequest)
24 | if err != nil {
25 | return err
26 | }
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/README.md:
--------------------------------------------------------------------------------
1 | [](https://godoc.org/gopkg.in/asn1-ber.v1) [](https://travis-ci.org/go-asn1-ber/asn1-ber)
2 |
3 |
4 | ASN1 BER Encoding / Decoding Library for the GO programming language.
5 | ---------------------------------------------------------------------
6 |
7 | Required libraries:
8 | None
9 |
10 | Working:
11 | Very basic encoding / decoding needed for LDAP protocol
12 |
13 | Tests Implemented:
14 | A few
15 |
16 | TODO:
17 | Fix all encoding / decoding to conform to ASN1 BER spec
18 | Implement Tests / Benchmarks
19 |
20 | ---
21 |
22 | The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
23 | The design is licensed under the Creative Commons 3.0 Attributions license.
24 | Read this article for more details: http://blog.golang.org/gopher
25 |
--------------------------------------------------------------------------------
/website/docs/r/computer.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Active Directory: ad_computer"
4 | sidebar_current: "docs-ad-resource-inventory-folder"
5 | description: |-
6 | Provides a Active Directory computer resource. This can be used to create and delete computer.
7 | ---
8 |
9 | # ad\_computer
10 |
11 | Provides a Active Directory computer resource. This can be used to create and delete computers from AD.
12 |
13 | ## Example Usage
14 |
15 | ```hcl
16 | resource "ad_computer" "web" {
17 | domain = "terraform.com"
18 | computer_name = "sampleName"
19 | description = "terraform sample server"
20 | }
21 | ```
22 |
23 | ## Argument Reference
24 |
25 | The following arguments are supported:
26 |
27 | * `domain` - (Required) The domain of the Active Directory
28 | * `computer_name` - (Required) The name of a Computer to be added to Active Directory
29 | * `description` - (Optional) The description property of Computer Object
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/header.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | func readHeader(reader io.Reader) (identifier Identifier, length int, read int, err error) {
10 | if i, c, err := readIdentifier(reader); err != nil {
11 | return Identifier{}, 0, read, err
12 | } else {
13 | identifier = i
14 | read += c
15 | }
16 |
17 | if l, c, err := readLength(reader); err != nil {
18 | return Identifier{}, 0, read, err
19 | } else {
20 | length = l
21 | read += c
22 | }
23 |
24 | // Validate length type with identifier (x.600, 8.1.3.2.a)
25 | if length == LengthIndefinite && identifier.TagType == TypePrimitive {
26 | return Identifier{}, 0, read, errors.New("indefinite length used with primitive type")
27 | }
28 |
29 | if length < LengthIndefinite {
30 | err = fmt.Errorf("length cannot be less than %d", LengthIndefinite)
31 | return
32 | }
33 |
34 | return identifier, length, read, nil
35 | }
36 |
--------------------------------------------------------------------------------
/website/docs/r/organizational_unit.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Active Directory: ad_organizational_unit"
4 | sidebar_current: "docs-ad-resource-inventory-folder"
5 | description: |-
6 | Creates Organizational Unit in active directory.
7 | ---
8 |
9 | # ad\_organizational\_unit
10 |
11 | Creates Organizational Unit in active directory.
12 |
13 | ## Example Usage
14 |
15 | ```hcl
16 | # Add Organizational Unit to Active Directory
17 | resource "ad_organizational_unit" "test" {
18 | ou_name = "sample-ou"
19 | ou_distinguished_name = "OU=groups,DC=company,DC=com"
20 | description = "Managed by terraform"
21 | }
22 | ```
23 |
24 | ## Argument Reference
25 |
26 | The following arguments are supported:
27 |
28 | * `ou_name` - (Required) Name of organizational unit.
29 | * `ou_distinguished_name` - (Required) The distinguished name of the Organizational Unit of the Active Directory to add the ou to.
30 | * `description` - (Optional) Sets the description property of the resultant ou object.
31 |
--------------------------------------------------------------------------------
/scripts/changelog-links.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script rewrites [GH-nnnn]-style references in the CHANGELOG.md file to
4 | # be Markdown links to the given github issues.
5 | #
6 | # This is run during releases so that the issue references in all of the
7 | # released items are presented as clickable links, but we can just use the
8 | # easy [GH-nnnn] shorthand for quickly adding items to the "Unrelease" section
9 | # while merging things between releases.
10 |
11 | set -e
12 |
13 | if [[ ! -f CHANGELOG.md ]]; then
14 | echo "ERROR: CHANGELOG.md not found in pwd."
15 | echo "Please run this from the root of the terraform provider repository"
16 | exit 1
17 | fi
18 |
19 | if [[ `uname` == "Darwin" ]]; then
20 | echo "Using BSD sed"
21 | SED="sed -i.bak -E -e"
22 | else
23 | echo "Using GNU sed"
24 | SED="sed -i.bak -r -e"
25 | fi
26 |
27 | PROVIDER_URL="https:\/\/github.com\/terraform-providers\/terraform-provider-ad\/issues"
28 |
29 | $SED "s/GH-([0-9]+)/\[#\1\]\($PROVIDER_URL\/\1\)/g" -e 's/\[\[#(.+)([0-9])\)]$/(\[#\1\2))/g' CHANGELOG.md
30 |
31 | rm CHANGELOG.md.bak
32 |
--------------------------------------------------------------------------------
/website/docs/r/computer_to_ou.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Active Directory: ad_computer_to_ou"
4 | sidebar_current: "docs-ad-resource-inventory-folder"
5 | description: |-
6 | Provides a Active Directory computer resource to Organizational Unit. This can be used to create and delete computer from OU.
7 | ---
8 |
9 | # ad\_computer\_to\_ou
10 |
11 | Provides a Active Directory computer resource to Organizational Unit. This can be used to create and delete computer from OU of AD.
12 |
13 | ## Example Usage
14 |
15 | ```hcl
16 | resource "ad_computer_to_ou" "bar" {
17 | ou_distinguished_name = "ou=SubOU,ou=MyOU,dc=terraform,dc=com"
18 | computer_name = "sampleName"
19 | description = "terraform sample server to OU"
20 | }
21 | ```
22 |
23 | ## Argument Reference
24 |
25 | The following arguments are supported:
26 |
27 | * `ou_distinguished_name` - (Required) The distinguished name of the Organizational Unit of the Active Directory
28 | * `computer_name` - (Required) The name of a Computer to be added to Active Directory
29 | * `description` - (Optional) The description property of Computer Object
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
4 | Portions copyright (c) 2015-2016 go-ldap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
4 | Portions copyright (c) 2015-2016 go-asn1-ber Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/README.md:
--------------------------------------------------------------------------------
1 | [](https://godoc.org/github.com/go-ldap/ldap)
2 | [](https://travis-ci.org/go-ldap/ldap)
3 |
4 | # Basic LDAP v3 functionality for the GO programming language.
5 |
6 | ## Features:
7 |
8 | - Connecting to LDAP server (non-TLS, TLS, STARTTLS)
9 | - Binding to LDAP server
10 | - Searching for entries
11 | - Filter Compile / Decompile
12 | - Paging Search Results
13 | - Modify Requests / Responses
14 | - Add Requests / Responses
15 | - Delete Requests / Responses
16 | - Modify DN Requests / Responses
17 |
18 | ## Examples:
19 |
20 | - search
21 | - modify
22 |
23 | ## Contributing:
24 |
25 | Bug reports and pull requests are welcome!
26 |
27 | Before submitting a pull request, please make sure tests and verification scripts pass:
28 | ```
29 | make all
30 | ```
31 |
32 | To set up a pre-push hook to run the tests and verify scripts before pushing:
33 | ```
34 | ln -s ../../.githooks/pre-push .git/hooks/pre-push
35 | ```
36 |
37 | ---
38 | The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
39 | The design is licensed under the Creative Commons 3.0 Attributions license.
40 | Read this article for more details: http://blog.golang.org/gopher
41 |
--------------------------------------------------------------------------------
/GNUmakefile:
--------------------------------------------------------------------------------
1 | -include $(HOME)/.tf-ad-devrc.mk
2 |
3 | TEST?=$$(go list ./... |grep -v 'vendor')
4 | GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
5 |
6 | default: build
7 |
8 | build: fmtcheck
9 | go install
10 |
11 | test: fmtcheck
12 | go test -i $(TEST) || exit 1
13 | echo $(TEST) | \
14 | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4
15 |
16 | testacc: fmtcheck
17 | TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m
18 |
19 | vet:
20 | @echo "go vet ."
21 | @go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
22 | echo ""; \
23 | echo "Vet found suspicious constructs. Please check the reported constructs"; \
24 | echo "and fix them if necessary before submitting the code for review."; \
25 | exit 1; \
26 | fi
27 |
28 | fmt:
29 | gofmt -w $(GOFMT_FILES)
30 |
31 | fmtcheck:
32 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
33 |
34 | errcheck:
35 | @sh -c "'$(CURDIR)/scripts/errcheck.sh'"
36 |
37 | vendor-status:
38 | @govendor status
39 |
40 | test-compile:
41 | @if [ "$(TEST)" = "./..." ]; then \
42 | echo "ERROR: Set TEST to a specific package. For example,"; \
43 | echo " make test-compile TEST=./aws"; \
44 | exit 1; \
45 | fi
46 | go test -c $(TEST) $(TESTARGS)
47 |
48 | .PHONY: build test testacc vet fmt fmtcheck errcheck vendor-status test-compile
49 |
50 |
--------------------------------------------------------------------------------
/ad/provider_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform/helper/schema"
8 | "github.com/hashicorp/terraform/terraform"
9 | )
10 |
11 | var testAccProviders map[string]terraform.ResourceProvider
12 | var testAccProvider *schema.Provider
13 |
14 | func init() {
15 | testAccProvider = Provider().(*schema.Provider)
16 | testAccProviders = map[string]terraform.ResourceProvider{
17 | "ad": testAccProvider,
18 | }
19 | }
20 |
21 | func TestProvider(t *testing.T) {
22 | if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
23 | t.Fatalf("err: %s", err)
24 | }
25 | }
26 |
27 | func TestProvider_impl(t *testing.T) {
28 | var _ terraform.ResourceProvider = Provider()
29 | }
30 |
31 | func testAccPreCheck(t *testing.T) {
32 | if v := os.Getenv("AD_DOMAIN"); v == "" {
33 | t.Fatal("AD_DOMAIN must be set for acceptance tests")
34 | }
35 |
36 | if v := os.Getenv("AD_IP"); v == "" {
37 | if v := os.Getenv("AD_URL"); v == "" {
38 | t.Fatal("AD_IP or AD_URL must be set for acceptance tests")
39 | }
40 | }
41 |
42 | if v := os.Getenv("AD_USER"); v == "" {
43 | t.Fatal("AD_USER must be set for acceptance tests")
44 | }
45 |
46 | if v := os.Getenv("AD_PASSWORD"); v == "" {
47 | t.Fatal("AD_PASSWORD must be set for acceptance tests")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ad/config.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "gopkg.in/ldap.v3"
8 | )
9 |
10 | type Config struct {
11 | Domain string
12 | IP string
13 | URL string
14 | Username string
15 | Password string
16 | }
17 |
18 | // Client() returns a connection for accessing AD services.
19 | func (c *Config) Client() (*ldap.Conn, error) {
20 | var username string
21 | var url string
22 | username = c.Username + "@" + c.Domain
23 |
24 | // stay downwards compatible
25 | switch {
26 | case c.URL != "":
27 | url = c.URL
28 | case c.IP != "":
29 | url = fmt.Sprintf("ldap://%s:389", c.IP)
30 | default:
31 | return nil, fmt.Errorf("Need either an IP or LDAP URL to connect to AD, check provider configuration")
32 | }
33 |
34 | adConn, err := clientConnect(url, username, c.Password)
35 |
36 | if err != nil {
37 | return nil, fmt.Errorf("Error while trying to connect active directory server, Check server IP address, username or password: %s", err)
38 | }
39 | log.Printf("[DEBUG] AD connection successful for user: %s", c.Username)
40 | return adConn, nil
41 | }
42 |
43 | func clientConnect(url, username, password string) (*ldap.Conn, error) {
44 | adConn, err := ldap.DialURL(url)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | err = adConn.Bind(username, password)
50 | if err != nil {
51 | return nil, err
52 | }
53 | return adConn, nil
54 | }
55 |
--------------------------------------------------------------------------------
/website/ad.erb:
--------------------------------------------------------------------------------
1 | <% wrap_layout :inner do %>
2 | <% content_for :sidebar do %>
3 |
30 | <% end %>
31 | <%= yield %>
32 | <% end %>
33 |
--------------------------------------------------------------------------------
/tf-ad-devrc.mk.example:
--------------------------------------------------------------------------------
1 | # This file is designed to assist you with configuring your environment for
2 | # testing the Active Directory provider, and also serves as a catalog of the environment
3 | # variables that are required to test all of the resources in this provider.
4 | #
5 | # This should be copied to ~/.tf-ad-devrc.mk and edited accordingly.
6 | #
7 | # Note that the use of all of this file is not required - environment variables
8 | # can still be completely set from the command line or your existing
9 | # environment. In this case, don't use this file as it may cause conflicts.
10 | #
11 | # NOTE: Remove the annotations from any variables that have them inline, or
12 | # else make will add the whitespace to the variable as well.
13 | #
14 | # The essentials. None of the tests will run if you don't have these.
15 | export AD_URL ?= server.url
16 | export AD_DOMAIN ?= server.domain
17 | export AD_USER ?= server.user
18 | export AD_PASSWORD ?= changeme
19 |
20 | # The following variables are shared across various tests. To ensure all tests
21 | # succeed, it's probably best to set all of these to valid values.
22 | export AD_COMPUTER_DOMAIN ?= base-linux # Active Directory Domain
23 | export AD_COMPUTER_OU_DISTINGUISHED_NAME ?= 'ou=DevComputers,dc=base-linux' #AD OU Distinguished name
24 | export AD_GROUP_OU_DISTINGUISHED_NAME ?= 'ou=DevGroups,dc=base-linux' #AD OU Distinguished name
25 | export AD_OU_DOMAIN ?= "example.com" # ou domain
26 | # vi: filetype=make
27 |
--------------------------------------------------------------------------------
/website/docs/r/add_to_group.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Active Directory: ad_add_to_group"
4 | sidebar_current: "docs-ad-resource-inventory-folder"
5 | description: |-
6 | Adds distinguished names to members of an Active Directory group object.
7 | ---
8 |
9 | # ad\_add\_to\_group
10 |
11 | Adds distinguished names to members of an Active Directory group object. Can be used to add users or groups to a group.
12 |
13 | ## Example Usage
14 |
15 | ```hcl
16 | resource "ad_group_to_ou" "admins" {
17 | ou_distinguished_name = "OU=groups,DC=company,DC=com"
18 | group_name = "admins"
19 | description = "Managed by terraform."
20 | gid_number = 9001
21 | }
22 |
23 | resource "ad_add_to_group" "main" {
24 | target_group = "CN=admins,OU=groups,DC=company,DC=com"
25 | dns_to_add = [
26 | "CN=alice,OU=users,DC=company,DC=com",
27 | "CN=bob,OU=users,DC=company,DC=com",
28 | ]
29 | }
30 | ```
31 |
32 | ## Argument Reference
33 |
34 | The following arguments are supported:
35 |
36 | * `target_group` - (Required) The distinguished name of the target group you're adding members to.
37 | * `dns_to_add` - (Required) A list of distinguished names to add to target_group. Can be users or groups.
38 |
39 | ## Attributes Reference
40 |
41 | The following attributes are exported:
42 |
43 | * `id` - A concatenation of each distinguished name in the `dns_to_add` list, seperated by a pipe `|`. e.g. 'CN=alice,OU=users,DC=company,DC=com|CN=bob,OU=users,DC=company,DC=com'
44 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/del.go:
--------------------------------------------------------------------------------
1 | //
2 | // https://tools.ietf.org/html/rfc4511
3 | //
4 | // DelRequest ::= [APPLICATION 10] LDAPDN
5 |
6 | package ldap
7 |
8 | import (
9 | "log"
10 |
11 | ber "github.com/go-asn1-ber/asn1-ber"
12 | )
13 |
14 | // DelRequest implements an LDAP deletion request
15 | type DelRequest struct {
16 | // DN is the name of the directory entry to delete
17 | DN string
18 | // Controls hold optional controls to send with the request
19 | Controls []Control
20 | }
21 |
22 | func (req *DelRequest) appendTo(envelope *ber.Packet) error {
23 | pkt := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, req.DN, "Del Request")
24 | pkt.Data.Write([]byte(req.DN))
25 |
26 | envelope.AppendChild(pkt)
27 | if len(req.Controls) > 0 {
28 | envelope.AppendChild(encodeControls(req.Controls))
29 | }
30 |
31 | return nil
32 | }
33 |
34 | // NewDelRequest creates a delete request for the given DN and controls
35 | func NewDelRequest(DN string, Controls []Control) *DelRequest {
36 | return &DelRequest{
37 | DN: DN,
38 | Controls: Controls,
39 | }
40 | }
41 |
42 | // Del executes the given delete request
43 | func (l *Conn) Del(delRequest *DelRequest) error {
44 | msgCtx, err := l.doRequest(delRequest)
45 | if err != nil {
46 | return err
47 | }
48 | defer l.finishMessage(msgCtx)
49 |
50 | packet, err := l.readPacket(msgCtx)
51 | if err != nil {
52 | return err
53 | }
54 |
55 | if packet.Children[1].Tag == ApplicationDelResponse {
56 | err := GetLDAPError(packet)
57 | if err != nil {
58 | return err
59 | }
60 | } else {
61 | log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
62 | }
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/request.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "errors"
5 |
6 | ber "github.com/go-asn1-ber/asn1-ber"
7 | )
8 |
9 | var (
10 | errRespChanClosed = errors.New("ldap: response channel closed")
11 | errCouldNotRetMsg = errors.New("ldap: could not retrieve message")
12 | )
13 |
14 | type request interface {
15 | appendTo(*ber.Packet) error
16 | }
17 |
18 | type requestFunc func(*ber.Packet) error
19 |
20 | func (f requestFunc) appendTo(p *ber.Packet) error {
21 | return f(p)
22 | }
23 |
24 | func (l *Conn) doRequest(req request) (*messageContext, error) {
25 | packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
26 | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
27 | if err := req.appendTo(packet); err != nil {
28 | return nil, err
29 | }
30 |
31 | if l.Debug {
32 | ber.PrintPacket(packet)
33 | }
34 |
35 | msgCtx, err := l.sendMessage(packet)
36 | if err != nil {
37 | return nil, err
38 | }
39 | l.Debug.Printf("%d: returning", msgCtx.id)
40 | return msgCtx, nil
41 | }
42 |
43 | func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
44 | l.Debug.Printf("%d: waiting for response", msgCtx.id)
45 | packetResponse, ok := <-msgCtx.responses
46 | if !ok {
47 | return nil, NewError(ErrorNetwork, errRespChanClosed)
48 | }
49 | packet, err := packetResponse.ReadPacket()
50 | l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | if packet == nil {
56 | return nil, NewError(ErrorNetwork, errCouldNotRetMsg)
57 | }
58 |
59 | if l.Debug {
60 | if err = addLDAPDescriptions(packet); err != nil {
61 | return nil, err
62 | }
63 | ber.PrintPacket(packet)
64 | }
65 | return packet, nil
66 | }
67 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: default install build test quicktest fmt vet lint
2 |
3 | # List of all release tags "supported" by our current Go version
4 | # E.g. ":go1.1:go1.2:go1.3:go1.4:go1.5:go1.6:go1.7:go1.8:go1.9:go1.10:go1.11:go1.12:"
5 | GO_RELEASE_TAGS := $(shell go list -f ':{{join (context.ReleaseTags) ":"}}:' runtime)
6 |
7 | # Only use the `-race` flag on newer versions of Go (version 1.3 and newer)
8 | ifeq (,$(findstring :go1.3:,$(GO_RELEASE_TAGS)))
9 | RACE_FLAG :=
10 | else
11 | RACE_FLAG := -race -cpu 1,2,4
12 | endif
13 |
14 | # Run `go vet` on Go 1.12 and newer. For Go 1.5-1.11, use `go tool vet`
15 | ifneq (,$(findstring :go1.12:,$(GO_RELEASE_TAGS)))
16 | GO_VET := go vet \
17 | -atomic \
18 | -bool \
19 | -copylocks \
20 | -nilfunc \
21 | -printf \
22 | -rangeloops \
23 | -unreachable \
24 | -unsafeptr \
25 | -unusedresult \
26 | .
27 | else ifneq (,$(findstring :go1.5:,$(GO_RELEASE_TAGS)))
28 | GO_VET := go tool vet \
29 | -atomic \
30 | -bool \
31 | -copylocks \
32 | -nilfunc \
33 | -printf \
34 | -shadow \
35 | -rangeloops \
36 | -unreachable \
37 | -unsafeptr \
38 | -unusedresult \
39 | .
40 | else
41 | GO_VET := @echo "go vet skipped -- not supported on this version of Go"
42 | endif
43 |
44 | default: fmt vet lint build quicktest
45 |
46 | install:
47 | go get -t -v ./...
48 |
49 | build:
50 | go build -v ./...
51 |
52 | test:
53 | go test -v $(RACE_FLAG) -cover ./...
54 |
55 | quicktest:
56 | go test ./...
57 |
58 | # Capture output and force failure when there is non-empty output
59 | fmt:
60 | @echo gofmt -l .
61 | @OUTPUT=`gofmt -l . 2>&1`; \
62 | if [ "$$OUTPUT" ]; then \
63 | echo "gofmt must be run on the following files:"; \
64 | echo "$$OUTPUT"; \
65 | exit 1; \
66 | fi
67 |
68 | vet:
69 | $(GO_VET)
70 |
71 | # https://github.com/golang/lint
72 | # go get github.com/golang/lint/golint
73 | # Capture output and force failure when there is non-empty output
74 | # Only run on go1.5+
75 | lint:
76 | @echo golint ./...
77 | @OUTPUT=`command -v golint >/dev/null 2>&1 && golint ./... 2>&1`; \
78 | if [ "$$OUTPUT" ]; then \
79 | echo "golint errors:"; \
80 | echo "$$OUTPUT"; \
81 | exit 1; \
82 | fi
83 |
--------------------------------------------------------------------------------
/ad/provider.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/hashicorp/terraform/helper/schema"
7 | "github.com/hashicorp/terraform/terraform"
8 | )
9 |
10 | func Provider() terraform.ResourceProvider {
11 | return &schema.Provider{
12 | Schema: map[string]*schema.Schema{
13 |
14 | "domain": {
15 | Type: schema.TypeString,
16 | Required: true,
17 | Description: "The Domain in which AD Server resides",
18 | DefaultFunc: schema.EnvDefaultFunc("AD_DOMAIN", nil),
19 | },
20 |
21 | "ip": {
22 | Type: schema.TypeString,
23 | Optional: true,
24 | Description: "The IP of the AD Server",
25 | DefaultFunc: schema.EnvDefaultFunc("AD_IP", nil),
26 | },
27 |
28 | "url": {
29 | Type: schema.TypeString,
30 | Optional: true,
31 | Description: "The LDAP URL of the AD Server",
32 | DefaultFunc: schema.EnvDefaultFunc("AD_URL", nil),
33 | },
34 |
35 | "user": {
36 | Type: schema.TypeString,
37 | Required: true,
38 | Description: "The user name of the AD Server",
39 | DefaultFunc: schema.EnvDefaultFunc("AD_USER", nil),
40 | },
41 |
42 | "password": {
43 | Type: schema.TypeString,
44 | Required: true,
45 | Sensitive: true,
46 | Description: "The user password of the AD Server",
47 | DefaultFunc: schema.EnvDefaultFunc("AD_PASSWORD", nil),
48 | },
49 | },
50 |
51 | ResourcesMap: map[string]*schema.Resource{
52 | "ad_computer": resourceComputer(),
53 | "ad_organizational_unit": resourceOU(),
54 | "ad_computer_to_ou": resourceComputerToOU(),
55 | "ad_group_to_ou": resourceGroupToOU(),
56 | "ad_add_to_group": resourceAddToGroup(),
57 | "ad_user": resourceUser(),
58 | },
59 |
60 | ConfigureFunc: providerConfigure,
61 | }
62 | }
63 |
64 | func providerConfigure(d *schema.ResourceData) (interface{}, error) {
65 |
66 | config := Config{
67 | Domain: d.Get("domain").(string),
68 | IP: d.Get("ip").(string),
69 | URL: d.Get("url").(string),
70 | Username: d.Get("user").(string),
71 | Password: d.Get("password").(string),
72 | }
73 | log.Printf("[DEBUG] Connecting to AD")
74 | return config.Client()
75 | }
76 |
--------------------------------------------------------------------------------
/website/docs/r/group_to_ou.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Active Directory: ad_group_to_ou"
4 | sidebar_current: "docs-ad-resource-inventory-folder"
5 | description: |-
6 | Creates a group object in an Active Directory Organizational Unit.
7 | ---
8 |
9 | # ad\_group\_to\_ou
10 |
11 | Creates a group object in an Active Directory Organizational Unit.
12 |
13 | ## Example Usage
14 |
15 | ```hcl
16 | resource "ad_group_to_ou" "admins" {
17 | ou_distinguished_name = "OU=groups,DC=company,DC=com"
18 | group_name = "admins"
19 | description = "Managed by terraform."
20 | gid_number = 9001
21 | }
22 | ```
23 |
24 | ## Advanced auto_gid usage
25 |
26 | ```hcl
27 | resource "ad_group_to_ou" "main" {
28 | count = 10
29 | ou_distinguished_name = "OU=groups,DC=company,DC=com"
30 | group_name = "sample_group${count.index + 1}"
31 | description = "Managed by terraform."
32 | auto_gid = true
33 | auto_gid_min = 9001
34 | auto_gid_max = 9010
35 | }
36 | ```
37 |
38 | ## Argument Reference
39 |
40 | The following arguments are supported:
41 |
42 | * `ou_distinguished_name` - (Required) The distinguished name of the Organizational Unit of the Active Directory to add the group to.
43 | * `group_name` - (Required) The name of the group to be added.
44 | * `description` - (Optional) Sets the description property of the resultant group object.
45 | * `gid_number` - (Optional) Statically sets the 'gidNumber' attribute on the resultant group for use by Linux systems.
46 | * `auto_gid` - (Optional) Boolean to automatically set the 'gidNumber' attribute on the resultant group, and ensure that it's unique. Does nothing when `gid_number` is set.
47 | * `auto_gid_min` - (Optional) The lower bounds of automatically assignable gid numbers. Does nothing when `auto_gid` is not set, or set to 'false'.
48 | * `auto_gid_max` - (Optional) The upper bounds of automatically assignable gid numbers. Does nothing when `auto_gid` is not set, or set to 'false'.
49 |
50 | ## Attributes Reference
51 |
52 | The following attributes are exported:
53 |
54 | * `id` - A concatenation of `group_name`/`ou_distinguished_name`.
55 | * `auto_gid_number` - The 'gidNumber' that was set with `auto_gid`.
56 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/length.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | func readLength(reader io.Reader) (length int, read int, err error) {
10 | // length byte
11 | b, err := readByte(reader)
12 | if err != nil {
13 | if Debug {
14 | fmt.Printf("error reading length byte: %v\n", err)
15 | }
16 | return 0, 0, err
17 | }
18 | read++
19 |
20 | switch {
21 | case b == 0xFF:
22 | // Invalid 0xFF (x.600, 8.1.3.5.c)
23 | return 0, read, errors.New("invalid length byte 0xff")
24 |
25 | case b == LengthLongFormBitmask:
26 | // Indefinite form, we have to decode packets until we encounter an EOC packet (x.600, 8.1.3.6)
27 | length = LengthIndefinite
28 |
29 | case b&LengthLongFormBitmask == 0:
30 | // Short definite form, extract the length from the bottom 7 bits (x.600, 8.1.3.4)
31 | length = int(b) & LengthValueBitmask
32 |
33 | case b&LengthLongFormBitmask != 0:
34 | // Long definite form, extract the number of length bytes to follow from the bottom 7 bits (x.600, 8.1.3.5.b)
35 | lengthBytes := int(b) & LengthValueBitmask
36 | // Protect against overflow
37 | // TODO: support big int length?
38 | if lengthBytes > 8 {
39 | return 0, read, errors.New("long-form length overflow")
40 | }
41 |
42 | // Accumulate into a 64-bit variable
43 | var length64 int64
44 | for i := 0; i < lengthBytes; i++ {
45 | b, err = readByte(reader)
46 | if err != nil {
47 | if Debug {
48 | fmt.Printf("error reading long-form length byte %d: %v\n", i, err)
49 | }
50 | return 0, read, err
51 | }
52 | read++
53 |
54 | // x.600, 8.1.3.5
55 | length64 <<= 8
56 | length64 |= int64(b)
57 | }
58 |
59 | // Cast to a platform-specific integer
60 | length = int(length64)
61 | // Ensure we didn't overflow
62 | if int64(length) != length64 {
63 | return 0, read, errors.New("long-form length overflow")
64 | }
65 |
66 | default:
67 | return 0, read, errors.New("invalid length byte")
68 | }
69 |
70 | return length, read, nil
71 | }
72 |
73 | func encodeLength(length int) []byte {
74 | length_bytes := encodeUnsignedInteger(uint64(length))
75 | if length > 127 || len(length_bytes) > 1 {
76 | longFormBytes := []byte{(LengthLongFormBitmask | byte(len(length_bytes)))}
77 | longFormBytes = append(longFormBytes, length_bytes...)
78 | length_bytes = longFormBytes
79 | }
80 | return length_bytes
81 | }
82 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/compare.go:
--------------------------------------------------------------------------------
1 | // File contains Compare functionality
2 | //
3 | // https://tools.ietf.org/html/rfc4511
4 | //
5 | // CompareRequest ::= [APPLICATION 14] SEQUENCE {
6 | // entry LDAPDN,
7 | // ava AttributeValueAssertion }
8 | //
9 | // AttributeValueAssertion ::= SEQUENCE {
10 | // attributeDesc AttributeDescription,
11 | // assertionValue AssertionValue }
12 | //
13 | // AttributeDescription ::= LDAPString
14 | // -- Constrained to
15 | // -- [RFC4512]
16 | //
17 | // AttributeValue ::= OCTET STRING
18 | //
19 |
20 | package ldap
21 |
22 | import (
23 | "fmt"
24 |
25 | ber "github.com/go-asn1-ber/asn1-ber"
26 | )
27 |
28 | // CompareRequest represents an LDAP CompareRequest operation.
29 | type CompareRequest struct {
30 | DN string
31 | Attribute string
32 | Value string
33 | }
34 |
35 | func (req *CompareRequest) appendTo(envelope *ber.Packet) error {
36 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
37 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
38 |
39 | ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
40 | ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Attribute, "AttributeDesc"))
41 | ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Value, "AssertionValue"))
42 |
43 | pkt.AppendChild(ava)
44 |
45 | envelope.AppendChild(pkt)
46 |
47 | return nil
48 | }
49 |
50 | // Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
51 | // false with any error that occurs if any.
52 | func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
53 | msgCtx, err := l.doRequest(&CompareRequest{
54 | DN: dn,
55 | Attribute: attribute,
56 | Value: value})
57 | if err != nil {
58 | return false, err
59 | }
60 | defer l.finishMessage(msgCtx)
61 |
62 | packet, err := l.readPacket(msgCtx)
63 | if err != nil {
64 | return false, err
65 | }
66 |
67 | if packet.Children[1].Tag == ApplicationCompareResponse {
68 | err := GetLDAPError(packet)
69 |
70 | switch {
71 | case IsErrorWithCode(err, LDAPResultCompareTrue):
72 | return true, nil
73 | case IsErrorWithCode(err, LDAPResultCompareFalse):
74 | return false, nil
75 | default:
76 | return false, err
77 | }
78 | }
79 | return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)
80 | }
81 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/moddn.go:
--------------------------------------------------------------------------------
1 | // Package ldap - moddn.go contains ModifyDN functionality
2 | //
3 | // https://tools.ietf.org/html/rfc4511
4 | // ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
5 | // entry LDAPDN,
6 | // newrdn RelativeLDAPDN,
7 | // deleteoldrdn BOOLEAN,
8 | // newSuperior [0] LDAPDN OPTIONAL }
9 | //
10 | //
11 | package ldap
12 |
13 | import (
14 | "log"
15 |
16 | ber "github.com/go-asn1-ber/asn1-ber"
17 | )
18 |
19 | // ModifyDNRequest holds the request to modify a DN
20 | type ModifyDNRequest struct {
21 | DN string
22 | NewRDN string
23 | DeleteOldRDN bool
24 | NewSuperior string
25 | }
26 |
27 | // NewModifyDNRequest creates a new request which can be passed to ModifyDN().
28 | //
29 | // To move an object in the tree, set the "newSup" to the new parent entry DN. Use an
30 | // empty string for just changing the object's RDN.
31 | //
32 | // For moving the object without renaming, the "rdn" must be the first
33 | // RDN of the given DN.
34 | //
35 | // A call like
36 | // mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "")
37 | // will setup the request to just rename uid=someone,dc=example,dc=org to
38 | // uid=newname,dc=example,dc=org.
39 | func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest {
40 | return &ModifyDNRequest{
41 | DN: dn,
42 | NewRDN: rdn,
43 | DeleteOldRDN: delOld,
44 | NewSuperior: newSup,
45 | }
46 | }
47 |
48 | func (req *ModifyDNRequest) appendTo(envelope *ber.Packet) error {
49 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request")
50 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
51 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.NewRDN, "New RDN"))
52 | pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.DeleteOldRDN, "Delete old RDN"))
53 | if req.NewSuperior != "" {
54 | pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.NewSuperior, "New Superior"))
55 | }
56 |
57 | envelope.AppendChild(pkt)
58 |
59 | return nil
60 | }
61 |
62 | // ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument
63 | // to NewModifyDNRequest() is not "").
64 | func (l *Conn) ModifyDN(m *ModifyDNRequest) error {
65 | msgCtx, err := l.doRequest(m)
66 | if err != nil {
67 | return err
68 | }
69 | defer l.finishMessage(msgCtx)
70 |
71 | packet, err := l.readPacket(msgCtx)
72 | if err != nil {
73 | return err
74 | }
75 |
76 | if packet.Children[1].Tag == ApplicationModifyDNResponse {
77 | err := GetLDAPError(packet)
78 | if err != nil {
79 | return err
80 | }
81 | } else {
82 | log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
83 | }
84 | return nil
85 | }
86 |
--------------------------------------------------------------------------------
/website/docs/index.html.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: "ad"
3 | page_title: "Provider: Active Directory"
4 | sidebar_current: "docs-ad-index"
5 | description: |-
6 | The Active Directory provider is used to interact with the resources supported by
7 | Active Directory. The provider needs to be configured with the proper credentials
8 | before it can be used.
9 | ---
10 |
11 | # Active Directory Provider
12 |
13 | The Active Directory provider is used to interact with the resources supported by
14 | Active Directory.
15 | The provider needs to be configured with the proper credentials before it can be used.
16 |
17 | Use the navigation to the left to read about the available resources.
18 |
19 | ~> **NOTE:** The Active Directory Provider currently represents _initial support_
20 | and therefore may undergo significant changes as the community improves it. This
21 | provider at this time only supports adding Computer Resource
22 |
23 | ## Example Usage
24 |
25 | ```hcl
26 | # Configure the Active Directory Provider
27 | provider "ad" {
28 | domain = var.ad_server_domain
29 | user = var.ad_server_user
30 | password = var.ad_server_password
31 | ip = var.ad_server_ip
32 | }
33 | # Add computer to Active Directory
34 | resource "ad_computer" "foo" {
35 | domain = var.ad_domain
36 | computer_name = "terraformSample"
37 | description = "terraform sample server"
38 | }
39 | # Add computer to Organizational Unit of Active Directory
40 | resource "ad_computer_to_ou" "bar" {
41 | ou_distinguished_name = var.ad_ou_dn
42 | computer_name = "terraformOuSample"
43 | description = "terraform sample server to OU"
44 | }
45 | # Add Organizational Unit to Active Directory
46 | resource "ad_organizational_unit" "test" {
47 | ou_name = "example-ou"
48 | domain = "example.com"
49 | }
50 | ```
51 |
52 | ## Argument Reference
53 |
54 | The following arguments are used to configure the Active Directory Provider:
55 |
56 | * `user` - (Required) This is the username for Active Directory Server operations. Can also
57 | be specified with the `AD_USER` environment variable.
58 | * `password` - (Required) This is the password for Active Directory API operations. Can
59 | also be specified with the `AD_PASSWORD` environment variable.
60 | * `ip` - (Required) This is the Active Directory server ip for Active Directory
61 | operations. Can also be specified with the `AD_SERVER` environment
62 | variable.
63 | * `domain` - (Required) This is the domain of the Active Directory Server.
64 |
65 | ## Acceptance Tests
66 |
67 | The Active Directory provider's acceptance tests require the above provider
68 | configuration fields to be set using the documented environment variables.
69 |
70 | In addition, the following environment variables are used in tests, and must be
71 | set to valid values for your Active Directory environment:
72 |
73 | * AD\_COMPUTER\_DOMAIN
74 | * AD\_COMPUTER\_OU\_DISTINGUISHED\_NAME
75 | * AD\_GROUP\_OU\_DISTINGUISHED\_NAME
76 | * AD\_OU\_DOMAIN
77 |
78 | Once all these variables are in place, the tests can be run like this:
79 |
80 | ```
81 | make testacc TEST=./builtin/providers/ad
82 | ```
83 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/identifier.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | func readIdentifier(reader io.Reader) (Identifier, int, error) {
10 | identifier := Identifier{}
11 | read := 0
12 |
13 | // identifier byte
14 | b, err := readByte(reader)
15 | if err != nil {
16 | if Debug {
17 | fmt.Printf("error reading identifier byte: %v\n", err)
18 | }
19 | return Identifier{}, read, err
20 | }
21 | read++
22 |
23 | identifier.ClassType = Class(b) & ClassBitmask
24 | identifier.TagType = Type(b) & TypeBitmask
25 |
26 | if tag := Tag(b) & TagBitmask; tag != HighTag {
27 | // short-form tag
28 | identifier.Tag = tag
29 | return identifier, read, nil
30 | }
31 |
32 | // high-tag-number tag
33 | tagBytes := 0
34 | for {
35 | b, err := readByte(reader)
36 | if err != nil {
37 | if Debug {
38 | fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err)
39 | }
40 | return Identifier{}, read, err
41 | }
42 | tagBytes++
43 | read++
44 |
45 | // Lowest 7 bits get appended to the tag value (x.690, 8.1.2.4.2.b)
46 | identifier.Tag <<= 7
47 | identifier.Tag |= Tag(b) & HighTagValueBitmask
48 |
49 | // First byte may not be all zeros (x.690, 8.1.2.4.2.c)
50 | if tagBytes == 1 && identifier.Tag == 0 {
51 | return Identifier{}, read, errors.New("invalid first high-tag-number tag byte")
52 | }
53 | // Overflow of int64
54 | // TODO: support big int tags?
55 | if tagBytes > 9 {
56 | return Identifier{}, read, errors.New("high-tag-number tag overflow")
57 | }
58 |
59 | // Top bit of 0 means this is the last byte in the high-tag-number tag (x.690, 8.1.2.4.2.a)
60 | if Tag(b)&HighTagContinueBitmask == 0 {
61 | break
62 | }
63 | }
64 |
65 | return identifier, read, nil
66 | }
67 |
68 | func encodeIdentifier(identifier Identifier) []byte {
69 | b := []byte{0x0}
70 | b[0] |= byte(identifier.ClassType)
71 | b[0] |= byte(identifier.TagType)
72 |
73 | if identifier.Tag < HighTag {
74 | // Short-form
75 | b[0] |= byte(identifier.Tag)
76 | } else {
77 | // high-tag-number
78 | b[0] |= byte(HighTag)
79 |
80 | tag := identifier.Tag
81 |
82 | b = append(b, encodeHighTag(tag)...)
83 | }
84 | return b
85 | }
86 |
87 | func encodeHighTag(tag Tag) []byte {
88 | // set cap=4 to hopefully avoid additional allocations
89 | b := make([]byte, 0, 4)
90 | for tag != 0 {
91 | // t := last 7 bits of tag (HighTagValueBitmask = 0x7F)
92 | t := tag & HighTagValueBitmask
93 |
94 | // right shift tag 7 to remove what was just pulled off
95 | tag >>= 7
96 |
97 | // if b already has entries this entry needs a continuation bit (0x80)
98 | if len(b) != 0 {
99 | t |= HighTagContinueBitmask
100 | }
101 |
102 | b = append(b, byte(t))
103 | }
104 | // reverse
105 | // since bits were pulled off 'tag' small to high the byte slice is in reverse order.
106 | // example: tag = 0xFF results in {0x7F, 0x01 + 0x80 (continuation bit)}
107 | // this needs to be reversed into 0x81 0x7F
108 | for i, j := 0, len(b)-1; i < len(b)/2; i++ {
109 | b[i], b[j-i] = b[j-i], b[i]
110 | }
111 | return b
112 | }
113 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/add.go:
--------------------------------------------------------------------------------
1 | //
2 | // https://tools.ietf.org/html/rfc4511
3 | //
4 | // AddRequest ::= [APPLICATION 8] SEQUENCE {
5 | // entry LDAPDN,
6 | // attributes AttributeList }
7 | //
8 | // AttributeList ::= SEQUENCE OF attribute Attribute
9 |
10 | package ldap
11 |
12 | import (
13 | "log"
14 |
15 | ber "github.com/go-asn1-ber/asn1-ber"
16 | )
17 |
18 | // Attribute represents an LDAP attribute
19 | type Attribute struct {
20 | // Type is the name of the LDAP attribute
21 | Type string
22 | // Vals are the LDAP attribute values
23 | Vals []string
24 | }
25 |
26 | func (a *Attribute) encode() *ber.Packet {
27 | seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
28 | seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type"))
29 | set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
30 | for _, value := range a.Vals {
31 | set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
32 | }
33 | seq.AppendChild(set)
34 | return seq
35 | }
36 |
37 | // AddRequest represents an LDAP AddRequest operation
38 | type AddRequest struct {
39 | // DN identifies the entry being added
40 | DN string
41 | // Attributes list the attributes of the new entry
42 | Attributes []Attribute
43 | // Controls hold optional controls to send with the request
44 | Controls []Control
45 | }
46 |
47 | func (req *AddRequest) appendTo(envelope *ber.Packet) error {
48 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request")
49 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
50 | attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
51 | for _, attribute := range req.Attributes {
52 | attributes.AppendChild(attribute.encode())
53 | }
54 | pkt.AppendChild(attributes)
55 |
56 | envelope.AppendChild(pkt)
57 | if len(req.Controls) > 0 {
58 | envelope.AppendChild(encodeControls(req.Controls))
59 | }
60 |
61 | return nil
62 | }
63 |
64 | // Attribute adds an attribute with the given type and values
65 | func (req *AddRequest) Attribute(attrType string, attrVals []string) {
66 | req.Attributes = append(req.Attributes, Attribute{Type: attrType, Vals: attrVals})
67 | }
68 |
69 | // NewAddRequest returns an AddRequest for the given DN, with no attributes
70 | func NewAddRequest(dn string, controls []Control) *AddRequest {
71 | return &AddRequest{
72 | DN: dn,
73 | Controls: controls,
74 | }
75 |
76 | }
77 |
78 | // Add performs the given AddRequest
79 | func (l *Conn) Add(addRequest *AddRequest) error {
80 | msgCtx, err := l.doRequest(addRequest)
81 | if err != nil {
82 | return err
83 | }
84 | defer l.finishMessage(msgCtx)
85 |
86 | packet, err := l.readPacket(msgCtx)
87 | if err != nil {
88 | return err
89 | }
90 |
91 | if packet.Children[1].Tag == ApplicationAddResponse {
92 | err := GetLDAPError(packet)
93 | if err != nil {
94 | return err
95 | }
96 | } else {
97 | log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
98 | }
99 | return nil
100 | }
101 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_organizational_unit_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform/helper/resource"
10 | "github.com/hashicorp/terraform/terraform"
11 | "gopkg.in/ldap.v3"
12 | )
13 |
14 | func TestAccAdOU_Basic(t *testing.T) {
15 | resource.Test(t, resource.TestCase{
16 | PreCheck: func() {
17 | testAccPreCheck(t)
18 | testAccResourceAdOUPreCheck(t)
19 | },
20 | Providers: testAccProviders,
21 | CheckDestroy: testAccCheckAdOUDestroy("ad_organizational_unit.test"),
22 | Steps: []resource.TestStep{
23 | resource.TestStep{
24 | Config: testAccResourceAdOUConfig(),
25 | Check: resource.ComposeTestCheckFunc(
26 | testAccCheckAdOUExists("ad_organizational_unit.test"),
27 | resource.TestCheckResourceAttr(
28 | "ad_organizational_unit.test", "ou_name", "terraform"),
29 | ),
30 | },
31 | },
32 | })
33 | }
34 |
35 | func testAccResourceAdOUPreCheck(t *testing.T) {
36 | if v := os.Getenv("AD_OU_DISTINGUISHED_NAME"); v == "" {
37 | t.Fatal("AD_OU_DISTINGUISHED_NAME must be set for acceptance tests")
38 | }
39 | }
40 |
41 | func testAccCheckAdOUDestroy(n string) resource.TestCheckFunc {
42 | return func(s *terraform.State) error {
43 | rs, ok := s.RootModule().Resources[n]
44 |
45 | if !ok {
46 | return fmt.Errorf("Not found: %s", n)
47 | }
48 |
49 | if rs.Primary.ID == "" {
50 | return fmt.Errorf("No AD OU ID is set")
51 | }
52 | client := testAccProvider.Meta().(*ldap.Conn)
53 | dnOfOU := rs.Primary.Attributes["ou_distinguished_name"]
54 | searchRequest := ldap.NewSearchRequest(
55 | dnOfOU, //"cn=code1,cn=OU,dc=terraform,dc=local", // The base dn to search
56 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
57 | "(&(objectClass=organizationalUNit)(cn="+rs.Primary.Attributes["ou_name"]+"))", // The filter to apply
58 | []string{"dn", "cn"}, // A list attributes to retrieve
59 | nil,
60 | )
61 | sr, err := client.Search(searchRequest)
62 | if err != nil {
63 | return err
64 | }
65 | if len(sr.Entries) == 0 {
66 | return nil
67 | }
68 |
69 | return fmt.Errorf("OU AD still exists")
70 | }
71 |
72 | }
73 |
74 | func testAccCheckAdOUExists(n string) resource.TestCheckFunc {
75 | return func(s *terraform.State) error {
76 | rs, ok := s.RootModule().Resources[n]
77 |
78 | if !ok {
79 | return fmt.Errorf("Not found: %s", n)
80 | }
81 |
82 | if rs.Primary.ID == "" {
83 | return fmt.Errorf("No AD OU ID is set")
84 | }
85 | client := testAccProvider.Meta().(*ldap.Conn)
86 | dnOfOU := rs.Primary.Attributes["ou_distinguished_name"]
87 | searchRequest := ldap.NewSearchRequest(
88 | dnOfOU, //"cn=code1,cn=OUs,dc=terraform,dc=local", // The base dn to search
89 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
90 | "(&(objectClass=organizationalUNit)(cn="+rs.Primary.Attributes["ou_name"]+"))", // The filter to apply
91 | []string{"dn", "cn"}, // A list attributes to retrieve
92 | nil,
93 | )
94 | sr, err := client.Search(searchRequest)
95 | if err != nil {
96 | return err
97 | }
98 | if len(sr.Entries) > 0 {
99 | return nil
100 | }
101 | return nil
102 | }
103 | }
104 |
105 | func testAccResourceAdOUConfig() string {
106 | return fmt.Sprintf(`
107 | provider "ad" {
108 | domain = "%s"
109 | ip = "%s"
110 | user = "%s"
111 | password = "%s"
112 | }
113 | resource "ad_organizational_unit" "test" {
114 | ou_name = "terraform"
115 | ou_distinguished_name = "%[5]s"
116 | description = "terraform test"
117 | }`,
118 | os.Getenv("AD_DOMAIN"),
119 | os.Getenv("AD_IP"),
120 | os.Getenv("AD_USER"),
121 | os.Getenv("AD_PASSWORD"),
122 | os.Getenv("AD_OU_DISTINGUISHED_NAME"))
123 | }
124 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_organizational_unit.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 |
8 | "github.com/hashicorp/terraform/helper/schema"
9 | ldap "gopkg.in/ldap.v3"
10 | )
11 |
12 | func resourceOU() *schema.Resource {
13 | return &schema.Resource{
14 | Create: ressourceADOUCreate,
15 | Read: resourceADOURead,
16 | Delete: resourceADOUDelete,
17 |
18 | Schema: map[string]*schema.Schema{
19 | "ou_name": {
20 | Type: schema.TypeString,
21 | Required: true,
22 | ForceNew: true,
23 | },
24 | "ou_distinguished_name": {
25 | Type: schema.TypeString,
26 | Required: true,
27 | ForceNew: true,
28 | },
29 | "description": {
30 | Type: schema.TypeString,
31 | Optional: true,
32 | Default: nil,
33 | ForceNew: true,
34 | },
35 | },
36 | }
37 | }
38 |
39 | func ressourceADOUCreate(d *schema.ResourceData, m interface{}) error {
40 | client := m.(*ldap.Conn) // m is our client to talk to server
41 | ouName := d.Get("ou_name").(string)
42 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
43 | description := d.Get("description").(string)
44 | var dnOfOU string
45 | dnOfOU += "OU=" + ouName + "," + OUDistinguishedName //object's entire path to the root
46 | log.Printf("[DEBUG] dnOfOU: %s ", dnOfOU)
47 | log.Printf("[DEBUG] Adding OU : %s ", ouName)
48 | err := addOU(ouName, dnOfOU, client, description)
49 | if err != nil {
50 | log.Printf("[ERROR] Error while adding OU: %s ", err)
51 | return fmt.Errorf("Error while adding OU %s", err)
52 | }
53 | log.Printf("[DEBUG] OU Added successfully: %s", ouName)
54 | d.SetId(OUDistinguishedName + "/" + ouName)
55 | return nil
56 |
57 | }
58 |
59 | func resourceADOURead(d *schema.ResourceData, m interface{}) error {
60 | client := m.(*ldap.Conn)
61 |
62 | ouName := d.Get("ou_name").(string)
63 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
64 |
65 | var dnOfOU string
66 | dnOfOU += OUDistinguishedName
67 |
68 | log.Printf("[DEBUG] Searching OU with domain: %s ", dnOfOU)
69 |
70 | NewReq := ldap.NewSearchRequest( //represents the search request send to the server
71 | dnOfOU, // base dnOfOU.
72 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
73 | "(&(objectClass=OrganizationalUnit)(ou="+ouName+"))", //applied filter
74 | []string{"ou", "dn"},
75 | nil,
76 | )
77 |
78 | sr, err := client.Search(NewReq)
79 | if err != nil {
80 | log.Printf("[ERROR] while seaching OU : %s", err)
81 | return fmt.Errorf("Error while searching OU : %s", err)
82 | }
83 |
84 | log.Println("[DEBUG] Found " + strconv.Itoa(len(sr.Entries)) + " Entries")
85 | for _, entry := range sr.Entries {
86 | log.Printf("[DEBUG] %s: %v\n", entry.DN, entry.GetAttributeValue("ou"))
87 |
88 | }
89 |
90 | if len(sr.Entries) == 0 {
91 | log.Println("[DEBUG] OU not found")
92 | d.SetId("")
93 | }
94 | return nil
95 | }
96 |
97 | func resourceADOUDelete(d *schema.ResourceData, m interface{}) error {
98 | log.Println("[ERROR] Finding OU")
99 | resourceADOURead(d, m)
100 | if d.Id() == "" {
101 | log.Println("[ERROR] Cannot find OU in the specified AD")
102 | return fmt.Errorf("[ERROR] Cannot find OU in the specified AD")
103 | }
104 | client := m.(*ldap.Conn)
105 |
106 | ouName := d.Get("ou_name").(string)
107 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
108 |
109 | var dnOfOU string
110 | dnOfOU += "OU=" + ouName + "," + OUDistinguishedName
111 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfOU)
112 | log.Printf("[DEBUG] Deleting the OU from the AD : %s ", ouName)
113 |
114 | err := deleteOU(dnOfOU, client)
115 | if err != nil {
116 | log.Printf("[ERROR] Error while Deleting OU from AD : %s ", err)
117 | return fmt.Errorf("Error while Deleting OU from AD %s", err)
118 | }
119 | log.Printf("[DEBUG] OU deleted from AD successfully: %s", ouName)
120 | return nil
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_user_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform/helper/resource"
10 | "github.com/hashicorp/terraform/terraform"
11 | "gopkg.in/ldap.v3"
12 | )
13 |
14 | //test function:
15 | func TestAccAdUser_basic(t *testing.T) {
16 | resource.Test(t, resource.TestCase{
17 | PreCheck: func() {
18 | testAccPreCheck(t)
19 | testAccResourceAdUserPreCheck(t)
20 | },
21 | Providers: testAccProviders,
22 | CheckDestroy: testAccCheckAdUserDestroy("ad_user.test"),
23 | Steps: []resource.TestStep{
24 | resource.TestStep{
25 | Config: testAccResourceAdUserConfig(),
26 | Check: resource.ComposeTestCheckFunc(
27 | testAccCheckAdUserExists("ad_user.test"),
28 | resource.TestCheckResourceAttr(
29 | "ad_user.test", "logon_name", "test"),
30 | ),
31 | },
32 | },
33 | })
34 | }
35 |
36 | func testAccResourceAdUserPreCheck(t *testing.T) {
37 | if v := os.Getenv("AD_USER_DOMAIN"); v == "" {
38 | t.Fatal("User Domain must be set for acceptance tests")
39 | }
40 | }
41 |
42 | func testAccCheckAdUserDestroy(n string) resource.TestCheckFunc {
43 | return func(s *terraform.State) error {
44 | rs, ok := s.RootModule().Resources[n]
45 |
46 | if !ok {
47 | return fmt.Errorf("Not found: %s", n)
48 | }
49 |
50 | if rs.Primary.ID == "" {
51 | return fmt.Errorf("No AD User ID is set")
52 | }
53 | client := testAccProvider.Meta().(*ldap.Conn)
54 | domain := rs.Primary.Attributes["domain"]
55 | var dnOfUser string
56 | domainArr := strings.Split(domain, ".")
57 | dnOfUser = "dc=" + domainArr[0]
58 | for index, item := range domainArr {
59 | if index == 0 {
60 | continue
61 | }
62 | dnOfUser += ",dc=" + item
63 | }
64 | searchRequest := ldap.NewSearchRequest(
65 | dnOfUser,
66 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
67 | "(&(objectClass=User)(cn="+rs.Primary.Attributes["name"]+"))", // The filter to apply
68 | []string{"dn", "cn"}, // A list attributes to retrieve
69 | nil,
70 | )
71 | sr, err := client.Search(searchRequest)
72 | if err != nil {
73 | return err
74 | }
75 | if len(sr.Entries) == 0 {
76 | return nil
77 | }
78 |
79 | return fmt.Errorf("User AD still exists")
80 | }
81 |
82 | }
83 |
84 | func testAccCheckAdUserExists(n string) resource.TestCheckFunc {
85 | return func(s *terraform.State) error {
86 | rs, ok := s.RootModule().Resources[n]
87 |
88 | if !ok {
89 | return fmt.Errorf("Not found: %s", n)
90 | }
91 |
92 | if rs.Primary.ID == "" {
93 | return fmt.Errorf("No AD User ID is set")
94 | }
95 | client := testAccProvider.Meta().(*ldap.Conn)
96 | domain := rs.Primary.Attributes["domain"]
97 | var dnOfUser string
98 | domainArr := strings.Split(domain, ".")
99 | dnOfUser = "dc=" + domainArr[0]
100 | for index, item := range domainArr {
101 | if index == 0 {
102 | continue
103 | }
104 | dnOfUser += ",dc=" + item
105 | }
106 | searchRequest := ldap.NewSearchRequest(
107 | dnOfUser,
108 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
109 | "(&(objectClass=User)(cn="+rs.Primary.Attributes["name"]+"))", // The filter to apply
110 | []string{"dn", "cn"}, // A list attributes to retrieve
111 | nil,
112 | )
113 | sr, err := client.Search(searchRequest)
114 | if err != nil {
115 | return err
116 | }
117 | if len(sr.Entries) > 0 {
118 | return nil
119 | }
120 | return nil
121 | }
122 | }
123 |
124 | func testAccResourceAdUserConfig() string {
125 | return fmt.Sprintf(`
126 | provider "ad" {
127 | domain = "%s"
128 | ip = "%s"
129 | url = "%s"
130 | user = "%s"
131 | password = "%s"
132 | }
133 | resource "ad_user" "test" {
134 | domain = "%s"
135 | first_name = "first"
136 | last_name = "last"
137 | logon_name = "test"
138 | password = "testpassword"
139 | }`,
140 | os.Getenv("AD_DOMAIN"),
141 | os.Getenv("AD_IP"),
142 | os.Getenv("AD_URL"),
143 | os.Getenv("AD_USER"),
144 | os.Getenv("AD_PASSWORD"),
145 | os.Getenv("AD_USER_DOMAIN"))
146 | }
147 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_computer_to_ou_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 |
8 | "github.com/hashicorp/terraform/helper/resource"
9 | "github.com/hashicorp/terraform/terraform"
10 | "gopkg.in/ldap.v3"
11 | )
12 |
13 | func TestAccAdComputerToOU_Basic(t *testing.T) {
14 | resource.Test(t, resource.TestCase{
15 | PreCheck: func() {
16 | testAccPreCheck(t)
17 | testAccResourceAdComputerToOUPreCheck(t)
18 | },
19 | Providers: testAccProviders,
20 | CheckDestroy: testAccCheckAdComputerToOUDestroy("ad_computer_to_ou.test"),
21 | Steps: []resource.TestStep{
22 | resource.TestStep{
23 | Config: testAccResourceAdComputerToOUConfig(),
24 | Check: resource.ComposeTestCheckFunc(
25 | testAccCheckAdComputerToOUExists("ad_computer_to_ou.test"),
26 | resource.TestCheckResourceAttr(
27 | "ad_computer_to_ou.test", "computer_name", "terraform"),
28 | ),
29 | },
30 | },
31 | })
32 | }
33 |
34 | func testAccResourceAdComputerToOUPreCheck(t *testing.T) {
35 | if v := os.Getenv("AD_COMPUTER_OU_DISTINGUISHED_NAME"); v == "" {
36 | t.Fatal("AD_COMPUTER_OU_DISTINGUISHED_NAME must be set for acceptance tests")
37 | }
38 | }
39 |
40 | func testAccCheckAdComputerToOUDestroy(n string) resource.TestCheckFunc {
41 | return func(s *terraform.State) error {
42 | rs, ok := s.RootModule().Resources[n]
43 |
44 | if !ok {
45 | return fmt.Errorf("Not found: %s", n)
46 | }
47 |
48 | if rs.Primary.ID == "" {
49 | return fmt.Errorf("No AD Computer ID is set")
50 | }
51 | client := testAccProvider.Meta().(*ldap.Conn)
52 | ouDistinguishedName := rs.Primary.Attributes["ou_distinguished_name"]
53 | var dnOfComputer string
54 | dnOfComputer = ouDistinguishedName
55 | searchRequest := ldap.NewSearchRequest(
56 | dnOfComputer, //"cn=code1,ou=DevComputers,dc=terraform,dc=local", // The base dn to search
57 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
58 | "(&(objectClass=Computer)(cn="+rs.Primary.Attributes["computer_name"]+"))", // The filter to apply
59 | []string{"dn", "cn"}, // A list attributes to retrieve
60 | nil,
61 | )
62 | sr, err := client.Search(searchRequest)
63 | if err != nil {
64 | return err
65 | }
66 | if len(sr.Entries) == 0 {
67 | return nil
68 | }
69 |
70 | return fmt.Errorf("Computer AD still exists")
71 | }
72 |
73 | }
74 |
75 | func testAccCheckAdComputerToOUExists(n string) resource.TestCheckFunc {
76 | return func(s *terraform.State) error {
77 | rs, ok := s.RootModule().Resources[n]
78 |
79 | if !ok {
80 | return fmt.Errorf("Not found: %s", n)
81 | }
82 |
83 | if rs.Primary.ID == "" {
84 | return fmt.Errorf("No AD Computer ID is set")
85 | }
86 | client := testAccProvider.Meta().(*ldap.Conn)
87 | ouDistinguishedName := rs.Primary.Attributes["ou_distinguished_name"]
88 | var dnOfComputer string
89 | dnOfComputer = ouDistinguishedName
90 | searchRequest := ldap.NewSearchRequest(
91 | dnOfComputer, //"cn=code1,ou=DevComputers,dc=terraform,dc=local", // The base dn to search
92 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
93 | "(&(objectClass=Computer)(cn="+rs.Primary.Attributes["computer_name"]+"))", // The filter to apply
94 | []string{"dn", "cn"}, // A list attributes to retrieve
95 | nil,
96 | )
97 | sr, err := client.Search(searchRequest)
98 | if err != nil {
99 | return err
100 | }
101 | if len(sr.Entries) > 0 {
102 | return nil
103 | }
104 | return nil
105 | }
106 | }
107 |
108 | func testAccResourceAdComputerToOUConfig() string {
109 | return fmt.Sprintf(`
110 | provider "ad" {
111 | domain = "%s"
112 | ip = "%s"
113 | url = "%s"
114 | user = "%s"
115 | password = "%s"
116 | }
117 |
118 | resource "ad_computer_to_ou" "test" {
119 | ou_distinguished_name = "%s"
120 | computer_name = "terraform"
121 | description = "terraform test"
122 | }`,
123 | os.Getenv("AD_DOMAIN"),
124 | os.Getenv("AD_IP"),
125 | os.Getenv("AD_URL"),
126 | os.Getenv("AD_USER"),
127 | os.Getenv("AD_PASSWORD"),
128 | os.Getenv("AD_COMPUTER_OU_DISTINGUISHED_NAME"))
129 | }
130 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_computer_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform/helper/resource"
10 | "github.com/hashicorp/terraform/terraform"
11 | "gopkg.in/ldap.v3"
12 | )
13 |
14 | func TestAccAdComputer_Basic(t *testing.T) {
15 | resource.Test(t, resource.TestCase{
16 | PreCheck: func() {
17 | testAccPreCheck(t)
18 | testAccResourceAdComputerPreCheck(t)
19 | },
20 | Providers: testAccProviders,
21 | CheckDestroy: testAccCheckAdComputerDestroy("ad_computer.test"),
22 | Steps: []resource.TestStep{
23 | resource.TestStep{
24 | Config: testAccResourceAdComputerConfig(),
25 | Check: resource.ComposeTestCheckFunc(
26 | testAccCheckAdComputerExists("ad_computer.test"),
27 | resource.TestCheckResourceAttr(
28 | "ad_computer.test", "computer_name", "terraform"),
29 | ),
30 | },
31 | },
32 | })
33 | }
34 |
35 | func testAccResourceAdComputerPreCheck(t *testing.T) {
36 | if v := os.Getenv("AD_COMPUTER_DOMAIN"); v == "" {
37 | t.Fatal("AD_COMPUTER_DOMAIN must be set for acceptance tests")
38 | }
39 | }
40 |
41 | func testAccCheckAdComputerDestroy(n string) resource.TestCheckFunc {
42 | return func(s *terraform.State) error {
43 | rs, ok := s.RootModule().Resources[n]
44 |
45 | if !ok {
46 | return fmt.Errorf("Not found: %s", n)
47 | }
48 |
49 | if rs.Primary.ID == "" {
50 | return fmt.Errorf("No AD Computer ID is set")
51 | }
52 | client := testAccProvider.Meta().(*ldap.Conn)
53 | domain := rs.Primary.Attributes["domain"]
54 | var dnOfComputer string
55 | domainArr := strings.Split(domain, ".")
56 | dnOfComputer = "dc=" + domainArr[0]
57 | for index, item := range domainArr {
58 | if index == 0 {
59 | continue
60 | }
61 | dnOfComputer += ",dc=" + item
62 | }
63 | searchRequest := ldap.NewSearchRequest(
64 | dnOfComputer, //"cn=code1,cn=Computers,dc=terraform,dc=local", // The base dn to search
65 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
66 | "(&(objectClass=Computer)(cn="+rs.Primary.Attributes["computer_name"]+"))", // The filter to apply
67 | []string{"dn", "cn"}, // A list attributes to retrieve
68 | nil,
69 | )
70 | sr, err := client.Search(searchRequest)
71 | if err != nil {
72 | return err
73 | }
74 | if len(sr.Entries) == 0 {
75 | return nil
76 | }
77 |
78 | return fmt.Errorf("Computer AD still exists")
79 | }
80 |
81 | }
82 |
83 | func testAccCheckAdComputerExists(n string) resource.TestCheckFunc {
84 | return func(s *terraform.State) error {
85 | rs, ok := s.RootModule().Resources[n]
86 |
87 | if !ok {
88 | return fmt.Errorf("Not found: %s", n)
89 | }
90 |
91 | if rs.Primary.ID == "" {
92 | return fmt.Errorf("No AD Computer ID is set")
93 | }
94 | client := testAccProvider.Meta().(*ldap.Conn)
95 | domain := rs.Primary.Attributes["domain"]
96 | var dnOfComputer string
97 | domainArr := strings.Split(domain, ".")
98 | dnOfComputer = "dc=" + domainArr[0]
99 | for index, item := range domainArr {
100 | if index == 0 {
101 | continue
102 | }
103 | dnOfComputer += ",dc=" + item
104 | }
105 | searchRequest := ldap.NewSearchRequest(
106 | dnOfComputer, //"cn=code1,cn=Computers,dc=terraform,dc=local", // The base dn to search
107 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
108 | "(&(objectClass=Computer)(cn="+rs.Primary.Attributes["computer_name"]+"))", // The filter to apply
109 | []string{"dn", "cn"}, // A list attributes to retrieve
110 | nil,
111 | )
112 | sr, err := client.Search(searchRequest)
113 | if err != nil {
114 | return err
115 | }
116 | if len(sr.Entries) > 0 {
117 | return nil
118 | }
119 | return nil
120 | }
121 | }
122 |
123 | func testAccResourceAdComputerConfig() string {
124 | return fmt.Sprintf(`
125 | provider "ad" {
126 | domain = "%s"
127 | ip = "%s"
128 | url = "%s"
129 | user = "%s"
130 | password = "%s"
131 | }
132 | resource "ad_computer" "test" {
133 | domain = "%s"
134 | computer_name = "terraform"
135 | description = "terraform test"
136 | }`,
137 | os.Getenv("AD_DOMAIN"),
138 | os.Getenv("AD_IP"),
139 | os.Getenv("AD_URL"),
140 | os.Getenv("AD_USER"),
141 | os.Getenv("AD_PASSWORD"),
142 | os.Getenv("AD_COMPUTER_DOMAIN"))
143 | }
144 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_computer_to_ou.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 |
8 | ldap "gopkg.in/ldap.v3"
9 |
10 | "github.com/hashicorp/terraform/helper/schema"
11 | )
12 |
13 | func resourceComputerToOU() *schema.Resource {
14 | return &schema.Resource{
15 | Create: resourceADComputerToOUCreate,
16 | Read: resourceADComputerToOURead,
17 | Delete: resourceADComputerToOUDelete,
18 | Schema: map[string]*schema.Schema{
19 | "computer_name": {
20 | Type: schema.TypeString,
21 | Required: true,
22 | ForceNew: true,
23 | },
24 | "ou_distinguished_name": {
25 | Type: schema.TypeString,
26 | Required: true,
27 | ForceNew: true,
28 | },
29 | "description": {
30 | Type: schema.TypeString,
31 | Optional: true,
32 | Default: nil,
33 | ForceNew: true,
34 | },
35 | },
36 | }
37 | }
38 |
39 | func resourceADComputerToOUCreate(d *schema.ResourceData, meta interface{}) error {
40 | client := meta.(*ldap.Conn)
41 |
42 | computerName := d.Get("computer_name").(string)
43 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
44 | description := d.Get("description").(string)
45 | var dnOfComputer string
46 | dnOfComputer += "cn=" + computerName + "," + OUDistinguishedName
47 |
48 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
49 | log.Printf("[DEBUG] Adding the Computer to the AD : %s ", computerName)
50 |
51 | err := addComputerToAD(computerName, dnOfComputer, client, description)
52 | if err != nil {
53 | log.Printf("[ERROR] Error while adding a Computer to the AD : %s ", err)
54 | return fmt.Errorf("Error while adding a Computer to the AD %s", err)
55 | }
56 | log.Printf("[DEBUG] Computer Added to AD successfully: %s", computerName)
57 | d.SetId(OUDistinguishedName + "/" + computerName)
58 | return nil
59 | }
60 |
61 | func resourceADComputerToOURead(d *schema.ResourceData, meta interface{}) error {
62 | log.Println("[ERROR] In Read function")
63 | client := meta.(*ldap.Conn)
64 |
65 | computerName := d.Get("computer_name").(string)
66 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
67 | var dnOfComputer string
68 | dnOfComputer += OUDistinguishedName
69 |
70 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
71 | log.Printf("[DEBUG] Searching the Computer from the AD : %s ", computerName)
72 |
73 | searchRequest := ldap.NewSearchRequest(
74 | dnOfComputer, // The base dn to search
75 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
76 | "(&(objectClass=Computer)(cn="+computerName+"))", // The filter to apply
77 | []string{"dn", "cn"}, // A list attributes to retrieve
78 | nil,
79 | )
80 |
81 | sr, err := client.Search(searchRequest)
82 | if err != nil {
83 | log.Printf("[ERROR] Error while searching a Computer : %s ", err)
84 | return fmt.Errorf("Error while searching a Computer : %s", err)
85 | }
86 | fmt.Println("[ERROR] Found " + strconv.Itoa(len(sr.Entries)) + " Entries")
87 | for _, entry := range sr.Entries {
88 | fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
89 | }
90 | if len(sr.Entries) == 0 {
91 | log.Println("[ERROR] Computer was not found")
92 | d.SetId("")
93 | }
94 | return nil
95 | }
96 |
97 | func resourceADComputerToOUDelete(d *schema.ResourceData, meta interface{}) error {
98 | log.Println("[ERROR] Finding computer")
99 | resourceADComputerToOURead(d, meta)
100 | if d.Id() == "" {
101 | log.Println("[ERROR] Cannot find Computer in the specified AD")
102 | return fmt.Errorf("[ERROR] Cannot find Computer in the specified AD")
103 | }
104 | client := meta.(*ldap.Conn)
105 |
106 | computerName := d.Get("computer_name").(string)
107 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
108 | var dnOfComputer string
109 | dnOfComputer += "cn=" + computerName + "," + OUDistinguishedName
110 |
111 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
112 | log.Printf("[DEBUG] Deleting the Computer from the AD : %s ", computerName)
113 |
114 | err := deleteComputerFromAD(dnOfComputer, client)
115 | if err != nil {
116 | log.Printf("[ERROR] Error while Deleting a Computer from AD : %s ", err)
117 | return fmt.Errorf("Error while Deleting a Computer from AD %s", err)
118 | }
119 | log.Printf("[DEBUG] Computer deleted from AD successfully: %s", computerName)
120 | return nil
121 | }
122 |
--------------------------------------------------------------------------------
/ad/active_directory_group_helper.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sort"
7 | "strconv"
8 | "strings"
9 |
10 | ldap "gopkg.in/ldap.v3"
11 | )
12 |
13 | func addGroupToAD(groupName string, dnName string, adConn *ldap.Conn, desc string, gidNumber string) error {
14 | addRequest := ldap.NewAddRequest(dnName, nil)
15 | addRequest.Attribute("objectClass", []string{"group"})
16 | addRequest.Attribute("sAMAccountName", []string{groupName})
17 | if desc != "" {
18 | addRequest.Attribute("description", []string{desc})
19 | }
20 | if gidNumber != "" {
21 | addRequest.Attribute("gidNumber", []string{gidNumber})
22 | }
23 | err := adConn.Add(addRequest)
24 | if err != nil {
25 | return err
26 | }
27 | return nil
28 | }
29 |
30 | func deleteGroupFromAD(dnName string, adConn *ldap.Conn) error {
31 | delRequest := ldap.NewDelRequest(dnName, nil)
32 | err := adConn.Del(delRequest)
33 | if err != nil {
34 | return err
35 | }
36 | return nil
37 | }
38 |
39 | func find_next_gidNumber(dnName string, adConn *ldap.Conn, auto_gid_min int, auto_gid_max int) (error, int) {
40 | log.Printf("[DEBUG] Searching next available gidNumber between %d and %d for %s.\n", auto_gid_min, auto_gid_max, dnName)
41 | baseDN := strings.Split(dnName, ",")
42 | searchRequest := ldap.NewSearchRequest(
43 | baseDN[len(baseDN)-2]+","+baseDN[len(baseDN)-1], // The base dn to search
44 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
45 | "(&(objectCategory=group)(gidNumber>="+strconv.Itoa(auto_gid_min)+")(gidNumber<="+strconv.Itoa(auto_gid_max)+"))", // The filter to apply
46 | []string{"dn", "gidNumber"}, // A list attributes to retrieve
47 | nil,
48 | )
49 | searchResult, err := adConn.Search(searchRequest)
50 | if err != nil {
51 | log.Fatal(err)
52 | return err, 0
53 | }
54 |
55 | var used_gid_numbers []int
56 | for _, entry := range searchResult.Entries {
57 | gid_number, _ := strconv.Atoi(entry.GetAttributeValues("gidNumber")[0])
58 | used_gid_numbers = append(used_gid_numbers, gid_number)
59 | }
60 |
61 | sort.Ints(used_gid_numbers)
62 |
63 | var next_available_gid int
64 | for potential_gid := auto_gid_min; potential_gid <= auto_gid_max; potential_gid++ {
65 | var potential_gid_used bool = false
66 | for _, used_gid := range used_gid_numbers {
67 | if used_gid == potential_gid {
68 | potential_gid_used = true
69 | break
70 | }
71 | }
72 | if potential_gid_used != true {
73 | next_available_gid = potential_gid
74 | break
75 | }
76 | }
77 |
78 | if next_available_gid == 0 {
79 | return fmt.Errorf("No available gidNumbers remaining."), 0
80 | }
81 |
82 | return nil, next_available_gid
83 | }
84 |
85 | func find_duplicate_gidNumber(dnName string, adConn *ldap.Conn, gidNumber int, auto_gid_min int, auto_gid_max int) (error, bool) {
86 | log.Printf("[DEBUG] Searching for duplicate groups with gidNumber %d.", gidNumber)
87 | baseDN := strings.Split(dnName, ",")
88 | searchRequest := ldap.NewSearchRequest(
89 | baseDN[len(baseDN)-2]+","+baseDN[len(baseDN)-1], // The base dn to search
90 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
91 | "(&(objectCategory=group)(gidNumber>="+strconv.Itoa(auto_gid_min)+")(gidNumber<="+strconv.Itoa(auto_gid_max)+"))", // The filter to apply
92 | []string{"dn", "gidNumber"}, // A list attributes to retrieve
93 | nil,
94 | )
95 | searchResult, err := adConn.Search(searchRequest)
96 | if err != nil {
97 | log.Fatal(err)
98 | return err, false
99 | }
100 |
101 | var used_gid_numbers []int
102 | for _, entry := range searchResult.Entries {
103 | gid_number, _ := strconv.Atoi(entry.GetAttributeValues("gidNumber")[0])
104 | used_gid_numbers = append(used_gid_numbers, gid_number)
105 | }
106 |
107 | var gidNumber_duplicate bool = false
108 | var gidNumber_times_used int = 0
109 | for _, used_gid := range used_gid_numbers {
110 | if used_gid == gidNumber {
111 | gidNumber_times_used++
112 | if gidNumber_times_used > 1 {
113 | gidNumber_duplicate = true
114 | break
115 | }
116 | }
117 | }
118 |
119 | return err, gidNumber_duplicate
120 | }
121 |
122 | func update_gidNumber(dnName string, adConn *ldap.Conn, gidNumber int) error {
123 | log.Printf("[DEBUG] Setting gidNumber %d on %s", gidNumber, dnName)
124 | modifyRequest := ldap.NewModifyRequest(dnName, nil)
125 | modifyRequest.Replace("gidNumber", []string{strconv.Itoa(gidNumber)})
126 |
127 | err := adConn.Modify(modifyRequest)
128 | if err != nil {
129 | return err
130 | }
131 | return nil
132 | }
133 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_computer.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | "strings"
8 |
9 | ldap "gopkg.in/ldap.v3"
10 |
11 | "github.com/hashicorp/terraform/helper/schema"
12 | )
13 |
14 | func resourceComputer() *schema.Resource {
15 | return &schema.Resource{
16 | Create: resourceADComputerCreate,
17 | Read: resourceADComputerRead,
18 | Delete: resourceADComputerDelete,
19 | Schema: map[string]*schema.Schema{
20 | "computer_name": {
21 | Type: schema.TypeString,
22 | Required: true,
23 | ForceNew: true,
24 | },
25 | "domain": {
26 | Type: schema.TypeString,
27 | Required: true,
28 | ForceNew: true,
29 | },
30 | "description": {
31 | Type: schema.TypeString,
32 | Optional: true,
33 | Default: nil,
34 | ForceNew: true,
35 | },
36 | },
37 | }
38 | }
39 |
40 | func resourceADComputerCreate(d *schema.ResourceData, meta interface{}) error {
41 | client := meta.(*ldap.Conn)
42 |
43 | computerName := d.Get("computer_name").(string)
44 | domain := d.Get("domain").(string)
45 | description := d.Get("description").(string)
46 | var dnOfComputer string
47 | dnOfComputer += "cn=" + computerName + ",cn=Computers"
48 | domainArr := strings.Split(domain, ".")
49 | for _, item := range domainArr {
50 | dnOfComputer += ",dc=" + item
51 | }
52 |
53 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
54 | log.Printf("[DEBUG] Adding the Computer to the AD : %s ", computerName)
55 |
56 | err := addComputerToAD(computerName, dnOfComputer, client, description)
57 | if err != nil {
58 | log.Printf("[ERROR] Error while adding a Computer to the AD : %s ", err)
59 | return fmt.Errorf("Error while adding a Computer to the AD %s", err)
60 | }
61 | log.Printf("[DEBUG] Computer Added to AD successfully: %s", computerName)
62 | d.SetId(domain + "/" + computerName)
63 | return nil
64 | }
65 |
66 | func resourceADComputerRead(d *schema.ResourceData, meta interface{}) error {
67 | client := meta.(*ldap.Conn)
68 |
69 | computerName := d.Get("computer_name").(string)
70 | domain := d.Get("domain").(string)
71 | var dnOfComputer string
72 | domainArr := strings.Split(domain, ".")
73 | dnOfComputer = "dc=" + domainArr[0]
74 | for index, item := range domainArr {
75 | if index == 0 {
76 | continue
77 | }
78 | dnOfComputer += ",dc=" + item
79 | }
80 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
81 | log.Printf("[DEBUG] Deleting the Computer from the AD : %s ", computerName)
82 |
83 | searchRequest := ldap.NewSearchRequest(
84 | dnOfComputer, // The base dn to search
85 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
86 | "(&(objectClass=Computer)(cn="+computerName+"))", // The filter to apply
87 | []string{"dn", "cn"}, // A list attributes to retrieve
88 | nil,
89 | )
90 |
91 | sr, err := client.Search(searchRequest)
92 | if err != nil {
93 | log.Printf("[ERROR] Error while searching a Computer : %s ", err)
94 | return fmt.Errorf("Error while searching a Computer : %s", err)
95 | }
96 | fmt.Println("[ERROR] Found " + strconv.Itoa(len(sr.Entries)) + " Entries")
97 | for _, entry := range sr.Entries {
98 | fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
99 | }
100 | if len(sr.Entries) == 0 {
101 | log.Println("[ERROR] Computer was not found")
102 | d.SetId("")
103 | }
104 | return nil
105 | }
106 |
107 | func resourceADComputerDelete(d *schema.ResourceData, meta interface{}) error {
108 | resourceADComputerRead(d, meta)
109 | if d.Id() == "" {
110 | log.Println("[ERROR] Cannot find Computer in the specified AD")
111 | return fmt.Errorf("[ERROR] Cannot find Computer in the specified AD")
112 | }
113 | client := meta.(*ldap.Conn)
114 |
115 | computerName := d.Get("computer_name").(string)
116 | domain := d.Get("domain").(string)
117 | var dnOfComputer string
118 | dnOfComputer += "cn=" + computerName + ",cn=Computers"
119 | domainArr := strings.Split(domain, ".")
120 | for _, item := range domainArr {
121 | dnOfComputer += ",dc=" + item
122 | }
123 |
124 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfComputer)
125 | log.Printf("[DEBUG] Deleting the Computer from the AD : %s ", computerName)
126 |
127 | err := deleteComputerFromAD(dnOfComputer, client)
128 | if err != nil {
129 | log.Printf("[ERROR] Error while Deleting a Computer from AD : %s ", err)
130 | return fmt.Errorf("Error while Deleting a Computer from AD %s", err)
131 | }
132 | log.Printf("[DEBUG] Computer deleted from AD successfully: %s", computerName)
133 | return nil
134 | }
135 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_add_to_group_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform/helper/resource"
10 | "github.com/hashicorp/terraform/terraform"
11 | ldap "gopkg.in/ldap.v3"
12 | )
13 |
14 | func TestAccAddToGroup_Basic(t *testing.T) {
15 | var groupDN string = os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME")
16 | resource.Test(t, resource.TestCase{
17 | PreCheck: func() {
18 | testAccPreCheck(t)
19 | testAccResourceAddToGroupPreCheck(t)
20 | },
21 | Providers: testAccProviders,
22 | CheckDestroy: testAccCheckAddToGroupDestroy("ad_add_to_group.test"),
23 | Steps: []resource.TestStep{
24 | resource.TestStep{
25 | Config: testAccResourceAddToGroupConfig(),
26 | Check: resource.ComposeTestCheckFunc(
27 | testAccCheckAddToGroupExists("ad_add_to_group.test"),
28 | resource.TestCheckResourceAttr(
29 | "ad_add_to_group.test", "id", "CN=terraform3,"+groupDN+"|CN=terraform2,"+groupDN),
30 | ),
31 | },
32 | },
33 | })
34 | }
35 |
36 | func testAccResourceAddToGroupPreCheck(t *testing.T) {
37 | if v := os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME"); v == "" {
38 | t.Fatal("AD_GROUP_OU_DISTINGUISHED_NAME must be set for acceptance tests")
39 | }
40 | }
41 |
42 | func testAccCheckAddToGroupDestroy(n string) resource.TestCheckFunc {
43 | return func(s *terraform.State) error {
44 | rs, ok := s.RootModule().Resources[n]
45 |
46 | if !ok {
47 | return fmt.Errorf("Not found: %s", n)
48 | }
49 |
50 | if rs.Primary.ID == "" {
51 | return fmt.Errorf("No Add To Group ID is set")
52 | }
53 |
54 | client := testAccProvider.Meta().(*ldap.Conn)
55 | targetGroup := rs.Primary.Attributes["target_group"]
56 | splitTargetGroup := strings.Split(targetGroup, ",") // split target group by commas
57 | searchRequest := ldap.NewSearchRequest(
58 | splitTargetGroup[len(splitTargetGroup)-2]+","+splitTargetGroup[len(splitTargetGroup)-1], // Make BaseDN from last two elements of split target group
59 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
60 | "(&(|(objectCategory=user)(objectCategory=group))(memberOf="+targetGroup+"))", // Find users and groups that are members of targetGroup
61 | []string{"dn"}, // A list attributes to retrieve
62 | nil,
63 | )
64 |
65 | searchResult, err := client.Search(searchRequest)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | if len(searchResult.Entries) == 0 {
71 | return nil
72 | }
73 |
74 | return fmt.Errorf("test group still has members")
75 | }
76 | }
77 |
78 | func testAccCheckAddToGroupExists(n string) resource.TestCheckFunc {
79 | return func(s *terraform.State) error {
80 | rs, ok := s.RootModule().Resources[n]
81 |
82 | if !ok {
83 | return fmt.Errorf("Not found: %s", n)
84 | }
85 |
86 | if rs.Primary.ID == "" {
87 | return fmt.Errorf("No Add To Group ID is set")
88 | }
89 |
90 | client := testAccProvider.Meta().(*ldap.Conn)
91 | targetGroup := rs.Primary.Attributes["target_group"]
92 | splitTargetGroup := strings.Split(targetGroup, ",") // split target group by commas
93 | searchRequest := ldap.NewSearchRequest(
94 | splitTargetGroup[len(splitTargetGroup)-2]+","+splitTargetGroup[len(splitTargetGroup)-1], // Make BaseDN from last two elements of split target group
95 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
96 | "(&(|(objectCategory=user)(objectCategory=group))(memberOf="+targetGroup+"))", // Find users and groups that are members of targetGroup
97 | []string{"dn"}, // A list attributes to retrieve
98 | nil,
99 | )
100 |
101 | searchResult, err := client.Search(searchRequest)
102 | if err != nil {
103 | return err
104 | }
105 |
106 | if len(searchResult.Entries) == 2 {
107 | return nil
108 | }
109 |
110 | return fmt.Errorf("expecting 2 members of test group")
111 | }
112 | }
113 |
114 | func testAccResourceAddToGroupConfig() string {
115 | return fmt.Sprintf(`
116 | provider "ad" {
117 | domain = "%s"
118 | ip = "%s"
119 | url = "%s"
120 | user = "%s"
121 | password = "%s"
122 | }
123 |
124 | resource "ad_group_to_ou" "test" {
125 | count = 3
126 | ou_distinguished_name = "%s"
127 | group_name = "terraform${count.index + 1}"
128 | description = "terraform test"
129 | }
130 |
131 | resource "ad_add_to_group" "test" {
132 | target_group = "CN=${ad_group_to_ou.test[0].group_name},${ad_group_to_ou.test[0].ou_distinguished_name}"
133 | dns_to_add = [
134 | "CN=${ad_group_to_ou.test[1].group_name},${ad_group_to_ou.test[1].ou_distinguished_name}",
135 | "CN=${ad_group_to_ou.test[2].group_name},${ad_group_to_ou.test[2].ou_distinguished_name}",
136 | ]
137 | }`,
138 | os.Getenv("AD_DOMAIN"),
139 | os.Getenv("AD_IP"),
140 | os.Getenv("AD_URL"),
141 | os.Getenv("AD_USER"),
142 | os.Getenv("AD_PASSWORD"),
143 | os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME"))
144 | }
145 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/passwdmodify.go:
--------------------------------------------------------------------------------
1 | // This file contains the password modify extended operation as specified in rfc 3062
2 | //
3 | // https://tools.ietf.org/html/rfc3062
4 | //
5 |
6 | package ldap
7 |
8 | import (
9 | "fmt"
10 |
11 | ber "github.com/go-asn1-ber/asn1-ber"
12 | )
13 |
14 | const (
15 | passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1"
16 | )
17 |
18 | // PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt
19 | type PasswordModifyRequest struct {
20 | // UserIdentity is an optional string representation of the user associated with the request.
21 | // This string may or may not be an LDAPDN [RFC2253].
22 | // If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session
23 | UserIdentity string
24 | // OldPassword, if present, contains the user's current password
25 | OldPassword string
26 | // NewPassword, if present, contains the desired password for this user
27 | NewPassword string
28 | }
29 |
30 | // PasswordModifyResult holds the server response to a PasswordModifyRequest
31 | type PasswordModifyResult struct {
32 | // GeneratedPassword holds a password generated by the server, if present
33 | GeneratedPassword string
34 | // Referral are the returned referral
35 | Referral string
36 | }
37 |
38 | func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error {
39 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation")
40 | pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID"))
41 |
42 | extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request")
43 | passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request")
44 | if req.UserIdentity != "" {
45 | passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.UserIdentity, "User Identity"))
46 | }
47 | if req.OldPassword != "" {
48 | passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, req.OldPassword, "Old Password"))
49 | }
50 | if req.NewPassword != "" {
51 | passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, req.NewPassword, "New Password"))
52 | }
53 | extendedRequestValue.AppendChild(passwordModifyRequestValue)
54 |
55 | pkt.AppendChild(extendedRequestValue)
56 |
57 | envelope.AppendChild(pkt)
58 |
59 | return nil
60 | }
61 |
62 | // NewPasswordModifyRequest creates a new PasswordModifyRequest
63 | //
64 | // According to the RFC 3602:
65 | // userIdentity is a string representing the user associated with the request.
66 | // This string may or may not be an LDAPDN (RFC 2253).
67 | // If userIdentity is empty then the operation will act on the user associated
68 | // with the session.
69 | //
70 | // oldPassword is the current user's password, it can be empty or it can be
71 | // needed depending on the session user access rights (usually an administrator
72 | // can change a user's password without knowing the current one) and the
73 | // password policy (see pwdSafeModify password policy's attribute)
74 | //
75 | // newPassword is the desired user's password. If empty the server can return
76 | // an error or generate a new password that will be available in the
77 | // PasswordModifyResult.GeneratedPassword
78 | //
79 | func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
80 | return &PasswordModifyRequest{
81 | UserIdentity: userIdentity,
82 | OldPassword: oldPassword,
83 | NewPassword: newPassword,
84 | }
85 | }
86 |
87 | // PasswordModify performs the modification request
88 | func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) {
89 | msgCtx, err := l.doRequest(passwordModifyRequest)
90 | if err != nil {
91 | return nil, err
92 | }
93 | defer l.finishMessage(msgCtx)
94 |
95 | packet, err := l.readPacket(msgCtx)
96 | if err != nil {
97 | return nil, err
98 | }
99 |
100 | result := &PasswordModifyResult{}
101 |
102 | if packet.Children[1].Tag == ApplicationExtendedResponse {
103 | err := GetLDAPError(packet)
104 | if err != nil {
105 | if IsErrorWithCode(err, LDAPResultReferral) {
106 | for _, child := range packet.Children[1].Children {
107 | if child.Tag == 3 {
108 | result.Referral = child.Children[0].Value.(string)
109 | }
110 | }
111 | }
112 | return result, err
113 | }
114 | } else {
115 | return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag))
116 | }
117 |
118 | extendedResponse := packet.Children[1]
119 | for _, child := range extendedResponse.Children {
120 | if child.Tag == 11 {
121 | passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
122 | if len(passwordModifyResponseValue.Children) == 1 {
123 | if passwordModifyResponseValue.Children[0].Tag == 0 {
124 | result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
125 | }
126 | }
127 | }
128 | }
129 |
130 | return result, nil
131 | }
132 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/modify.go:
--------------------------------------------------------------------------------
1 | // File contains Modify functionality
2 | //
3 | // https://tools.ietf.org/html/rfc4511
4 | //
5 | // ModifyRequest ::= [APPLICATION 6] SEQUENCE {
6 | // object LDAPDN,
7 | // changes SEQUENCE OF change SEQUENCE {
8 | // operation ENUMERATED {
9 | // add (0),
10 | // delete (1),
11 | // replace (2),
12 | // ... },
13 | // modification PartialAttribute } }
14 | //
15 | // PartialAttribute ::= SEQUENCE {
16 | // type AttributeDescription,
17 | // vals SET OF value AttributeValue }
18 | //
19 | // AttributeDescription ::= LDAPString
20 | // -- Constrained to
21 | // -- [RFC4512]
22 | //
23 | // AttributeValue ::= OCTET STRING
24 | //
25 |
26 | package ldap
27 |
28 | import (
29 | "log"
30 |
31 | ber "github.com/go-asn1-ber/asn1-ber"
32 | )
33 |
34 | // Change operation choices
35 | const (
36 | AddAttribute = 0
37 | DeleteAttribute = 1
38 | ReplaceAttribute = 2
39 | )
40 |
41 | // PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
42 | type PartialAttribute struct {
43 | // Type is the type of the partial attribute
44 | Type string
45 | // Vals are the values of the partial attribute
46 | Vals []string
47 | }
48 |
49 | func (p *PartialAttribute) encode() *ber.Packet {
50 | seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
51 | seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
52 | set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
53 | for _, value := range p.Vals {
54 | set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
55 | }
56 | seq.AppendChild(set)
57 | return seq
58 | }
59 |
60 | // Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
61 | type Change struct {
62 | // Operation is the type of change to be made
63 | Operation uint
64 | // Modification is the attribute to be modified
65 | Modification PartialAttribute
66 | }
67 |
68 | func (c *Change) encode() *ber.Packet {
69 | change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
70 | change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation"))
71 | change.AppendChild(c.Modification.encode())
72 | return change
73 | }
74 |
75 | // ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
76 | type ModifyRequest struct {
77 | // DN is the distinguishedName of the directory entry to modify
78 | DN string
79 | // Changes contain the attributes to modify
80 | Changes []Change
81 | // Controls hold optional controls to send with the request
82 | Controls []Control
83 | }
84 |
85 | // Add appends the given attribute to the list of changes to be made
86 | func (req *ModifyRequest) Add(attrType string, attrVals []string) {
87 | req.appendChange(AddAttribute, attrType, attrVals)
88 | }
89 |
90 | // Delete appends the given attribute to the list of changes to be made
91 | func (req *ModifyRequest) Delete(attrType string, attrVals []string) {
92 | req.appendChange(DeleteAttribute, attrType, attrVals)
93 | }
94 |
95 | // Replace appends the given attribute to the list of changes to be made
96 | func (req *ModifyRequest) Replace(attrType string, attrVals []string) {
97 | req.appendChange(ReplaceAttribute, attrType, attrVals)
98 | }
99 |
100 | func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
101 | req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
102 | }
103 |
104 | func (req *ModifyRequest) appendTo(envelope *ber.Packet) error {
105 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
106 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
107 | changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
108 | for _, change := range req.Changes {
109 | changes.AppendChild(change.encode())
110 | }
111 | pkt.AppendChild(changes)
112 |
113 | envelope.AppendChild(pkt)
114 | if len(req.Controls) > 0 {
115 | envelope.AppendChild(encodeControls(req.Controls))
116 | }
117 |
118 | return nil
119 | }
120 |
121 | // NewModifyRequest creates a modify request for the given DN
122 | func NewModifyRequest(dn string, controls []Control) *ModifyRequest {
123 | return &ModifyRequest{
124 | DN: dn,
125 | Controls: controls,
126 | }
127 | }
128 |
129 | // Modify performs the ModifyRequest
130 | func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
131 | msgCtx, err := l.doRequest(modifyRequest)
132 | if err != nil {
133 | return err
134 | }
135 | defer l.finishMessage(msgCtx)
136 |
137 | packet, err := l.readPacket(msgCtx)
138 | if err != nil {
139 | return err
140 | }
141 |
142 | if packet.Children[1].Tag == ApplicationModifyResponse {
143 | err := GetLDAPError(packet)
144 | if err != nil {
145 | return err
146 | }
147 | } else {
148 | log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
149 | }
150 | return nil
151 | }
152 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/bind.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | ber "github.com/go-asn1-ber/asn1-ber"
8 | )
9 |
10 | // SimpleBindRequest represents a username/password bind operation
11 | type SimpleBindRequest struct {
12 | // Username is the name of the Directory object that the client wishes to bind as
13 | Username string
14 | // Password is the credentials to bind with
15 | Password string
16 | // Controls are optional controls to send with the bind request
17 | Controls []Control
18 | // AllowEmptyPassword sets whether the client allows binding with an empty password
19 | // (normally used for unauthenticated bind).
20 | AllowEmptyPassword bool
21 | }
22 |
23 | // SimpleBindResult contains the response from the server
24 | type SimpleBindResult struct {
25 | Controls []Control
26 | }
27 |
28 | // NewSimpleBindRequest returns a bind request
29 | func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
30 | return &SimpleBindRequest{
31 | Username: username,
32 | Password: password,
33 | Controls: controls,
34 | AllowEmptyPassword: false,
35 | }
36 | }
37 |
38 | func (req *SimpleBindRequest) appendTo(envelope *ber.Packet) error {
39 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
40 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
41 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Username, "User Name"))
42 | pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.Password, "Password"))
43 |
44 | envelope.AppendChild(pkt)
45 | if len(req.Controls) > 0 {
46 | envelope.AppendChild(encodeControls(req.Controls))
47 | }
48 |
49 | return nil
50 | }
51 |
52 | // SimpleBind performs the simple bind operation defined in the given request
53 | func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
54 | if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword {
55 | return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
56 | }
57 |
58 | msgCtx, err := l.doRequest(simpleBindRequest)
59 | if err != nil {
60 | return nil, err
61 | }
62 | defer l.finishMessage(msgCtx)
63 |
64 | packet, err := l.readPacket(msgCtx)
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | result := &SimpleBindResult{
70 | Controls: make([]Control, 0),
71 | }
72 |
73 | if len(packet.Children) == 3 {
74 | for _, child := range packet.Children[2].Children {
75 | decodedChild, decodeErr := DecodeControl(child)
76 | if decodeErr != nil {
77 | return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
78 | }
79 | result.Controls = append(result.Controls, decodedChild)
80 | }
81 | }
82 |
83 | err = GetLDAPError(packet)
84 | return result, err
85 | }
86 |
87 | // Bind performs a bind with the given username and password.
88 | //
89 | // It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
90 | // for that.
91 | func (l *Conn) Bind(username, password string) error {
92 | req := &SimpleBindRequest{
93 | Username: username,
94 | Password: password,
95 | AllowEmptyPassword: false,
96 | }
97 | _, err := l.SimpleBind(req)
98 | return err
99 | }
100 |
101 | // UnauthenticatedBind performs an unauthenticated bind.
102 | //
103 | // A username may be provided for trace (e.g. logging) purpose only, but it is normally not
104 | // authenticated or otherwise validated by the LDAP server.
105 | //
106 | // See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
107 | // See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
108 | func (l *Conn) UnauthenticatedBind(username string) error {
109 | req := &SimpleBindRequest{
110 | Username: username,
111 | Password: "",
112 | AllowEmptyPassword: true,
113 | }
114 | _, err := l.SimpleBind(req)
115 | return err
116 | }
117 |
118 | var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
119 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
120 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
121 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
122 |
123 | saslAuth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
124 | saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "EXTERNAL", "SASL Mech"))
125 | saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "SASL Cred"))
126 |
127 | pkt.AppendChild(saslAuth)
128 |
129 | envelope.AppendChild(pkt)
130 |
131 | return nil
132 | })
133 |
134 | // ExternalBind performs SASL/EXTERNAL authentication.
135 | //
136 | // Use ldap.DialURL("ldapi://") to connect to the Unix socket before ExternalBind.
137 | //
138 | // See https://tools.ietf.org/html/rfc4422#appendix-A
139 | func (l *Conn) ExternalBind() error {
140 | msgCtx, err := l.doRequest(externalBindRequest)
141 | if err != nil {
142 | return err
143 | }
144 | defer l.finishMessage(msgCtx)
145 |
146 | packet, err := l.readPacket(msgCtx)
147 | if err != nil {
148 | return err
149 | }
150 |
151 | return GetLDAPError(packet)
152 | }
153 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_group_to_ou_test.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 |
8 | "github.com/hashicorp/terraform/helper/resource"
9 | "github.com/hashicorp/terraform/terraform"
10 | "gopkg.in/ldap.v3"
11 | )
12 |
13 | func TestAccAdGroupToOU_Basic(t *testing.T) {
14 | resource.Test(t, resource.TestCase{
15 | PreCheck: func() {
16 | testAccPreCheck(t)
17 | testAccResourceAdGroupToOUPreCheck(t)
18 | },
19 | Providers: testAccProviders,
20 | CheckDestroy: testAccCheckAdGroupToOUDestroy("ad_group_to_ou.test"),
21 | Steps: []resource.TestStep{
22 | resource.TestStep{
23 | Config: testAccResourceAdGroupToOUConfig(),
24 | Check: resource.ComposeTestCheckFunc(
25 | testAccCheckAdGroupToOUExists("ad_group_to_ou.test"),
26 | resource.TestCheckResourceAttr(
27 | "ad_group_to_ou.test", "group_name", "terraform"),
28 | resource.TestCheckResourceAttr(
29 | "ad_group_to_ou.test", "gid_number", "9001"),
30 | ),
31 | },
32 | },
33 | })
34 |
35 | resource.Test(t, resource.TestCase{
36 | PreCheck: func() {
37 | testAccPreCheck(t)
38 | testAccResourceAdGroupToOUPreCheck(t)
39 | },
40 | Providers: testAccProviders,
41 | CheckDestroy: testAccCheckAdGroupToOUDestroy("ad_group_to_ou.test"),
42 | Steps: []resource.TestStep{
43 | resource.TestStep{
44 | Config: testAccResourceAdGroupToOUConfig_with_auto_gid(),
45 | Check: resource.ComposeTestCheckFunc(
46 | // make sure we get gidNumber 9001 when 9000 is used already.
47 | resource.TestCheckResourceAttr(
48 | "ad_group_to_ou.test", "auto_gid_number", "9001"),
49 | ),
50 | },
51 | },
52 | })
53 | }
54 |
55 | func testAccResourceAdGroupToOUPreCheck(t *testing.T) {
56 | if v := os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME"); v == "" {
57 | t.Fatal("AD_GROUP_OU_DISTINGUISHED_NAME must be set for acceptance tests")
58 | }
59 | }
60 |
61 | func testAccCheckAdGroupToOUDestroy(n string) resource.TestCheckFunc {
62 | return func(s *terraform.State) error {
63 | rs, ok := s.RootModule().Resources[n]
64 |
65 | if !ok {
66 | return fmt.Errorf("Not found: %s", n)
67 | }
68 |
69 | if rs.Primary.ID == "" {
70 | return fmt.Errorf("No AD Group ID is set")
71 | }
72 | client := testAccProvider.Meta().(*ldap.Conn)
73 | ouDistinguishedName := rs.Primary.Attributes["ou_distinguished_name"]
74 | var dnOfGroup string
75 | dnOfGroup = ouDistinguishedName
76 | searchRequest := ldap.NewSearchRequest(
77 | dnOfGroup, //"cn=code1,ou=DevGroups,dc=terraform,dc=local", // The base dn to search
78 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
79 | "(&(objectClass=Group)(cn="+rs.Primary.Attributes["group_name"]+"))", // The filter to apply
80 | []string{"dn"}, // A list attributes to retrieve
81 | nil,
82 | )
83 | sr, err := client.Search(searchRequest)
84 | if err != nil {
85 | return err
86 | }
87 | if len(sr.Entries) == 0 {
88 | return nil
89 | }
90 |
91 | return fmt.Errorf("Group AD still exists")
92 | }
93 |
94 | }
95 |
96 | func testAccCheckAdGroupToOUExists(n string) resource.TestCheckFunc {
97 | return func(s *terraform.State) error {
98 | rs, ok := s.RootModule().Resources[n]
99 |
100 | if !ok {
101 | return fmt.Errorf("Not found: %s", n)
102 | }
103 |
104 | if rs.Primary.ID == "" {
105 | return fmt.Errorf("No AD Group ID is set")
106 | }
107 | client := testAccProvider.Meta().(*ldap.Conn)
108 | ouDistinguishedName := rs.Primary.Attributes["ou_distinguished_name"]
109 | var dnOfGroup string
110 | dnOfGroup = ouDistinguishedName
111 | searchRequest := ldap.NewSearchRequest(
112 | dnOfGroup, //"cn=code1,ou=DevGroups,dc=terraform,dc=local", // The base dn to search
113 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
114 | // filter for the test group by cn and gidNumber
115 | "(&(objectClass=Group)(cn="+rs.Primary.Attributes["group_name"]+")(gidNumber="+rs.Primary.Attributes["gid_number"]+"))", // The filter to apply
116 | []string{"dn"}, // A list attributes to retrieve
117 | nil,
118 | )
119 | sr, err := client.Search(searchRequest)
120 | if err != nil {
121 | return err
122 | }
123 | if len(sr.Entries) > 0 {
124 | return nil
125 | }
126 | return nil
127 | }
128 | }
129 |
130 | func testAccResourceAdGroupToOUConfig() string {
131 | return fmt.Sprintf(`
132 | provider "ad" {
133 | domain = "%s"
134 | ip = "%s"
135 | url = "%s"
136 | user = "%s"
137 | password = "%s"
138 | }
139 |
140 | resource "ad_group_to_ou" "test" {
141 | ou_distinguished_name = "%s"
142 | group_name = "terraform"
143 | description = "terraform test"
144 | gid_number = "9001"
145 | }`,
146 | os.Getenv("AD_DOMAIN"),
147 | os.Getenv("AD_IP"),
148 | os.Getenv("AD_URL"),
149 | os.Getenv("AD_USER"),
150 | os.Getenv("AD_PASSWORD"),
151 | os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME"))
152 | }
153 |
154 | func testAccResourceAdGroupToOUConfig_with_auto_gid() string {
155 | return fmt.Sprintf(`
156 | provider "ad" {
157 | domain = "%s"
158 | ip = "%s"
159 | url = "%s"
160 | user = "%s"
161 | password = "%s"
162 | }
163 |
164 | resource "ad_group_to_ou" "static_gid" {
165 | ou_distinguished_name = "%s"
166 | group_name = "terraform9000"
167 | description = "terraform test"
168 | gid_number = "9000"
169 | }
170 |
171 | resource "ad_group_to_ou" "test" {
172 | ou_distinguished_name = "%[5]s"
173 | group_name = "terraform9001"
174 | description = "terraform test"
175 | auto_gid = true
176 | auto_gid_min = 9000
177 | auto_gid_max = 9001
178 | }`,
179 | os.Getenv("AD_DOMAIN"),
180 | os.Getenv("AD_IP"),
181 | os.Getenv("AD_URL"),
182 | os.Getenv("AD_USER"),
183 | os.Getenv("AD_PASSWORD"),
184 | os.Getenv("AD_GROUP_OU_DISTINGUISHED_NAME"))
185 | }
186 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_add_to_group.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strings"
7 |
8 | "github.com/hashicorp/terraform/helper/schema"
9 | ldap "gopkg.in/ldap.v3"
10 | )
11 |
12 | func resourceAddToGroup() *schema.Resource {
13 | return &schema.Resource{
14 | Create: resourceAddToGroupCreate,
15 | Read: resourceAddToGroupRead,
16 | Delete: resourceAddToGroupDelete,
17 | Schema: map[string]*schema.Schema{
18 | "dns_to_add": &schema.Schema{
19 | Type: schema.TypeSet,
20 | Description: "A list of distinguished names to add to target_group.",
21 | Elem: &schema.Schema{Type: schema.TypeString},
22 | Set: schema.HashString,
23 | Required: true,
24 | ForceNew: true,
25 | },
26 | "target_group": &schema.Schema{
27 | Type: schema.TypeString,
28 | Description: "The distinguished name of the target group you're adding members to.",
29 | Required: true,
30 | ForceNew: true,
31 | },
32 | },
33 | }
34 | }
35 |
36 | func resourceAddToGroupCreate(d *schema.ResourceData, meta interface{}) error {
37 | client := meta.(*ldap.Conn)
38 |
39 | var resourceId string
40 |
41 | targetGroup := d.Get("target_group").(string)
42 | // get each DN out of the Set and interate to do the work
43 | for _, distinguishedName := range d.Get("dns_to_add").(*schema.Set).List() {
44 | distinguishedNameToAdd := fmt.Sprintf("%s", distinguishedName)
45 | log.Printf("[DEBUG] Adding %s to %s", distinguishedNameToAdd, targetGroup)
46 | // call the helper to do the work
47 | err := addToGroup(distinguishedNameToAdd, targetGroup, client)
48 | if err != nil {
49 | log.Printf("[ERROR] Error while adding %s to %s : %s", distinguishedNameToAdd, targetGroup, err)
50 | return fmt.Errorf("Error while adding %s to %s : %s", distinguishedNameToAdd, targetGroup, err)
51 | }
52 | resourceId = resourceId + "|" + distinguishedNameToAdd
53 | log.Printf("[DEBUG] Successfully added %s to %s.", distinguishedNameToAdd, targetGroup)
54 | }
55 | resourceId = strings.TrimPrefix(resourceId, "|")
56 | d.SetId(resourceId)
57 |
58 | return nil
59 | }
60 |
61 | func resourceAddToGroupRead(d *schema.ResourceData, meta interface{}) error {
62 | client := meta.(*ldap.Conn)
63 | targetGroup := strings.ToLower(d.Get("target_group").(string))
64 | log.Printf("[DEBUG] Searching for members of %s", targetGroup)
65 | splitTargetGroup := strings.SplitN(targetGroup, "dc", 2) // split target group
66 | searchRequest := ldap.NewSearchRequest(
67 | "dc"+splitTargetGroup[1], // Make BaseDN from dc elements of split target group
68 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
69 | "(&(|(objectCategory=user)(objectCategory=group))(memberOf="+targetGroup+"))", // Find users and groups that are members of targetGroup
70 | []string{"dn"}, // A list attributes to retrieve
71 | nil,
72 | )
73 |
74 | searchResult, err := client.Search(searchRequest)
75 | if err != nil {
76 | log.Printf("[ERROR] Error while searching group : %s ", err)
77 | return fmt.Errorf("Error while searching group : %s", err)
78 | }
79 | if len(searchResult.Entries) == 0 {
80 | log.Println("[ERROR] Target Group was not found, or is empty.")
81 | d.SetId("")
82 | return nil
83 | }
84 |
85 | var readDns []string
86 | for _, distinguishedName := range d.Get("dns_to_add").(*schema.Set).List() {
87 | distinguishedNameToRead := fmt.Sprintf("%s", distinguishedName)
88 | log.Printf("[DEBUG] Checking if %s is a member.", distinguishedNameToRead)
89 | for _, entry := range searchResult.Entries {
90 | if strings.EqualFold(entry.DN, distinguishedNameToRead) {
91 | log.Printf("[DEBUG] found %s", distinguishedNameToRead)
92 | readDns = append(readDns, distinguishedNameToRead)
93 | }
94 | }
95 | }
96 |
97 | d.Set("dns_to_add", readDns)
98 | return nil
99 | }
100 |
101 | func resourceAddToGroupDelete(d *schema.ResourceData, meta interface{}) error {
102 | client := meta.(*ldap.Conn)
103 | targetGroup := strings.ToLower(d.Get("target_group").(string))
104 | log.Printf("[DEBUG] Searching for members of %s", targetGroup)
105 | splitTargetGroup := strings.SplitN(targetGroup, "dc", 2) // split target group
106 | searchRequest := ldap.NewSearchRequest(
107 | "dc"+splitTargetGroup[1], // Make BaseDN from dc elements of split target group
108 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
109 | "(&(|(objectCategory=user)(objectCategory=group))(memberOf="+targetGroup+"))", // Find users and groups that are members of targetGroup
110 | []string{"dn"}, // A list attributes to retrieve
111 | nil,
112 | )
113 |
114 | searchResult, err := client.Search(searchRequest)
115 | if err != nil {
116 | log.Printf("[ERROR] Error while searching group : %s ", err)
117 | return fmt.Errorf("Error while searching group : %s", err)
118 | }
119 | if len(searchResult.Entries) == 0 {
120 | log.Println("[ERROR] Target Group was not found, or is empty.")
121 | d.SetId("")
122 | return nil
123 | }
124 |
125 | dnsToSplit := d.Id()
126 | dnsToRemove := strings.Split(dnsToSplit, "|")
127 |
128 | for _, distinguishedNameToRemove := range dnsToRemove {
129 | log.Printf("Checking for %s in target group.", distinguishedNameToRemove)
130 | for _, entry := range searchResult.Entries {
131 | if strings.EqualFold(entry.DN, distinguishedNameToRemove) {
132 | log.Printf("[DEBUG] Removing %s from %s", distinguishedNameToRemove, targetGroup)
133 | // call the helper to do the work
134 | err := removeFromGroup(distinguishedNameToRemove, targetGroup, client)
135 | if err != nil {
136 | log.Printf("[ERROR] Error while removing %s from %s : %s", distinguishedNameToRemove, targetGroup, err)
137 | return fmt.Errorf("Error while removing %s from %s : %s", distinguishedNameToRemove, targetGroup, err)
138 | }
139 | }
140 | }
141 | }
142 |
143 | return nil
144 | }
145 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_user.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/hashicorp/terraform/helper/schema"
10 | "gopkg.in/ldap.v3"
11 | )
12 |
13 | func resourceUser() *schema.Resource {
14 | return &schema.Resource{
15 | Create: resourceADUserCreate,
16 | Read: resourceADUserRead,
17 | Delete: resourceADUserDelete,
18 |
19 | Schema: map[string]*schema.Schema{
20 | "first_name": {
21 | Type: schema.TypeString,
22 | Required: true,
23 | ForceNew: true,
24 | },
25 | "last_name": {
26 | Type: schema.TypeString,
27 | Required: true,
28 | ForceNew: true,
29 | },
30 | "domain": {
31 | Type: schema.TypeString,
32 | Required: true,
33 | ForceNew: true,
34 | },
35 | "email": {
36 | Type: schema.TypeString,
37 | Required: true,
38 | ForceNew: true,
39 | },
40 | "logon_name": {
41 | Type: schema.TypeString,
42 | Required: true,
43 | ForceNew: true,
44 | },
45 | "password": {
46 | Type: schema.TypeString,
47 | Required: true,
48 | ForceNew: true,
49 | },
50 | },
51 | }
52 | }
53 |
54 | // function to add a user in AD:
55 |
56 | func resourceADUserCreate(d *schema.ResourceData, m interface{}) error {
57 | client := m.(*ldap.Conn)
58 | firstName := d.Get("first_name").(string)
59 | lastName := d.Get("last_name").(string)
60 | pass := d.Get("password").(string)
61 | domain := d.Get("domain").(string)
62 | logonName := d.Get("logon_name").(string)
63 | upn := logonName + "@" + domain
64 | userName := logonName
65 | mail := d.Get("email").(string)
66 | var dnOfUser string // dnOfUser: distingished names uniquely identifies an entry to AD.
67 | dnOfUser += "CN=" + userName + ",CN=Users"
68 | domainArr := strings.Split(domain, ".")
69 | for _, i := range domainArr {
70 | dnOfUser += ",DC=" + i
71 | }
72 |
73 | log.Printf("[DEBUG] dnOfUser: %s ", dnOfUser)
74 | log.Printf("[DEBUG] Adding user : %s ", userName)
75 | err := addUser(userName, mail, dnOfUser, client, upn, firstName, lastName, pass)
76 | if err != nil {
77 | log.Printf("[ERROR] Error while adding user: %s ", err)
78 | return fmt.Errorf("Error while adding user %s", err)
79 | }
80 | log.Printf("[DEBUG] User Added success: %s", userName)
81 | d.SetId(domain + "/" + userName)
82 | return nil
83 | }
84 |
85 | // Function to read user in AD:
86 |
87 | func resourceADUserRead(d *schema.ResourceData, m interface{}) error {
88 | client := m.(*ldap.Conn)
89 | domain := d.Get("domain").(string)
90 | userName := d.Get("logon_name").(string)
91 | var dnOfUser string // dnOfUser: distingished names uniquely identifies an entry to AD.
92 | domainArr := strings.Split(domain, ".")
93 | dnOfUser = "dc=" + domainArr[0]
94 | for index, i := range domainArr {
95 | if index == 0 {
96 | continue
97 | }
98 | dnOfUser += ",dc=" + i
99 | }
100 | log.Printf("[DEBUG] dnOfUser: %s ", dnOfUser)
101 | log.Printf("[DEBUG] Deleting user : %s ", userName)
102 |
103 | NewReq := ldap.NewSearchRequest(
104 | dnOfUser, // base dnOfUser.
105 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0,
106 | false,
107 | "(&(objectClass=User)(cn="+userName+"))", //applied filter
108 | []string{"dnOfUser", "cn"},
109 | nil,
110 | )
111 |
112 | sr, err := client.Search(NewReq)
113 | if err != nil {
114 | log.Printf("[ERROR] while seaching user : %s", err)
115 | return fmt.Errorf("Error while searching user : %s", err)
116 | }
117 |
118 | fmt.Println("[ERROR] Found " + strconv.Itoa(len(sr.Entries)) + " Entries")
119 | for _, entry := range sr.Entries {
120 | fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
121 |
122 | }
123 |
124 | if len(sr.Entries) == 0 {
125 | log.Println("[ERROR] user not found")
126 | d.SetId("")
127 | }
128 | return nil
129 | }
130 |
131 | //function to delete user from AD :
132 |
133 | func resourceADUserDelete(d *schema.ResourceData, m interface{}) error {
134 | resourceADUserRead(d, m)
135 | if d.Id() == "" {
136 | log.Printf("[ERROR] user not found !!")
137 | return fmt.Errorf("[ERROR] Cannot find user")
138 | }
139 | client := m.(*ldap.Conn)
140 | domain := d.Get("domain").(string)
141 | userName := d.Get("logon_name").(string)
142 | var dnOfUser string
143 | dnOfUser += "CN=" + userName + ",CN=Users"
144 | domainArr := strings.Split(domain, ".")
145 | for _, i := range domainArr {
146 | dnOfUser += ",DC=" + i
147 | }
148 | log.Printf("[DEBUG] dnOfUser: %s ", dnOfUser)
149 | log.Printf("[DEBUG] deleting user : %s ", userName)
150 | err := delUser(userName, dnOfUser, client)
151 | if err != nil {
152 | log.Printf("[ERROR] Error in deletion :%s", err)
153 | return fmt.Errorf("[ERROR] Error in deletion :%s", err)
154 | }
155 | log.Printf("[DEBUG] user Deleted success: %s", userName)
156 | return nil
157 | }
158 |
159 | // Helper function for adding user:
160 | func addUser(userName string, mail string, dnName string, adConn *ldap.Conn, upn string, firstName string, lastName string, pass string) error {
161 | a := ldap.NewAddRequest(dnName, nil) // returns a new AddRequest without attributes " with dn".
162 | a.Attribute("objectClass", []string{"organizationalPerson", "person", "top", "user"})
163 | a.Attribute("sAMAccountName", []string{userName})
164 | a.Attribute("userPrincipalName", []string{upn})
165 | a.Attribute("name", []string{userName})
166 | a.Attribute("mail", []string{mail})
167 | a.Attribute("givenName", []string{firstName})
168 | a.Attribute("sn", []string{lastName})
169 | a.Attribute("userPassword", []string{pass})
170 |
171 | err := adConn.Add(a)
172 | if err != nil {
173 | return err
174 | }
175 | return nil
176 | }
177 |
178 | //Helper function to delete user:
179 |
180 | func delUser(userName string, dnName string, adConn *ldap.Conn) error {
181 | delReq := ldap.NewDelRequest(dnName, nil)
182 | err := adConn.Del(delReq)
183 | if err != nil {
184 | return err
185 | }
186 | return nil
187 | }
188 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform Active Directory Provider
2 |
3 | This is the repository for the Terraform Active Directory Provider, which one can use
4 | with Terraform to work with Active Directory.
5 |
6 | [1]: https://www.vmware.com/products/vcenter-server.html
7 | [2]: https://www.vmware.com/products/esxi-and-esx.html
8 |
9 | Coverage is currently only limited to a one resource only computer, but in the coming months we are planning release coverage for most essential Active Directory workflows.
10 | Watch this space!
11 |
12 | For general information about Terraform, visit the [official website][3] and the
13 | [GitHub project page][4].
14 |
15 | [3]: https://terraform.io/
16 | [4]: https://github.com/hashicorp/terraform
17 |
18 | # Using the Provider
19 |
20 | The current version of this provider requires Terraform v0.10.2 or higher to
21 | run.
22 |
23 | Note that you need to run `terraform init` to fetch the provider before
24 | deploying. Read about the provider split and other changes to TF v0.10.0 in the
25 | official release announcement found [here][4].
26 |
27 | [4]: https://www.hashicorp.com/blog/hashicorp-terraform-0-10/
28 |
29 | ## Full Provider Documentation
30 |
31 | The provider is useful in adding computers to Active Directory.
32 | ### Example
33 | ```hcl
34 | # Configure the Active Directory Provider
35 | provider "ad" {
36 | domain = var.ad_server_domain
37 | user = var.ad_server_user
38 | password = var.ad_server_password
39 | ip = var.ad_server_ip
40 | }
41 |
42 | # Add computer to Active Directory
43 | resource "ad_computer" "foo" {
44 | domain = var.ad_domain
45 | computer_name = "terraformSample"
46 | description = "terraform sample server"
47 | }
48 |
49 | # Add computer to Organizational Unit of Active Directory
50 | resource "ad_computer_to_ou" "bar" {
51 | ou_distinguished_name = var.ad_ou_dn
52 | computer_name = "terraformOuSample"
53 | description = "terraform sample server to OU"
54 | }
55 |
56 | # Add group to Organizational Unit of Active Directory
57 | resource "ad_group_to_ou" "baz" {
58 | ou_distinguished_name = var.ad_ou_dn
59 | group_name = "terraformGroupSample"
60 | description = "terraform sample group to OU"
61 | }
62 |
63 | # Add User to Active Directory
64 | resource "ad_user" "foo1"{
65 | domain ="domain"
66 | first_name = "firstname"
67 | last_name = "lastname"
68 | logon_name = "logonname"
69 | email = "logonname@domain"
70 | password = "password"
71 | }
72 |
73 | # Add Organizational Unit to Active Directory
74 | resource "ad_organizational_unit" "test" {
75 | ou_name = "eample-ou"
76 | domain = "example.com"
77 | }
78 | ```
79 |
80 | # Building The Provider
81 |
82 | **NOTE:** Unless you are [developing][7] or require a pre-release bugfix or feature,
83 | you will want to use the officially released version of the provider (see [the
84 | section above][8]).
85 |
86 | [7]: #developing-the-provider
87 | [8]: #using-the-provider
88 |
89 |
90 | ## Cloning the Project
91 |
92 | First, you will want to clone the repository to
93 | `$GOPATH/src/github.com/terraform-providers/terraform-provider-ad`:
94 |
95 | ```sh
96 | mkdir -p $GOPATH/src/github.com/terraform-providers
97 | cd $GOPATH/src/github.com/terraform-providers
98 | git clone git@github.com:terraform-providers/terraform-provider-ad
99 | ```
100 |
101 | ## Running the Build
102 |
103 | After the clone has been completed, you can enter the provider directory and
104 | build the provider.
105 |
106 | ```sh
107 | cd $GOPATH/src/github.com/terraform-providers/terraform-provider-ad
108 | make build
109 | ```
110 |
111 | ## Installing the Local Plugin
112 |
113 | After the build is complete, copy the `terraform-provider-ad` binary into
114 | the same path as your `terraform` binary, and re-run `terraform init`.
115 |
116 | After this, your project-local `.terraform/plugins/ARCH/lock.json` (where `ARCH`
117 | matches the architecture of your machine) file should contain a SHA256 sum that
118 | matches the local plugin. Run `shasum -a 256` on the binary to verify the values
119 | match.
120 |
121 | # Developing the Provider
122 |
123 | If you wish to work on the provider, you'll first need [Go][9] installed on your
124 | machine (version 1.9+ is **required**). You'll also need to correctly setup a
125 | [GOPATH][10], as well as adding `$GOPATH/bin` to your `$PATH`.
126 |
127 | [9]: https://golang.org/
128 | [10]: http://golang.org/doc/code.html#GOPATH
129 |
130 | See [Building the Provider][11] for details on building the provider.
131 |
132 | [11]: #building-the-provider
133 |
134 | # Testing the Provider
135 |
136 | **NOTE:** Testing the Active Directory provider is currently a complex operation as it
137 | requires having a Active Directory Server to test against.
138 |
139 | ## Configuring Environment Variables
140 |
141 | Most of the tests in this provider require a comprehensive list of environment
142 | variables to run. See the individual `*_test.go` files in the
143 | [`ad/`](ad/) directory for more details. The next section also
144 | describes how you can manage a configuration file of the test environment
145 | variables.
146 |
147 | ### Using the `.tf-ad-devrc.mk` file
148 |
149 | The [`tf-ad-devrc.mk.example`](tf-ad-devrc.mk.example) file contains
150 | an up-to-date list of environment variables required to run the acceptance
151 | tests. Copy this to `$HOME/.tf-ad-devrc.mk` and change the permissions to
152 | something more secure (ie: `chmod 600 $HOME/.tf-ad-devrc.mk`), and
153 | configure the variables accordingly.
154 |
155 | ## Running the Acceptance Tests
156 |
157 | After this is done, you can run the acceptance tests by running:
158 |
159 | ```sh
160 | $ make testacc
161 | ```
162 |
163 | If you want to run against a specific set of tests, run `make testacc` with the
164 | `TESTARGS` parameter containing the run mask as per below:
165 |
166 | ```sh
167 | make testacc TESTARGS="-run=TestAccAdComputer_Basic"
168 | ```
169 | OR
170 | ```sh
171 | make testacc TESTARGS="-run=TestAccAdComputerToOU_Basic"
172 | ```
173 |
174 | This following example would run all of the acceptance tests matching
175 | `TestAccAdComputer_Basic` OR `TestAccAdComputerToOU_Basic`. Change this for the
176 | specific tests you want to run.
177 |
--------------------------------------------------------------------------------
/ad/resource_active_directory_group_to_ou.go:
--------------------------------------------------------------------------------
1 | package ad
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "math/rand"
7 | "strconv"
8 | "time"
9 |
10 | ldap "gopkg.in/ldap.v3"
11 |
12 | "github.com/hashicorp/terraform/helper/schema"
13 | )
14 |
15 | func resourceGroupToOU() *schema.Resource {
16 | return &schema.Resource{
17 | Create: resourceADGroupToOUCreate,
18 | Read: resourceADGroupToOURead,
19 | Delete: resourceADGroupToOUDelete,
20 | Schema: map[string]*schema.Schema{
21 | "group_name": {
22 | Type: schema.TypeString,
23 | Required: true,
24 | ForceNew: true,
25 | },
26 | "ou_distinguished_name": {
27 | Type: schema.TypeString,
28 | Required: true,
29 | ForceNew: true,
30 | },
31 | "description": {
32 | Type: schema.TypeString,
33 | Optional: true,
34 | Default: nil,
35 | ForceNew: true,
36 | },
37 | "gid_number": {
38 | Type: schema.TypeString,
39 | Description: "Statically sets the 'gidNumber' attribute on the resultant group.",
40 | Optional: true,
41 | Default: nil,
42 | ForceNew: true,
43 | },
44 | "auto_gid": {
45 | Type: schema.TypeBool,
46 | Description: "Automatically set the 'gidNumber' attribute on the resultant group, and ensure that it's unique.",
47 | Optional: true,
48 | Default: false,
49 | ForceNew: true,
50 | },
51 | "auto_gid_min": {
52 | Type: schema.TypeInt,
53 | Description: "The lower bounds of automatically assignable gid numbers.",
54 | Optional: true,
55 | Default: nil,
56 | ForceNew: true,
57 | },
58 | "auto_gid_max": {
59 | Type: schema.TypeInt,
60 | Description: "The upper bounds of automatically assignable gid numbers.",
61 | Optional: true,
62 | Default: nil,
63 | ForceNew: true,
64 | },
65 | "auto_gid_number": {
66 | Type: schema.TypeInt,
67 | Description: "The resultant gid number that was automatically set.",
68 | Computed: true,
69 | },
70 | },
71 | }
72 | }
73 |
74 | func resourceADGroupToOUCreate(d *schema.ResourceData, meta interface{}) error {
75 | client := meta.(*ldap.Conn)
76 |
77 | groupName := d.Get("group_name").(string)
78 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
79 | description := d.Get("description").(string)
80 | gidNumber := d.Get("gid_number").(string)
81 | auto_gid := d.Get("auto_gid").(bool)
82 | auto_gid_min := d.Get("auto_gid_min").(int)
83 | auto_gid_max := d.Get("auto_gid_max").(int)
84 |
85 | var dnOfGroup string
86 | dnOfGroup += "cn=" + groupName + "," + OUDistinguishedName
87 |
88 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfGroup)
89 | log.Printf("[DEBUG] Adding the Group to the AD : %s ", groupName)
90 |
91 | err := addGroupToAD(groupName, dnOfGroup, client, description, gidNumber)
92 | if err != nil {
93 | log.Printf("[ERROR] Error while adding a Group to the AD : %s ", err)
94 | return fmt.Errorf("Error while adding a Group to the AD %s", err)
95 | }
96 |
97 | // if gid_max is enabled and no gidNumber has been dictated
98 | if auto_gid == true && gidNumber == "" {
99 | // some sane defaults if no gid_min or gid_max have been chosen
100 | if auto_gid_min <= 0 {
101 | // probably don't want this either, but it's better than 0 which is reserved for root
102 | auto_gid_min = 1
103 | }
104 | if auto_gid_max <= 0 {
105 | // a "safe" gid max per resolution note 3:
106 | // https://access.redhat.com/solutions/25404
107 | auto_gid_max = 2097151
108 | }
109 | // check for smaller max than min
110 | if auto_gid_max < auto_gid_min {
111 | return fmt.Errorf("[ERROR] auto_gid_max must be greater than or equal to auto_gid_min.")
112 | }
113 | // random wait to help with gidNumber race
114 | rand.Seed(time.Now().UnixNano())
115 | n := rand.Intn(30) // maybe this wait should be variablized and pulled from an attrbiute
116 | time.Sleep(time.Duration(n) * time.Second)
117 |
118 | var duplicate_check bool = true
119 | for duplicate_check {
120 | // find next available gidNumber
121 | err, next_available_gid := find_next_gidNumber(dnOfGroup, client, auto_gid_min, auto_gid_max)
122 | if err != nil {
123 | log.Fatal(err)
124 | return fmt.Errorf("[ERROR] Error while searching for next available gidNumber. %s", err)
125 | }
126 | log.Printf("[DEBUG] Received %d as next available.", next_available_gid)
127 | // try updating the group with it
128 | err = update_gidNumber(dnOfGroup, client, next_available_gid)
129 | if err != nil {
130 | log.Fatal(err)
131 | return fmt.Errorf("[ERROR] Error while updating gidNumber of group. %s", err)
132 | }
133 | d.Set("auto_gid_number", next_available_gid)
134 | // wait a moment for things to stick
135 | time.Sleep(2 * time.Second) // maybe this wait should be variablized and pulled from an attrbiute
136 | // check for duplicates will return false and break the loop if no dups found
137 | err, duplicate_check = find_duplicate_gidNumber(dnOfGroup, client, next_available_gid, auto_gid_min, auto_gid_max)
138 | if err != nil {
139 | log.Fatal(err)
140 | return fmt.Errorf("[ERROR] Error while checking for duplicate gidNumbers %s", err)
141 | }
142 | // if we got a duplicate, wait, and to the top try again
143 | if duplicate_check {
144 | rand.Seed(time.Now().UnixNano())
145 | n := rand.Intn(10) // maybe this wait should be variablized and pulled from an attrbiute
146 | log.Printf("[DEBUG] Found duplicate gidNumber %d. Trying again in %d seconds.", next_available_gid, n)
147 | time.Sleep(time.Duration(n) * time.Second)
148 | }
149 | // maybe need some max retries code here
150 | }
151 | }
152 |
153 | log.Printf("[DEBUG] Group Added to AD successfully: %s", groupName)
154 | d.SetId(OUDistinguishedName + "/" + groupName)
155 | return nil
156 | }
157 |
158 | func resourceADGroupToOURead(d *schema.ResourceData, meta interface{}) error {
159 | log.Println("[ERROR] In Read function")
160 | client := meta.(*ldap.Conn)
161 |
162 | groupName := d.Get("group_name").(string)
163 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
164 | var dnOfGroup string
165 | dnOfGroup += OUDistinguishedName
166 |
167 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfGroup)
168 | log.Printf("[DEBUG] Searching the Group from the AD : %s ", groupName)
169 |
170 | searchRequest := ldap.NewSearchRequest(
171 | dnOfGroup, // The base dn to search
172 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
173 | "(&(objectClass=Group)(cn="+groupName+"))", // The filter to apply
174 | []string{"dn", "cn"}, // A list attributes to retrieve
175 | nil,
176 | )
177 |
178 | sr, err := client.Search(searchRequest)
179 | if err != nil {
180 | log.Printf("[ERROR] Error while searching a Group : %s ", err)
181 | return fmt.Errorf("Error while searching a Group : %s", err)
182 | }
183 | fmt.Println("[ERROR] Found " + strconv.Itoa(len(sr.Entries)) + " Entries")
184 | for _, entry := range sr.Entries {
185 | fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
186 | }
187 | if len(sr.Entries) == 0 {
188 | log.Println("[ERROR] Group was not found")
189 | d.SetId("")
190 | }
191 | return nil
192 | }
193 |
194 | func resourceADGroupToOUDelete(d *schema.ResourceData, meta interface{}) error {
195 | log.Println("[ERROR] Finding group")
196 | resourceADGroupToOURead(d, meta)
197 | if d.Id() == "" {
198 | log.Println("[ERROR] Cannot find Group in the specified AD")
199 | return fmt.Errorf("[ERROR] Cannot find Group in the specified AD")
200 | }
201 | client := meta.(*ldap.Conn)
202 |
203 | groupName := d.Get("group_name").(string)
204 | OUDistinguishedName := d.Get("ou_distinguished_name").(string)
205 | var dnOfGroup string
206 | dnOfGroup += "cn=" + groupName + "," + OUDistinguishedName
207 |
208 | log.Printf("[DEBUG] Name of the DN is : %s ", dnOfGroup)
209 | log.Printf("[DEBUG] Deleting the Group from the AD : %s ", groupName)
210 |
211 | err := deleteGroupFromAD(dnOfGroup, client)
212 | if err != nil {
213 | log.Printf("[ERROR] Error while Deleting a Group from AD : %s ", err)
214 | return fmt.Errorf("Error while Deleting a Group from AD %s", err)
215 | }
216 | log.Printf("[DEBUG] Group deleted from AD successfully: %s", groupName)
217 | return nil
218 | }
219 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/dn.go:
--------------------------------------------------------------------------------
1 | // File contains DN parsing functionality
2 | //
3 | // https://tools.ietf.org/html/rfc4514
4 | //
5 | // distinguishedName = [ relativeDistinguishedName
6 | // *( COMMA relativeDistinguishedName ) ]
7 | // relativeDistinguishedName = attributeTypeAndValue
8 | // *( PLUS attributeTypeAndValue )
9 | // attributeTypeAndValue = attributeType EQUALS attributeValue
10 | // attributeType = descr / numericoid
11 | // attributeValue = string / hexstring
12 | //
13 | // ; The following characters are to be escaped when they appear
14 | // ; in the value to be encoded: ESC, one of , leading
15 | // ; SHARP or SPACE, trailing SPACE, and NULL.
16 | // string = [ ( leadchar / pair ) [ *( stringchar / pair )
17 | // ( trailchar / pair ) ] ]
18 | //
19 | // leadchar = LUTF1 / UTFMB
20 | // LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
21 | // %x3D / %x3F-5B / %x5D-7F
22 | //
23 | // trailchar = TUTF1 / UTFMB
24 | // TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
25 | // %x3D / %x3F-5B / %x5D-7F
26 | //
27 | // stringchar = SUTF1 / UTFMB
28 | // SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
29 | // %x3D / %x3F-5B / %x5D-7F
30 | //
31 | // pair = ESC ( ESC / special / hexpair )
32 | // special = escaped / SPACE / SHARP / EQUALS
33 | // escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
34 | // hexstring = SHARP 1*hexpair
35 | // hexpair = HEX HEX
36 | //
37 | // where the productions , , , ,
38 | // , , , , , , , ,
39 | // , , and are defined in [RFC4512].
40 | //
41 |
42 | package ldap
43 |
44 | import (
45 | "bytes"
46 | enchex "encoding/hex"
47 | "errors"
48 | "fmt"
49 | "strings"
50 |
51 | "github.com/go-asn1-ber/asn1-ber"
52 | )
53 |
54 | // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
55 | type AttributeTypeAndValue struct {
56 | // Type is the attribute type
57 | Type string
58 | // Value is the attribute value
59 | Value string
60 | }
61 |
62 | // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
63 | type RelativeDN struct {
64 | Attributes []*AttributeTypeAndValue
65 | }
66 |
67 | // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
68 | type DN struct {
69 | RDNs []*RelativeDN
70 | }
71 |
72 | // ParseDN returns a distinguishedName or an error
73 | func ParseDN(str string) (*DN, error) {
74 | dn := new(DN)
75 | dn.RDNs = make([]*RelativeDN, 0)
76 | rdn := new(RelativeDN)
77 | rdn.Attributes = make([]*AttributeTypeAndValue, 0)
78 | buffer := bytes.Buffer{}
79 | attribute := new(AttributeTypeAndValue)
80 | escaping := false
81 |
82 | unescapedTrailingSpaces := 0
83 | stringFromBuffer := func() string {
84 | s := buffer.String()
85 | s = s[0 : len(s)-unescapedTrailingSpaces]
86 | buffer.Reset()
87 | unescapedTrailingSpaces = 0
88 | return s
89 | }
90 |
91 | for i := 0; i < len(str); i++ {
92 | char := str[i]
93 | switch {
94 | case escaping:
95 | unescapedTrailingSpaces = 0
96 | escaping = false
97 | switch char {
98 | case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
99 | buffer.WriteByte(char)
100 | continue
101 | }
102 | // Not a special character, assume hex encoded octet
103 | if len(str) == i+1 {
104 | return nil, errors.New("got corrupted escaped character")
105 | }
106 |
107 | dst := []byte{0}
108 | n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
109 | if err != nil {
110 | return nil, fmt.Errorf("failed to decode escaped character: %s", err)
111 | } else if n != 1 {
112 | return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n)
113 | }
114 | buffer.WriteByte(dst[0])
115 | i++
116 | case char == '\\':
117 | unescapedTrailingSpaces = 0
118 | escaping = true
119 | case char == '=':
120 | attribute.Type = stringFromBuffer()
121 | // Special case: If the first character in the value is # the
122 | // following data is BER encoded so we can just fast forward
123 | // and decode.
124 | if len(str) > i+1 && str[i+1] == '#' {
125 | i += 2
126 | index := strings.IndexAny(str[i:], ",+")
127 | data := str
128 | if index > 0 {
129 | data = str[i : i+index]
130 | } else {
131 | data = str[i:]
132 | }
133 | rawBER, err := enchex.DecodeString(data)
134 | if err != nil {
135 | return nil, fmt.Errorf("failed to decode BER encoding: %s", err)
136 | }
137 | packet, err := ber.DecodePacketErr(rawBER)
138 | if err != nil {
139 | return nil, fmt.Errorf("failed to decode BER packet: %s", err)
140 | }
141 | buffer.WriteString(packet.Data.String())
142 | i += len(data) - 1
143 | }
144 | case char == ',' || char == '+':
145 | // We're done with this RDN or value, push it
146 | if len(attribute.Type) == 0 {
147 | return nil, errors.New("incomplete type, value pair")
148 | }
149 | attribute.Value = stringFromBuffer()
150 | rdn.Attributes = append(rdn.Attributes, attribute)
151 | attribute = new(AttributeTypeAndValue)
152 | if char == ',' {
153 | dn.RDNs = append(dn.RDNs, rdn)
154 | rdn = new(RelativeDN)
155 | rdn.Attributes = make([]*AttributeTypeAndValue, 0)
156 | }
157 | case char == ' ' && buffer.Len() == 0:
158 | // ignore unescaped leading spaces
159 | continue
160 | default:
161 | if char == ' ' {
162 | // Track unescaped spaces in case they are trailing and we need to remove them
163 | unescapedTrailingSpaces++
164 | } else {
165 | // Reset if we see a non-space char
166 | unescapedTrailingSpaces = 0
167 | }
168 | buffer.WriteByte(char)
169 | }
170 | }
171 | if buffer.Len() > 0 {
172 | if len(attribute.Type) == 0 {
173 | return nil, errors.New("DN ended with incomplete type, value pair")
174 | }
175 | attribute.Value = stringFromBuffer()
176 | rdn.Attributes = append(rdn.Attributes, attribute)
177 | dn.RDNs = append(dn.RDNs, rdn)
178 | }
179 | return dn, nil
180 | }
181 |
182 | // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
183 | // Returns true if they have the same number of relative distinguished names
184 | // and corresponding relative distinguished names (by position) are the same.
185 | func (d *DN) Equal(other *DN) bool {
186 | if len(d.RDNs) != len(other.RDNs) {
187 | return false
188 | }
189 | for i := range d.RDNs {
190 | if !d.RDNs[i].Equal(other.RDNs[i]) {
191 | return false
192 | }
193 | }
194 | return true
195 | }
196 |
197 | // AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
198 | // "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
199 | // "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
200 | // "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
201 | func (d *DN) AncestorOf(other *DN) bool {
202 | if len(d.RDNs) >= len(other.RDNs) {
203 | return false
204 | }
205 | // Take the last `len(d.RDNs)` RDNs from the other DN to compare against
206 | otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
207 | for i := range d.RDNs {
208 | if !d.RDNs[i].Equal(otherRDNs[i]) {
209 | return false
210 | }
211 | }
212 | return true
213 | }
214 |
215 | // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
216 | // Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
217 | // and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
218 | // The order of attributes is not significant.
219 | // Case of attribute types is not significant.
220 | func (r *RelativeDN) Equal(other *RelativeDN) bool {
221 | if len(r.Attributes) != len(other.Attributes) {
222 | return false
223 | }
224 | return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
225 | }
226 |
227 | func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
228 | for _, attr := range attrs {
229 | found := false
230 | for _, myattr := range r.Attributes {
231 | if myattr.Equal(attr) {
232 | found = true
233 | break
234 | }
235 | }
236 | if !found {
237 | return false
238 | }
239 | }
240 | return true
241 | }
242 |
243 | // Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
244 | // Case of the attribute type is not significant
245 | func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
246 | return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
247 | }
248 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/ldap.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 |
8 | ber "github.com/go-asn1-ber/asn1-ber"
9 | )
10 |
11 | // LDAP Application Codes
12 | const (
13 | ApplicationBindRequest = 0
14 | ApplicationBindResponse = 1
15 | ApplicationUnbindRequest = 2
16 | ApplicationSearchRequest = 3
17 | ApplicationSearchResultEntry = 4
18 | ApplicationSearchResultDone = 5
19 | ApplicationModifyRequest = 6
20 | ApplicationModifyResponse = 7
21 | ApplicationAddRequest = 8
22 | ApplicationAddResponse = 9
23 | ApplicationDelRequest = 10
24 | ApplicationDelResponse = 11
25 | ApplicationModifyDNRequest = 12
26 | ApplicationModifyDNResponse = 13
27 | ApplicationCompareRequest = 14
28 | ApplicationCompareResponse = 15
29 | ApplicationAbandonRequest = 16
30 | ApplicationSearchResultReference = 19
31 | ApplicationExtendedRequest = 23
32 | ApplicationExtendedResponse = 24
33 | )
34 |
35 | // ApplicationMap contains human readable descriptions of LDAP Application Codes
36 | var ApplicationMap = map[uint8]string{
37 | ApplicationBindRequest: "Bind Request",
38 | ApplicationBindResponse: "Bind Response",
39 | ApplicationUnbindRequest: "Unbind Request",
40 | ApplicationSearchRequest: "Search Request",
41 | ApplicationSearchResultEntry: "Search Result Entry",
42 | ApplicationSearchResultDone: "Search Result Done",
43 | ApplicationModifyRequest: "Modify Request",
44 | ApplicationModifyResponse: "Modify Response",
45 | ApplicationAddRequest: "Add Request",
46 | ApplicationAddResponse: "Add Response",
47 | ApplicationDelRequest: "Del Request",
48 | ApplicationDelResponse: "Del Response",
49 | ApplicationModifyDNRequest: "Modify DN Request",
50 | ApplicationModifyDNResponse: "Modify DN Response",
51 | ApplicationCompareRequest: "Compare Request",
52 | ApplicationCompareResponse: "Compare Response",
53 | ApplicationAbandonRequest: "Abandon Request",
54 | ApplicationSearchResultReference: "Search Result Reference",
55 | ApplicationExtendedRequest: "Extended Request",
56 | ApplicationExtendedResponse: "Extended Response",
57 | }
58 |
59 | // Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
60 | const (
61 | BeheraPasswordExpired = 0
62 | BeheraAccountLocked = 1
63 | BeheraChangeAfterReset = 2
64 | BeheraPasswordModNotAllowed = 3
65 | BeheraMustSupplyOldPassword = 4
66 | BeheraInsufficientPasswordQuality = 5
67 | BeheraPasswordTooShort = 6
68 | BeheraPasswordTooYoung = 7
69 | BeheraPasswordInHistory = 8
70 | )
71 |
72 | // BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes
73 | var BeheraPasswordPolicyErrorMap = map[int8]string{
74 | BeheraPasswordExpired: "Password expired",
75 | BeheraAccountLocked: "Account locked",
76 | BeheraChangeAfterReset: "Password must be changed",
77 | BeheraPasswordModNotAllowed: "Policy prevents password modification",
78 | BeheraMustSupplyOldPassword: "Policy requires old password in order to change password",
79 | BeheraInsufficientPasswordQuality: "Password fails quality checks",
80 | BeheraPasswordTooShort: "Password is too short for policy",
81 | BeheraPasswordTooYoung: "Password has been changed too recently",
82 | BeheraPasswordInHistory: "New password is in list of old passwords",
83 | }
84 |
85 | // Adds descriptions to an LDAP Response packet for debugging
86 | func addLDAPDescriptions(packet *ber.Packet) (err error) {
87 | defer func() {
88 | if r := recover(); r != nil {
89 | err = NewError(ErrorDebugging, fmt.Errorf("ldap: cannot process packet to add descriptions: %s", r))
90 | }
91 | }()
92 | packet.Description = "LDAP Response"
93 | packet.Children[0].Description = "Message ID"
94 |
95 | application := uint8(packet.Children[1].Tag)
96 | packet.Children[1].Description = ApplicationMap[application]
97 |
98 | switch application {
99 | case ApplicationBindRequest:
100 | err = addRequestDescriptions(packet)
101 | case ApplicationBindResponse:
102 | err = addDefaultLDAPResponseDescriptions(packet)
103 | case ApplicationUnbindRequest:
104 | err = addRequestDescriptions(packet)
105 | case ApplicationSearchRequest:
106 | err = addRequestDescriptions(packet)
107 | case ApplicationSearchResultEntry:
108 | packet.Children[1].Children[0].Description = "Object Name"
109 | packet.Children[1].Children[1].Description = "Attributes"
110 | for _, child := range packet.Children[1].Children[1].Children {
111 | child.Description = "Attribute"
112 | child.Children[0].Description = "Attribute Name"
113 | child.Children[1].Description = "Attribute Values"
114 | for _, grandchild := range child.Children[1].Children {
115 | grandchild.Description = "Attribute Value"
116 | }
117 | }
118 | if len(packet.Children) == 3 {
119 | err = addControlDescriptions(packet.Children[2])
120 | }
121 | case ApplicationSearchResultDone:
122 | err = addDefaultLDAPResponseDescriptions(packet)
123 | case ApplicationModifyRequest:
124 | err = addRequestDescriptions(packet)
125 | case ApplicationModifyResponse:
126 | case ApplicationAddRequest:
127 | err = addRequestDescriptions(packet)
128 | case ApplicationAddResponse:
129 | case ApplicationDelRequest:
130 | err = addRequestDescriptions(packet)
131 | case ApplicationDelResponse:
132 | case ApplicationModifyDNRequest:
133 | err = addRequestDescriptions(packet)
134 | case ApplicationModifyDNResponse:
135 | case ApplicationCompareRequest:
136 | err = addRequestDescriptions(packet)
137 | case ApplicationCompareResponse:
138 | case ApplicationAbandonRequest:
139 | err = addRequestDescriptions(packet)
140 | case ApplicationSearchResultReference:
141 | case ApplicationExtendedRequest:
142 | err = addRequestDescriptions(packet)
143 | case ApplicationExtendedResponse:
144 | }
145 |
146 | return err
147 | }
148 |
149 | func addControlDescriptions(packet *ber.Packet) error {
150 | packet.Description = "Controls"
151 | for _, child := range packet.Children {
152 | var value *ber.Packet
153 | controlType := ""
154 | child.Description = "Control"
155 | switch len(child.Children) {
156 | case 0:
157 | // at least one child is required for control type
158 | return fmt.Errorf("at least one child is required for control type")
159 |
160 | case 1:
161 | // just type, no criticality or value
162 | controlType = child.Children[0].Value.(string)
163 | child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
164 |
165 | case 2:
166 | controlType = child.Children[0].Value.(string)
167 | child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
168 | // Children[1] could be criticality or value (both are optional)
169 | // duck-type on whether this is a boolean
170 | if _, ok := child.Children[1].Value.(bool); ok {
171 | child.Children[1].Description = "Criticality"
172 | } else {
173 | child.Children[1].Description = "Control Value"
174 | value = child.Children[1]
175 | }
176 |
177 | case 3:
178 | // criticality and value present
179 | controlType = child.Children[0].Value.(string)
180 | child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
181 | child.Children[1].Description = "Criticality"
182 | child.Children[2].Description = "Control Value"
183 | value = child.Children[2]
184 |
185 | default:
186 | // more than 3 children is invalid
187 | return fmt.Errorf("more than 3 children for control packet found")
188 | }
189 |
190 | if value == nil {
191 | continue
192 | }
193 | switch controlType {
194 | case ControlTypePaging:
195 | value.Description += " (Paging)"
196 | if value.Value != nil {
197 | valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
198 | if err != nil {
199 | return fmt.Errorf("failed to decode data bytes: %s", err)
200 | }
201 | value.Data.Truncate(0)
202 | value.Value = nil
203 | valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
204 | value.AppendChild(valueChildren)
205 | }
206 | value.Children[0].Description = "Real Search Control Value"
207 | value.Children[0].Children[0].Description = "Paging Size"
208 | value.Children[0].Children[1].Description = "Cookie"
209 |
210 | case ControlTypeBeheraPasswordPolicy:
211 | value.Description += " (Password Policy - Behera Draft)"
212 | if value.Value != nil {
213 | valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
214 | if err != nil {
215 | return fmt.Errorf("failed to decode data bytes: %s", err)
216 | }
217 | value.Data.Truncate(0)
218 | value.Value = nil
219 | value.AppendChild(valueChildren)
220 | }
221 | sequence := value.Children[0]
222 | for _, child := range sequence.Children {
223 | if child.Tag == 0 {
224 | //Warning
225 | warningPacket := child.Children[0]
226 | packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes())
227 | if err != nil {
228 | return fmt.Errorf("failed to decode data bytes: %s", err)
229 | }
230 | val, ok := packet.Value.(int64)
231 | if ok {
232 | if warningPacket.Tag == 0 {
233 | //timeBeforeExpiration
234 | value.Description += " (TimeBeforeExpiration)"
235 | warningPacket.Value = val
236 | } else if warningPacket.Tag == 1 {
237 | //graceAuthNsRemaining
238 | value.Description += " (GraceAuthNsRemaining)"
239 | warningPacket.Value = val
240 | }
241 | }
242 | } else if child.Tag == 1 {
243 | // Error
244 | packet, err := ber.DecodePacketErr(child.Data.Bytes())
245 | if err != nil {
246 | return fmt.Errorf("failed to decode data bytes: %s", err)
247 | }
248 | val, ok := packet.Value.(int8)
249 | if !ok {
250 | val = -1
251 | }
252 | child.Description = "Error"
253 | child.Value = val
254 | }
255 | }
256 | }
257 | }
258 | return nil
259 | }
260 |
261 | func addRequestDescriptions(packet *ber.Packet) error {
262 | packet.Description = "LDAP Request"
263 | packet.Children[0].Description = "Message ID"
264 | packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)]
265 | if len(packet.Children) == 3 {
266 | return addControlDescriptions(packet.Children[2])
267 | }
268 | return nil
269 | }
270 |
271 | func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error {
272 | err := GetLDAPError(packet)
273 | if err == nil {
274 | return nil
275 | }
276 | packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[err.(*Error).ResultCode] + ")"
277 | packet.Children[1].Children[1].Description = "Matched DN (" + err.(*Error).MatchedDN + ")"
278 | packet.Children[1].Children[2].Description = "Error Message"
279 | if len(packet.Children[1].Children) > 3 {
280 | packet.Children[1].Children[3].Description = "Referral"
281 | }
282 | if len(packet.Children) == 3 {
283 | return addControlDescriptions(packet.Children[2])
284 | }
285 | return nil
286 | }
287 |
288 | // DebugBinaryFile reads and prints packets from the given filename
289 | func DebugBinaryFile(fileName string) error {
290 | file, err := ioutil.ReadFile(fileName)
291 | if err != nil {
292 | return NewError(ErrorDebugging, err)
293 | }
294 | ber.PrintBytes(os.Stdout, file, "")
295 | packet, err := ber.DecodePacketErr(file)
296 | if err != nil {
297 | return fmt.Errorf("failed to decode packet: %s", err)
298 | }
299 | if err := addLDAPDescriptions(packet); err != nil {
300 | return err
301 | }
302 | ber.PrintPacket(packet)
303 |
304 | return nil
305 | }
306 |
307 | var hex = "0123456789abcdef"
308 |
309 | func mustEscape(c byte) bool {
310 | return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0
311 | }
312 |
313 | // EscapeFilter escapes from the provided LDAP filter string the special
314 | // characters in the set `()*\` and those out of the range 0 < c < 0x80,
315 | // as defined in RFC4515.
316 | func EscapeFilter(filter string) string {
317 | escape := 0
318 | for i := 0; i < len(filter); i++ {
319 | if mustEscape(filter[i]) {
320 | escape++
321 | }
322 | }
323 | if escape == 0 {
324 | return filter
325 | }
326 | buf := make([]byte, len(filter)+escape*2)
327 | for i, j := 0, 0; i < len(filter); i++ {
328 | c := filter[i]
329 | if mustEscape(c) {
330 | buf[j+0] = '\\'
331 | buf[j+1] = hex[c>>4]
332 | buf[j+2] = hex[c&0xf]
333 | j += 3
334 | } else {
335 | buf[j] = c
336 | j++
337 | }
338 | }
339 | return string(buf)
340 | }
341 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/error.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "fmt"
5 |
6 | ber "github.com/go-asn1-ber/asn1-ber"
7 | )
8 |
9 | // LDAP Result Codes
10 | const (
11 | LDAPResultSuccess = 0
12 | LDAPResultOperationsError = 1
13 | LDAPResultProtocolError = 2
14 | LDAPResultTimeLimitExceeded = 3
15 | LDAPResultSizeLimitExceeded = 4
16 | LDAPResultCompareFalse = 5
17 | LDAPResultCompareTrue = 6
18 | LDAPResultAuthMethodNotSupported = 7
19 | LDAPResultStrongAuthRequired = 8
20 | LDAPResultReferral = 10
21 | LDAPResultAdminLimitExceeded = 11
22 | LDAPResultUnavailableCriticalExtension = 12
23 | LDAPResultConfidentialityRequired = 13
24 | LDAPResultSaslBindInProgress = 14
25 | LDAPResultNoSuchAttribute = 16
26 | LDAPResultUndefinedAttributeType = 17
27 | LDAPResultInappropriateMatching = 18
28 | LDAPResultConstraintViolation = 19
29 | LDAPResultAttributeOrValueExists = 20
30 | LDAPResultInvalidAttributeSyntax = 21
31 | LDAPResultNoSuchObject = 32
32 | LDAPResultAliasProblem = 33
33 | LDAPResultInvalidDNSyntax = 34
34 | LDAPResultIsLeaf = 35
35 | LDAPResultAliasDereferencingProblem = 36
36 | LDAPResultInappropriateAuthentication = 48
37 | LDAPResultInvalidCredentials = 49
38 | LDAPResultInsufficientAccessRights = 50
39 | LDAPResultBusy = 51
40 | LDAPResultUnavailable = 52
41 | LDAPResultUnwillingToPerform = 53
42 | LDAPResultLoopDetect = 54
43 | LDAPResultSortControlMissing = 60
44 | LDAPResultOffsetRangeError = 61
45 | LDAPResultNamingViolation = 64
46 | LDAPResultObjectClassViolation = 65
47 | LDAPResultNotAllowedOnNonLeaf = 66
48 | LDAPResultNotAllowedOnRDN = 67
49 | LDAPResultEntryAlreadyExists = 68
50 | LDAPResultObjectClassModsProhibited = 69
51 | LDAPResultResultsTooLarge = 70
52 | LDAPResultAffectsMultipleDSAs = 71
53 | LDAPResultVirtualListViewErrorOrControlError = 76
54 | LDAPResultOther = 80
55 | LDAPResultServerDown = 81
56 | LDAPResultLocalError = 82
57 | LDAPResultEncodingError = 83
58 | LDAPResultDecodingError = 84
59 | LDAPResultTimeout = 85
60 | LDAPResultAuthUnknown = 86
61 | LDAPResultFilterError = 87
62 | LDAPResultUserCanceled = 88
63 | LDAPResultParamError = 89
64 | LDAPResultNoMemory = 90
65 | LDAPResultConnectError = 91
66 | LDAPResultNotSupported = 92
67 | LDAPResultControlNotFound = 93
68 | LDAPResultNoResultsReturned = 94
69 | LDAPResultMoreResultsToReturn = 95
70 | LDAPResultClientLoop = 96
71 | LDAPResultReferralLimitExceeded = 97
72 | LDAPResultInvalidResponse = 100
73 | LDAPResultAmbiguousResponse = 101
74 | LDAPResultTLSNotSupported = 112
75 | LDAPResultIntermediateResponse = 113
76 | LDAPResultUnknownType = 114
77 | LDAPResultCanceled = 118
78 | LDAPResultNoSuchOperation = 119
79 | LDAPResultTooLate = 120
80 | LDAPResultCannotCancel = 121
81 | LDAPResultAssertionFailed = 122
82 | LDAPResultAuthorizationDenied = 123
83 | LDAPResultSyncRefreshRequired = 4096
84 |
85 | ErrorNetwork = 200
86 | ErrorFilterCompile = 201
87 | ErrorFilterDecompile = 202
88 | ErrorDebugging = 203
89 | ErrorUnexpectedMessage = 204
90 | ErrorUnexpectedResponse = 205
91 | ErrorEmptyPassword = 206
92 | )
93 |
94 | // LDAPResultCodeMap contains string descriptions for LDAP error codes
95 | var LDAPResultCodeMap = map[uint16]string{
96 | LDAPResultSuccess: "Success",
97 | LDAPResultOperationsError: "Operations Error",
98 | LDAPResultProtocolError: "Protocol Error",
99 | LDAPResultTimeLimitExceeded: "Time Limit Exceeded",
100 | LDAPResultSizeLimitExceeded: "Size Limit Exceeded",
101 | LDAPResultCompareFalse: "Compare False",
102 | LDAPResultCompareTrue: "Compare True",
103 | LDAPResultAuthMethodNotSupported: "Auth Method Not Supported",
104 | LDAPResultStrongAuthRequired: "Strong Auth Required",
105 | LDAPResultReferral: "Referral",
106 | LDAPResultAdminLimitExceeded: "Admin Limit Exceeded",
107 | LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
108 | LDAPResultConfidentialityRequired: "Confidentiality Required",
109 | LDAPResultSaslBindInProgress: "Sasl Bind In Progress",
110 | LDAPResultNoSuchAttribute: "No Such Attribute",
111 | LDAPResultUndefinedAttributeType: "Undefined Attribute Type",
112 | LDAPResultInappropriateMatching: "Inappropriate Matching",
113 | LDAPResultConstraintViolation: "Constraint Violation",
114 | LDAPResultAttributeOrValueExists: "Attribute Or Value Exists",
115 | LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax",
116 | LDAPResultNoSuchObject: "No Such Object",
117 | LDAPResultAliasProblem: "Alias Problem",
118 | LDAPResultInvalidDNSyntax: "Invalid DN Syntax",
119 | LDAPResultIsLeaf: "Is Leaf",
120 | LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem",
121 | LDAPResultInappropriateAuthentication: "Inappropriate Authentication",
122 | LDAPResultInvalidCredentials: "Invalid Credentials",
123 | LDAPResultInsufficientAccessRights: "Insufficient Access Rights",
124 | LDAPResultBusy: "Busy",
125 | LDAPResultUnavailable: "Unavailable",
126 | LDAPResultUnwillingToPerform: "Unwilling To Perform",
127 | LDAPResultLoopDetect: "Loop Detect",
128 | LDAPResultSortControlMissing: "Sort Control Missing",
129 | LDAPResultOffsetRangeError: "Result Offset Range Error",
130 | LDAPResultNamingViolation: "Naming Violation",
131 | LDAPResultObjectClassViolation: "Object Class Violation",
132 | LDAPResultResultsTooLarge: "Results Too Large",
133 | LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf",
134 | LDAPResultNotAllowedOnRDN: "Not Allowed On RDN",
135 | LDAPResultEntryAlreadyExists: "Entry Already Exists",
136 | LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
137 | LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
138 | LDAPResultVirtualListViewErrorOrControlError: "Failed because of a problem related to the virtual list view",
139 | LDAPResultOther: "Other",
140 | LDAPResultServerDown: "Cannot establish a connection",
141 | LDAPResultLocalError: "An error occurred",
142 | LDAPResultEncodingError: "LDAP encountered an error while encoding",
143 | LDAPResultDecodingError: "LDAP encountered an error while decoding",
144 | LDAPResultTimeout: "LDAP timeout while waiting for a response from the server",
145 | LDAPResultAuthUnknown: "The auth method requested in a bind request is unknown",
146 | LDAPResultFilterError: "An error occurred while encoding the given search filter",
147 | LDAPResultUserCanceled: "The user canceled the operation",
148 | LDAPResultParamError: "An invalid parameter was specified",
149 | LDAPResultNoMemory: "Out of memory error",
150 | LDAPResultConnectError: "A connection to the server could not be established",
151 | LDAPResultNotSupported: "An attempt has been made to use a feature not supported LDAP",
152 | LDAPResultControlNotFound: "The controls required to perform the requested operation were not found",
153 | LDAPResultNoResultsReturned: "No results were returned from the server",
154 | LDAPResultMoreResultsToReturn: "There are more results in the chain of results",
155 | LDAPResultClientLoop: "A loop has been detected. For example when following referrals",
156 | LDAPResultReferralLimitExceeded: "The referral hop limit has been exceeded",
157 | LDAPResultCanceled: "Operation was canceled",
158 | LDAPResultNoSuchOperation: "Server has no knowledge of the operation requested for cancellation",
159 | LDAPResultTooLate: "Too late to cancel the outstanding operation",
160 | LDAPResultCannotCancel: "The identified operation does not support cancellation or the cancel operation cannot be performed",
161 | LDAPResultAssertionFailed: "An assertion control given in the LDAP operation evaluated to false causing the operation to not be performed",
162 | LDAPResultSyncRefreshRequired: "Refresh Required",
163 | LDAPResultInvalidResponse: "Invalid Response",
164 | LDAPResultAmbiguousResponse: "Ambiguous Response",
165 | LDAPResultTLSNotSupported: "Tls Not Supported",
166 | LDAPResultIntermediateResponse: "Intermediate Response",
167 | LDAPResultUnknownType: "Unknown Type",
168 | LDAPResultAuthorizationDenied: "Authorization Denied",
169 |
170 | ErrorNetwork: "Network Error",
171 | ErrorFilterCompile: "Filter Compile Error",
172 | ErrorFilterDecompile: "Filter Decompile Error",
173 | ErrorDebugging: "Debugging Error",
174 | ErrorUnexpectedMessage: "Unexpected Message",
175 | ErrorUnexpectedResponse: "Unexpected Response",
176 | ErrorEmptyPassword: "Empty password not allowed by the client",
177 | }
178 |
179 | // Error holds LDAP error information
180 | type Error struct {
181 | // Err is the underlying error
182 | Err error
183 | // ResultCode is the LDAP error code
184 | ResultCode uint16
185 | // MatchedDN is the matchedDN returned if any
186 | MatchedDN string
187 | }
188 |
189 | func (e *Error) Error() string {
190 | return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
191 | }
192 |
193 | // GetLDAPError creates an Error out of a BER packet representing a LDAPResult
194 | // The return is an error object. It can be casted to a Error structure.
195 | // This function returns nil if resultCode in the LDAPResult sequence is success(0).
196 | func GetLDAPError(packet *ber.Packet) error {
197 | if packet == nil {
198 | return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")}
199 | }
200 |
201 | if len(packet.Children) >= 2 {
202 | response := packet.Children[1]
203 | if response == nil {
204 | return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet")}
205 | }
206 | if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
207 | resultCode := uint16(response.Children[0].Value.(int64))
208 | if resultCode == 0 { // No error
209 | return nil
210 | }
211 | return &Error{ResultCode: resultCode, MatchedDN: response.Children[1].Value.(string),
212 | Err: fmt.Errorf("%s", response.Children[2].Value.(string))}
213 | }
214 | }
215 |
216 | return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format")}
217 | }
218 |
219 | // NewError creates an LDAP error with the given code and underlying error
220 | func NewError(resultCode uint16, err error) error {
221 | return &Error{ResultCode: resultCode, Err: err}
222 | }
223 |
224 | // IsErrorWithCode returns true if the given error is an LDAP error with the given result code
225 | func IsErrorWithCode(err error, desiredResultCode uint16) bool {
226 | if err == nil {
227 | return false
228 | }
229 |
230 | serverError, ok := err.(*Error)
231 | if !ok {
232 | return false
233 | }
234 |
235 | return serverError.ResultCode == desiredResultCode
236 | }
237 |
--------------------------------------------------------------------------------
/vendor/github.com/go-asn1-ber/asn1-ber/ber.go:
--------------------------------------------------------------------------------
1 | package ber
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "math"
9 | "os"
10 | "reflect"
11 | )
12 |
13 | // MaxPacketLengthBytes specifies the maximum allowed packet size when calling ReadPacket or DecodePacket. Set to 0 for
14 | // no limit.
15 | var MaxPacketLengthBytes int64 = math.MaxInt32
16 |
17 | type Packet struct {
18 | Identifier
19 | Value interface{}
20 | ByteValue []byte
21 | Data *bytes.Buffer
22 | Children []*Packet
23 | Description string
24 | }
25 |
26 | type Identifier struct {
27 | ClassType Class
28 | TagType Type
29 | Tag Tag
30 | }
31 |
32 | type Tag uint64
33 |
34 | const (
35 | TagEOC Tag = 0x00
36 | TagBoolean Tag = 0x01
37 | TagInteger Tag = 0x02
38 | TagBitString Tag = 0x03
39 | TagOctetString Tag = 0x04
40 | TagNULL Tag = 0x05
41 | TagObjectIdentifier Tag = 0x06
42 | TagObjectDescriptor Tag = 0x07
43 | TagExternal Tag = 0x08
44 | TagRealFloat Tag = 0x09
45 | TagEnumerated Tag = 0x0a
46 | TagEmbeddedPDV Tag = 0x0b
47 | TagUTF8String Tag = 0x0c
48 | TagRelativeOID Tag = 0x0d
49 | TagSequence Tag = 0x10
50 | TagSet Tag = 0x11
51 | TagNumericString Tag = 0x12
52 | TagPrintableString Tag = 0x13
53 | TagT61String Tag = 0x14
54 | TagVideotexString Tag = 0x15
55 | TagIA5String Tag = 0x16
56 | TagUTCTime Tag = 0x17
57 | TagGeneralizedTime Tag = 0x18
58 | TagGraphicString Tag = 0x19
59 | TagVisibleString Tag = 0x1a
60 | TagGeneralString Tag = 0x1b
61 | TagUniversalString Tag = 0x1c
62 | TagCharacterString Tag = 0x1d
63 | TagBMPString Tag = 0x1e
64 | TagBitmask Tag = 0x1f // xxx11111b
65 |
66 | // HighTag indicates the start of a high-tag byte sequence
67 | HighTag Tag = 0x1f // xxx11111b
68 | // HighTagContinueBitmask indicates the high-tag byte sequence should continue
69 | HighTagContinueBitmask Tag = 0x80 // 10000000b
70 | // HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte
71 | HighTagValueBitmask Tag = 0x7f // 01111111b
72 | )
73 |
74 | const (
75 | // LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used
76 | LengthLongFormBitmask = 0x80
77 | // LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence
78 | LengthValueBitmask = 0x7f
79 |
80 | // LengthIndefinite is returned from readLength to indicate an indefinite length
81 | LengthIndefinite = -1
82 | )
83 |
84 | var tagMap = map[Tag]string{
85 | TagEOC: "EOC (End-of-Content)",
86 | TagBoolean: "Boolean",
87 | TagInteger: "Integer",
88 | TagBitString: "Bit String",
89 | TagOctetString: "Octet String",
90 | TagNULL: "NULL",
91 | TagObjectIdentifier: "Object Identifier",
92 | TagObjectDescriptor: "Object Descriptor",
93 | TagExternal: "External",
94 | TagRealFloat: "Real (float)",
95 | TagEnumerated: "Enumerated",
96 | TagEmbeddedPDV: "Embedded PDV",
97 | TagUTF8String: "UTF8 String",
98 | TagRelativeOID: "Relative-OID",
99 | TagSequence: "Sequence and Sequence of",
100 | TagSet: "Set and Set OF",
101 | TagNumericString: "Numeric String",
102 | TagPrintableString: "Printable String",
103 | TagT61String: "T61 String",
104 | TagVideotexString: "Videotex String",
105 | TagIA5String: "IA5 String",
106 | TagUTCTime: "UTC Time",
107 | TagGeneralizedTime: "Generalized Time",
108 | TagGraphicString: "Graphic String",
109 | TagVisibleString: "Visible String",
110 | TagGeneralString: "General String",
111 | TagUniversalString: "Universal String",
112 | TagCharacterString: "Character String",
113 | TagBMPString: "BMP String",
114 | }
115 |
116 | type Class uint8
117 |
118 | const (
119 | ClassUniversal Class = 0 // 00xxxxxxb
120 | ClassApplication Class = 64 // 01xxxxxxb
121 | ClassContext Class = 128 // 10xxxxxxb
122 | ClassPrivate Class = 192 // 11xxxxxxb
123 | ClassBitmask Class = 192 // 11xxxxxxb
124 | )
125 |
126 | var ClassMap = map[Class]string{
127 | ClassUniversal: "Universal",
128 | ClassApplication: "Application",
129 | ClassContext: "Context",
130 | ClassPrivate: "Private",
131 | }
132 |
133 | type Type uint8
134 |
135 | const (
136 | TypePrimitive Type = 0 // xx0xxxxxb
137 | TypeConstructed Type = 32 // xx1xxxxxb
138 | TypeBitmask Type = 32 // xx1xxxxxb
139 | )
140 |
141 | var TypeMap = map[Type]string{
142 | TypePrimitive: "Primitive",
143 | TypeConstructed: "Constructed",
144 | }
145 |
146 | var Debug bool = false
147 |
148 | func PrintBytes(out io.Writer, buf []byte, indent string) {
149 | data_lines := make([]string, (len(buf)/30)+1)
150 | num_lines := make([]string, (len(buf)/30)+1)
151 |
152 | for i, b := range buf {
153 | data_lines[i/30] += fmt.Sprintf("%02x ", b)
154 | num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
155 | }
156 |
157 | for i := 0; i < len(data_lines); i++ {
158 | out.Write([]byte(indent + data_lines[i] + "\n"))
159 | out.Write([]byte(indent + num_lines[i] + "\n\n"))
160 | }
161 | }
162 |
163 | func WritePacket(out io.Writer, p *Packet) {
164 | printPacket(out, p, 0, false)
165 | }
166 |
167 | func PrintPacket(p *Packet) {
168 | printPacket(os.Stdout, p, 0, false)
169 | }
170 |
171 | func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
172 | indent_str := ""
173 |
174 | for len(indent_str) != indent {
175 | indent_str += " "
176 | }
177 |
178 | class_str := ClassMap[p.ClassType]
179 |
180 | tagtype_str := TypeMap[p.TagType]
181 |
182 | tag_str := fmt.Sprintf("0x%02X", p.Tag)
183 |
184 | if p.ClassType == ClassUniversal {
185 | tag_str = tagMap[p.Tag]
186 | }
187 |
188 | value := fmt.Sprint(p.Value)
189 | description := ""
190 |
191 | if p.Description != "" {
192 | description = p.Description + ": "
193 | }
194 |
195 | fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
196 |
197 | if printBytes {
198 | PrintBytes(out, p.Bytes(), indent_str)
199 | }
200 |
201 | for _, child := range p.Children {
202 | printPacket(out, child, indent+1, printBytes)
203 | }
204 | }
205 |
206 | // ReadPacket reads a single Packet from the reader
207 | func ReadPacket(reader io.Reader) (*Packet, error) {
208 | p, _, err := readPacket(reader)
209 | if err != nil {
210 | return nil, err
211 | }
212 | return p, nil
213 | }
214 |
215 | func DecodeString(data []byte) string {
216 | return string(data)
217 | }
218 |
219 | func ParseInt64(bytes []byte) (ret int64, err error) {
220 | if len(bytes) > 8 {
221 | // We'll overflow an int64 in this case.
222 | err = fmt.Errorf("integer too large")
223 | return
224 | }
225 | for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
226 | ret <<= 8
227 | ret |= int64(bytes[bytesRead])
228 | }
229 |
230 | // Shift up and down in order to sign extend the result.
231 | ret <<= 64 - uint8(len(bytes))*8
232 | ret >>= 64 - uint8(len(bytes))*8
233 | return
234 | }
235 |
236 | func encodeInteger(i int64) []byte {
237 | n := int64Length(i)
238 | out := make([]byte, n)
239 |
240 | var j int
241 | for ; n > 0; n-- {
242 | out[j] = (byte(i >> uint((n-1)*8)))
243 | j++
244 | }
245 |
246 | return out
247 | }
248 |
249 | func int64Length(i int64) (numBytes int) {
250 | numBytes = 1
251 |
252 | for i > 127 {
253 | numBytes++
254 | i >>= 8
255 | }
256 |
257 | for i < -128 {
258 | numBytes++
259 | i >>= 8
260 | }
261 |
262 | return
263 | }
264 |
265 | // DecodePacket decodes the given bytes into a single Packet
266 | // If a decode error is encountered, nil is returned.
267 | func DecodePacket(data []byte) *Packet {
268 | p, _, _ := readPacket(bytes.NewBuffer(data))
269 |
270 | return p
271 | }
272 |
273 | // DecodePacketErr decodes the given bytes into a single Packet
274 | // If a decode error is encountered, nil is returned
275 | func DecodePacketErr(data []byte) (*Packet, error) {
276 | p, _, err := readPacket(bytes.NewBuffer(data))
277 | if err != nil {
278 | return nil, err
279 | }
280 | return p, nil
281 | }
282 |
283 | // readPacket reads a single Packet from the reader, returning the number of bytes read
284 | func readPacket(reader io.Reader) (*Packet, int, error) {
285 | identifier, length, read, err := readHeader(reader)
286 | if err != nil {
287 | return nil, read, err
288 | }
289 |
290 | p := &Packet{
291 | Identifier: identifier,
292 | }
293 |
294 | p.Data = new(bytes.Buffer)
295 | p.Children = make([]*Packet, 0, 2)
296 | p.Value = nil
297 |
298 | if p.TagType == TypeConstructed {
299 | // TODO: if universal, ensure tag type is allowed to be constructed
300 |
301 | // Track how much content we've read
302 | contentRead := 0
303 | for {
304 | if length != LengthIndefinite {
305 | // End if we've read what we've been told to
306 | if contentRead == length {
307 | break
308 | }
309 | // Detect if a packet boundary didn't fall on the expected length
310 | if contentRead > length {
311 | return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead)
312 | }
313 | }
314 |
315 | // Read the next packet
316 | child, r, err := readPacket(reader)
317 | if err != nil {
318 | return nil, read, err
319 | }
320 | contentRead += r
321 | read += r
322 |
323 | // Test is this is the EOC marker for our packet
324 | if isEOCPacket(child) {
325 | if length == LengthIndefinite {
326 | break
327 | }
328 | return nil, read, errors.New("eoc child not allowed with definite length")
329 | }
330 |
331 | // Append and continue
332 | p.AppendChild(child)
333 | }
334 | return p, read, nil
335 | }
336 |
337 | if length == LengthIndefinite {
338 | return nil, read, errors.New("indefinite length used with primitive type")
339 | }
340 |
341 | // Read definite-length content
342 | if MaxPacketLengthBytes > 0 && int64(length) > MaxPacketLengthBytes {
343 | return nil, read, fmt.Errorf("length %d greater than maximum %d", length, MaxPacketLengthBytes)
344 | }
345 | content := make([]byte, length, length)
346 | if length > 0 {
347 | _, err := io.ReadFull(reader, content)
348 | if err != nil {
349 | if err == io.EOF {
350 | return nil, read, io.ErrUnexpectedEOF
351 | }
352 | return nil, read, err
353 | }
354 | read += length
355 | }
356 |
357 | if p.ClassType == ClassUniversal {
358 | p.Data.Write(content)
359 | p.ByteValue = content
360 |
361 | switch p.Tag {
362 | case TagEOC:
363 | case TagBoolean:
364 | val, _ := ParseInt64(content)
365 |
366 | p.Value = val != 0
367 | case TagInteger:
368 | p.Value, _ = ParseInt64(content)
369 | case TagBitString:
370 | case TagOctetString:
371 | // the actual string encoding is not known here
372 | // (e.g. for LDAP content is already an UTF8-encoded
373 | // string). Return the data without further processing
374 | p.Value = DecodeString(content)
375 | case TagNULL:
376 | case TagObjectIdentifier:
377 | case TagObjectDescriptor:
378 | case TagExternal:
379 | case TagRealFloat:
380 | case TagEnumerated:
381 | p.Value, _ = ParseInt64(content)
382 | case TagEmbeddedPDV:
383 | case TagUTF8String:
384 | p.Value = DecodeString(content)
385 | case TagRelativeOID:
386 | case TagSequence:
387 | case TagSet:
388 | case TagNumericString:
389 | case TagPrintableString:
390 | p.Value = DecodeString(content)
391 | case TagT61String:
392 | case TagVideotexString:
393 | case TagIA5String:
394 | case TagUTCTime:
395 | case TagGeneralizedTime:
396 | case TagGraphicString:
397 | case TagVisibleString:
398 | case TagGeneralString:
399 | case TagUniversalString:
400 | case TagCharacterString:
401 | case TagBMPString:
402 | }
403 | } else {
404 | p.Data.Write(content)
405 | }
406 |
407 | return p, read, nil
408 | }
409 |
410 | func (p *Packet) Bytes() []byte {
411 | var out bytes.Buffer
412 |
413 | out.Write(encodeIdentifier(p.Identifier))
414 | out.Write(encodeLength(p.Data.Len()))
415 | out.Write(p.Data.Bytes())
416 |
417 | return out.Bytes()
418 | }
419 |
420 | func (p *Packet) AppendChild(child *Packet) {
421 | p.Data.Write(child.Bytes())
422 | p.Children = append(p.Children, child)
423 | }
424 |
425 | func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
426 | p := new(Packet)
427 |
428 | p.ClassType = ClassType
429 | p.TagType = TagType
430 | p.Tag = Tag
431 | p.Data = new(bytes.Buffer)
432 |
433 | p.Children = make([]*Packet, 0, 2)
434 |
435 | p.Value = Value
436 | p.Description = Description
437 |
438 | if Value != nil {
439 | v := reflect.ValueOf(Value)
440 |
441 | if ClassType == ClassUniversal {
442 | switch Tag {
443 | case TagOctetString:
444 | sv, ok := v.Interface().(string)
445 |
446 | if ok {
447 | p.Data.Write([]byte(sv))
448 | }
449 | }
450 | }
451 | }
452 |
453 | return p
454 | }
455 |
456 | func NewSequence(Description string) *Packet {
457 | return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description)
458 | }
459 |
460 | func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet {
461 | intValue := int64(0)
462 |
463 | if Value {
464 | intValue = 1
465 | }
466 |
467 | p := Encode(ClassType, TagType, Tag, nil, Description)
468 |
469 | p.Value = Value
470 | p.Data.Write(encodeInteger(intValue))
471 |
472 | return p
473 | }
474 |
475 | func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
476 | p := Encode(ClassType, TagType, Tag, nil, Description)
477 |
478 | p.Value = Value
479 | switch v := Value.(type) {
480 | case int:
481 | p.Data.Write(encodeInteger(int64(v)))
482 | case uint:
483 | p.Data.Write(encodeInteger(int64(v)))
484 | case int64:
485 | p.Data.Write(encodeInteger(v))
486 | case uint64:
487 | // TODO : check range or add encodeUInt...
488 | p.Data.Write(encodeInteger(int64(v)))
489 | case int32:
490 | p.Data.Write(encodeInteger(int64(v)))
491 | case uint32:
492 | p.Data.Write(encodeInteger(int64(v)))
493 | case int16:
494 | p.Data.Write(encodeInteger(int64(v)))
495 | case uint16:
496 | p.Data.Write(encodeInteger(int64(v)))
497 | case int8:
498 | p.Data.Write(encodeInteger(int64(v)))
499 | case uint8:
500 | p.Data.Write(encodeInteger(int64(v)))
501 | default:
502 | // TODO : add support for big.Int ?
503 | panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v))
504 | }
505 |
506 | return p
507 | }
508 |
509 | func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet {
510 | p := Encode(ClassType, TagType, Tag, nil, Description)
511 |
512 | p.Value = Value
513 | p.Data.Write([]byte(Value))
514 |
515 | return p
516 | }
517 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/search.go:
--------------------------------------------------------------------------------
1 | // File contains Search functionality
2 | //
3 | // https://tools.ietf.org/html/rfc4511
4 | //
5 | // SearchRequest ::= [APPLICATION 3] SEQUENCE {
6 | // baseObject LDAPDN,
7 | // scope ENUMERATED {
8 | // baseObject (0),
9 | // singleLevel (1),
10 | // wholeSubtree (2),
11 | // ... },
12 | // derefAliases ENUMERATED {
13 | // neverDerefAliases (0),
14 | // derefInSearching (1),
15 | // derefFindingBaseObj (2),
16 | // derefAlways (3) },
17 | // sizeLimit INTEGER (0 .. maxInt),
18 | // timeLimit INTEGER (0 .. maxInt),
19 | // typesOnly BOOLEAN,
20 | // filter Filter,
21 | // attributes AttributeSelection }
22 | //
23 | // AttributeSelection ::= SEQUENCE OF selector LDAPString
24 | // -- The LDAPString is constrained to
25 | // -- in Section 4.5.1.8
26 | //
27 | // Filter ::= CHOICE {
28 | // and [0] SET SIZE (1..MAX) OF filter Filter,
29 | // or [1] SET SIZE (1..MAX) OF filter Filter,
30 | // not [2] Filter,
31 | // equalityMatch [3] AttributeValueAssertion,
32 | // substrings [4] SubstringFilter,
33 | // greaterOrEqual [5] AttributeValueAssertion,
34 | // lessOrEqual [6] AttributeValueAssertion,
35 | // present [7] AttributeDescription,
36 | // approxMatch [8] AttributeValueAssertion,
37 | // extensibleMatch [9] MatchingRuleAssertion,
38 | // ... }
39 | //
40 | // SubstringFilter ::= SEQUENCE {
41 | // type AttributeDescription,
42 | // substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
43 | // initial [0] AssertionValue, -- can occur at most once
44 | // any [1] AssertionValue,
45 | // final [2] AssertionValue } -- can occur at most once
46 | // }
47 | //
48 | // MatchingRuleAssertion ::= SEQUENCE {
49 | // matchingRule [1] MatchingRuleId OPTIONAL,
50 | // type [2] AttributeDescription OPTIONAL,
51 | // matchValue [3] AssertionValue,
52 | // dnAttributes [4] BOOLEAN DEFAULT FALSE }
53 | //
54 | //
55 |
56 | package ldap
57 |
58 | import (
59 | "errors"
60 | "fmt"
61 | "sort"
62 | "strings"
63 |
64 | ber "github.com/go-asn1-ber/asn1-ber"
65 | )
66 |
67 | // scope choices
68 | const (
69 | ScopeBaseObject = 0
70 | ScopeSingleLevel = 1
71 | ScopeWholeSubtree = 2
72 | )
73 |
74 | // ScopeMap contains human readable descriptions of scope choices
75 | var ScopeMap = map[int]string{
76 | ScopeBaseObject: "Base Object",
77 | ScopeSingleLevel: "Single Level",
78 | ScopeWholeSubtree: "Whole Subtree",
79 | }
80 |
81 | // derefAliases
82 | const (
83 | NeverDerefAliases = 0
84 | DerefInSearching = 1
85 | DerefFindingBaseObj = 2
86 | DerefAlways = 3
87 | )
88 |
89 | // DerefMap contains human readable descriptions of derefAliases choices
90 | var DerefMap = map[int]string{
91 | NeverDerefAliases: "NeverDerefAliases",
92 | DerefInSearching: "DerefInSearching",
93 | DerefFindingBaseObj: "DerefFindingBaseObj",
94 | DerefAlways: "DerefAlways",
95 | }
96 |
97 | // NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
98 | // The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
99 | // same input map of attributes, the output entry will contain the same order of attributes
100 | func NewEntry(dn string, attributes map[string][]string) *Entry {
101 | var attributeNames []string
102 | for attributeName := range attributes {
103 | attributeNames = append(attributeNames, attributeName)
104 | }
105 | sort.Strings(attributeNames)
106 |
107 | var encodedAttributes []*EntryAttribute
108 | for _, attributeName := range attributeNames {
109 | encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
110 | }
111 | return &Entry{
112 | DN: dn,
113 | Attributes: encodedAttributes,
114 | }
115 | }
116 |
117 | // Entry represents a single search result entry
118 | type Entry struct {
119 | // DN is the distinguished name of the entry
120 | DN string
121 | // Attributes are the returned attributes for the entry
122 | Attributes []*EntryAttribute
123 | }
124 |
125 | // GetAttributeValues returns the values for the named attribute, or an empty list
126 | func (e *Entry) GetAttributeValues(attribute string) []string {
127 | for _, attr := range e.Attributes {
128 | if attr.Name == attribute {
129 | return attr.Values
130 | }
131 | }
132 | return []string{}
133 | }
134 |
135 | // GetRawAttributeValues returns the byte values for the named attribute, or an empty list
136 | func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
137 | for _, attr := range e.Attributes {
138 | if attr.Name == attribute {
139 | return attr.ByteValues
140 | }
141 | }
142 | return [][]byte{}
143 | }
144 |
145 | // GetAttributeValue returns the first value for the named attribute, or ""
146 | func (e *Entry) GetAttributeValue(attribute string) string {
147 | values := e.GetAttributeValues(attribute)
148 | if len(values) == 0 {
149 | return ""
150 | }
151 | return values[0]
152 | }
153 |
154 | // GetRawAttributeValue returns the first value for the named attribute, or an empty slice
155 | func (e *Entry) GetRawAttributeValue(attribute string) []byte {
156 | values := e.GetRawAttributeValues(attribute)
157 | if len(values) == 0 {
158 | return []byte{}
159 | }
160 | return values[0]
161 | }
162 |
163 | // Print outputs a human-readable description
164 | func (e *Entry) Print() {
165 | fmt.Printf("DN: %s\n", e.DN)
166 | for _, attr := range e.Attributes {
167 | attr.Print()
168 | }
169 | }
170 |
171 | // PrettyPrint outputs a human-readable description indenting
172 | func (e *Entry) PrettyPrint(indent int) {
173 | fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
174 | for _, attr := range e.Attributes {
175 | attr.PrettyPrint(indent + 2)
176 | }
177 | }
178 |
179 | // NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
180 | func NewEntryAttribute(name string, values []string) *EntryAttribute {
181 | var bytes [][]byte
182 | for _, value := range values {
183 | bytes = append(bytes, []byte(value))
184 | }
185 | return &EntryAttribute{
186 | Name: name,
187 | Values: values,
188 | ByteValues: bytes,
189 | }
190 | }
191 |
192 | // EntryAttribute holds a single attribute
193 | type EntryAttribute struct {
194 | // Name is the name of the attribute
195 | Name string
196 | // Values contain the string values of the attribute
197 | Values []string
198 | // ByteValues contain the raw values of the attribute
199 | ByteValues [][]byte
200 | }
201 |
202 | // Print outputs a human-readable description
203 | func (e *EntryAttribute) Print() {
204 | fmt.Printf("%s: %s\n", e.Name, e.Values)
205 | }
206 |
207 | // PrettyPrint outputs a human-readable description with indenting
208 | func (e *EntryAttribute) PrettyPrint(indent int) {
209 | fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
210 | }
211 |
212 | // SearchResult holds the server's response to a search request
213 | type SearchResult struct {
214 | // Entries are the returned entries
215 | Entries []*Entry
216 | // Referrals are the returned referrals
217 | Referrals []string
218 | // Controls are the returned controls
219 | Controls []Control
220 | }
221 |
222 | // Print outputs a human-readable description
223 | func (s *SearchResult) Print() {
224 | for _, entry := range s.Entries {
225 | entry.Print()
226 | }
227 | }
228 |
229 | // PrettyPrint outputs a human-readable description with indenting
230 | func (s *SearchResult) PrettyPrint(indent int) {
231 | for _, entry := range s.Entries {
232 | entry.PrettyPrint(indent)
233 | }
234 | }
235 |
236 | // SearchRequest represents a search request to send to the server
237 | type SearchRequest struct {
238 | BaseDN string
239 | Scope int
240 | DerefAliases int
241 | SizeLimit int
242 | TimeLimit int
243 | TypesOnly bool
244 | Filter string
245 | Attributes []string
246 | Controls []Control
247 | }
248 |
249 | func (req *SearchRequest) appendTo(envelope *ber.Packet) error {
250 | pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
251 | pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.BaseDN, "Base DN"))
252 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.Scope), "Scope"))
253 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.DerefAliases), "Deref Aliases"))
254 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.SizeLimit), "Size Limit"))
255 | pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.TimeLimit), "Time Limit"))
256 | pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.TypesOnly, "Types Only"))
257 | // compile and encode filter
258 | filterPacket, err := CompileFilter(req.Filter)
259 | if err != nil {
260 | return err
261 | }
262 | pkt.AppendChild(filterPacket)
263 | // encode attributes
264 | attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
265 | for _, attribute := range req.Attributes {
266 | attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
267 | }
268 | pkt.AppendChild(attributesPacket)
269 |
270 | envelope.AppendChild(pkt)
271 | if len(req.Controls) > 0 {
272 | envelope.AppendChild(encodeControls(req.Controls))
273 | }
274 |
275 | return nil
276 | }
277 |
278 | // NewSearchRequest creates a new search request
279 | func NewSearchRequest(
280 | BaseDN string,
281 | Scope, DerefAliases, SizeLimit, TimeLimit int,
282 | TypesOnly bool,
283 | Filter string,
284 | Attributes []string,
285 | Controls []Control,
286 | ) *SearchRequest {
287 | return &SearchRequest{
288 | BaseDN: BaseDN,
289 | Scope: Scope,
290 | DerefAliases: DerefAliases,
291 | SizeLimit: SizeLimit,
292 | TimeLimit: TimeLimit,
293 | TypesOnly: TypesOnly,
294 | Filter: Filter,
295 | Attributes: Attributes,
296 | Controls: Controls,
297 | }
298 | }
299 |
300 | // SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
301 | // search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
302 | // The following four cases are possible given the arguments:
303 | // - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
304 | // - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
305 | // - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
306 | // - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
307 | // A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
308 | func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
309 | var pagingControl *ControlPaging
310 |
311 | control := FindControl(searchRequest.Controls, ControlTypePaging)
312 | if control == nil {
313 | pagingControl = NewControlPaging(pagingSize)
314 | searchRequest.Controls = append(searchRequest.Controls, pagingControl)
315 | } else {
316 | castControl, ok := control.(*ControlPaging)
317 | if !ok {
318 | return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control)
319 | }
320 | if castControl.PagingSize != pagingSize {
321 | return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
322 | }
323 | pagingControl = castControl
324 | }
325 |
326 | searchResult := new(SearchResult)
327 | for {
328 | result, err := l.Search(searchRequest)
329 | l.Debug.Printf("Looking for Paging Control...")
330 | if err != nil {
331 | return searchResult, err
332 | }
333 | if result == nil {
334 | return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
335 | }
336 |
337 | for _, entry := range result.Entries {
338 | searchResult.Entries = append(searchResult.Entries, entry)
339 | }
340 | for _, referral := range result.Referrals {
341 | searchResult.Referrals = append(searchResult.Referrals, referral)
342 | }
343 | for _, control := range result.Controls {
344 | searchResult.Controls = append(searchResult.Controls, control)
345 | }
346 |
347 | l.Debug.Printf("Looking for Paging Control...")
348 | pagingResult := FindControl(result.Controls, ControlTypePaging)
349 | if pagingResult == nil {
350 | pagingControl = nil
351 | l.Debug.Printf("Could not find paging control. Breaking...")
352 | break
353 | }
354 |
355 | cookie := pagingResult.(*ControlPaging).Cookie
356 | if len(cookie) == 0 {
357 | pagingControl = nil
358 | l.Debug.Printf("Could not find cookie. Breaking...")
359 | break
360 | }
361 | pagingControl.SetCookie(cookie)
362 | }
363 |
364 | if pagingControl != nil {
365 | l.Debug.Printf("Abandoning Paging...")
366 | pagingControl.PagingSize = 0
367 | l.Search(searchRequest)
368 | }
369 |
370 | return searchResult, nil
371 | }
372 |
373 | // Search performs the given search request
374 | func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
375 | msgCtx, err := l.doRequest(searchRequest)
376 | if err != nil {
377 | return nil, err
378 | }
379 | defer l.finishMessage(msgCtx)
380 |
381 | result := &SearchResult{
382 | Entries: make([]*Entry, 0),
383 | Referrals: make([]string, 0),
384 | Controls: make([]Control, 0)}
385 |
386 | for {
387 | packet, err := l.readPacket(msgCtx)
388 | if err != nil {
389 | return nil, err
390 | }
391 |
392 | switch packet.Children[1].Tag {
393 | case 4:
394 | entry := new(Entry)
395 | entry.DN = packet.Children[1].Children[0].Value.(string)
396 | for _, child := range packet.Children[1].Children[1].Children {
397 | attr := new(EntryAttribute)
398 | attr.Name = child.Children[0].Value.(string)
399 | for _, value := range child.Children[1].Children {
400 | attr.Values = append(attr.Values, value.Value.(string))
401 | attr.ByteValues = append(attr.ByteValues, value.ByteValue)
402 | }
403 | entry.Attributes = append(entry.Attributes, attr)
404 | }
405 | result.Entries = append(result.Entries, entry)
406 | case 5:
407 | err := GetLDAPError(packet)
408 | if err != nil {
409 | return nil, err
410 | }
411 | if len(packet.Children) == 3 {
412 | for _, child := range packet.Children[2].Children {
413 | decodedChild, err := DecodeControl(child)
414 | if err != nil {
415 | return nil, fmt.Errorf("failed to decode child control: %s", err)
416 | }
417 | result.Controls = append(result.Controls, decodedChild)
418 | }
419 | }
420 | return result, nil
421 | case 19:
422 | result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
423 | }
424 | }
425 | }
426 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ldap.v3/conn.go:
--------------------------------------------------------------------------------
1 | package ldap
2 |
3 | import (
4 | "crypto/tls"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "net"
9 | "net/url"
10 | "sync"
11 | "sync/atomic"
12 | "time"
13 |
14 | ber "github.com/go-asn1-ber/asn1-ber"
15 | )
16 |
17 | const (
18 | // MessageQuit causes the processMessages loop to exit
19 | MessageQuit = 0
20 | // MessageRequest sends a request to the server
21 | MessageRequest = 1
22 | // MessageResponse receives a response from the server
23 | MessageResponse = 2
24 | // MessageFinish indicates the client considers a particular message ID to be finished
25 | MessageFinish = 3
26 | // MessageTimeout indicates the client-specified timeout for a particular message ID has been reached
27 | MessageTimeout = 4
28 | )
29 |
30 | const (
31 | // DefaultLdapPort default ldap port for pure TCP connection
32 | DefaultLdapPort = "389"
33 | // DefaultLdapsPort default ldap port for SSL connection
34 | DefaultLdapsPort = "636"
35 | )
36 |
37 | // PacketResponse contains the packet or error encountered reading a response
38 | type PacketResponse struct {
39 | // Packet is the packet read from the server
40 | Packet *ber.Packet
41 | // Error is an error encountered while reading
42 | Error error
43 | }
44 |
45 | // ReadPacket returns the packet or an error
46 | func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) {
47 | if (pr == nil) || (pr.Packet == nil && pr.Error == nil) {
48 | return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
49 | }
50 | return pr.Packet, pr.Error
51 | }
52 |
53 | type messageContext struct {
54 | id int64
55 | // close(done) should only be called from finishMessage()
56 | done chan struct{}
57 | // close(responses) should only be called from processMessages(), and only sent to from sendResponse()
58 | responses chan *PacketResponse
59 | }
60 |
61 | // sendResponse should only be called within the processMessages() loop which
62 | // is also responsible for closing the responses channel.
63 | func (msgCtx *messageContext) sendResponse(packet *PacketResponse) {
64 | select {
65 | case msgCtx.responses <- packet:
66 | // Successfully sent packet to message handler.
67 | case <-msgCtx.done:
68 | // The request handler is done and will not receive more
69 | // packets.
70 | }
71 | }
72 |
73 | type messagePacket struct {
74 | Op int
75 | MessageID int64
76 | Packet *ber.Packet
77 | Context *messageContext
78 | }
79 |
80 | type sendMessageFlags uint
81 |
82 | const (
83 | startTLS sendMessageFlags = 1 << iota
84 | )
85 |
86 | // Conn represents an LDAP Connection
87 | type Conn struct {
88 | // requestTimeout is loaded atomically
89 | // so we need to ensure 64-bit alignment on 32-bit platforms.
90 | requestTimeout int64
91 | conn net.Conn
92 | isTLS bool
93 | closing uint32
94 | closeErr atomic.Value
95 | isStartingTLS bool
96 | Debug debugging
97 | chanConfirm chan struct{}
98 | messageContexts map[int64]*messageContext
99 | chanMessage chan *messagePacket
100 | chanMessageID chan int64
101 | wgClose sync.WaitGroup
102 | outstandingRequests uint
103 | messageMutex sync.Mutex
104 | }
105 |
106 | var _ Client = &Conn{}
107 |
108 | // DefaultTimeout is a package-level variable that sets the timeout value
109 | // used for the Dial and DialTLS methods.
110 | //
111 | // WARNING: since this is a package-level variable, setting this value from
112 | // multiple places will probably result in undesired behaviour.
113 | var DefaultTimeout = 60 * time.Second
114 |
115 | // Dial connects to the given address on the given network using net.Dial
116 | // and then returns a new Conn for the connection.
117 | func Dial(network, addr string) (*Conn, error) {
118 | c, err := net.DialTimeout(network, addr, DefaultTimeout)
119 | if err != nil {
120 | return nil, NewError(ErrorNetwork, err)
121 | }
122 | conn := NewConn(c, false)
123 | conn.Start()
124 | return conn, nil
125 | }
126 |
127 | // DialTLS connects to the given address on the given network using tls.Dial
128 | // and then returns a new Conn for the connection.
129 | func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
130 | c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config)
131 | if err != nil {
132 | return nil, NewError(ErrorNetwork, err)
133 | }
134 | conn := NewConn(c, true)
135 | conn.Start()
136 | return conn, nil
137 | }
138 |
139 | // DialURL connects to the given ldap URL vie TCP using tls.Dial or net.Dial if ldaps://
140 | // or ldap:// specified as protocol. On success a new Conn for the connection
141 | // is returned.
142 | func DialURL(addr string) (*Conn, error) {
143 | lurl, err := url.Parse(addr)
144 | if err != nil {
145 | return nil, NewError(ErrorNetwork, err)
146 | }
147 |
148 | host, port, err := net.SplitHostPort(lurl.Host)
149 | if err != nil {
150 | // we asume that error is due to missing port
151 | host = lurl.Host
152 | port = ""
153 | }
154 |
155 | switch lurl.Scheme {
156 | case "ldapi":
157 | if lurl.Path == "" || lurl.Path == "/" {
158 | lurl.Path = "/var/run/slapd/ldapi"
159 | }
160 | return Dial("unix", lurl.Path)
161 | case "ldap":
162 | if port == "" {
163 | port = DefaultLdapPort
164 | }
165 | return Dial("tcp", net.JoinHostPort(host, port))
166 | case "ldaps":
167 | if port == "" {
168 | port = DefaultLdapsPort
169 | }
170 | tlsConf := &tls.Config{
171 | ServerName: host,
172 | }
173 | return DialTLS("tcp", net.JoinHostPort(host, port), tlsConf)
174 | }
175 |
176 | return nil, NewError(ErrorNetwork, fmt.Errorf("Unknown scheme '%s'", lurl.Scheme))
177 | }
178 |
179 | // NewConn returns a new Conn using conn for network I/O.
180 | func NewConn(conn net.Conn, isTLS bool) *Conn {
181 | return &Conn{
182 | conn: conn,
183 | chanConfirm: make(chan struct{}),
184 | chanMessageID: make(chan int64),
185 | chanMessage: make(chan *messagePacket, 10),
186 | messageContexts: map[int64]*messageContext{},
187 | requestTimeout: 0,
188 | isTLS: isTLS,
189 | }
190 | }
191 |
192 | // Start initializes goroutines to read responses and process messages
193 | func (l *Conn) Start() {
194 | l.wgClose.Add(1)
195 | go l.reader()
196 | go l.processMessages()
197 | }
198 |
199 | // IsClosing returns whether or not we're currently closing.
200 | func (l *Conn) IsClosing() bool {
201 | return atomic.LoadUint32(&l.closing) == 1
202 | }
203 |
204 | // setClosing sets the closing value to true
205 | func (l *Conn) setClosing() bool {
206 | return atomic.CompareAndSwapUint32(&l.closing, 0, 1)
207 | }
208 |
209 | // Close closes the connection.
210 | func (l *Conn) Close() {
211 | l.messageMutex.Lock()
212 | defer l.messageMutex.Unlock()
213 |
214 | if l.setClosing() {
215 | l.Debug.Printf("Sending quit message and waiting for confirmation")
216 | l.chanMessage <- &messagePacket{Op: MessageQuit}
217 | <-l.chanConfirm
218 | close(l.chanMessage)
219 |
220 | l.Debug.Printf("Closing network connection")
221 | if err := l.conn.Close(); err != nil {
222 | log.Println(err)
223 | }
224 |
225 | l.wgClose.Done()
226 | }
227 | l.wgClose.Wait()
228 | }
229 |
230 | // SetTimeout sets the time after a request is sent that a MessageTimeout triggers
231 | func (l *Conn) SetTimeout(timeout time.Duration) {
232 | if timeout > 0 {
233 | atomic.StoreInt64(&l.requestTimeout, int64(timeout))
234 | }
235 | }
236 |
237 | // Returns the next available messageID
238 | func (l *Conn) nextMessageID() int64 {
239 | if messageID, ok := <-l.chanMessageID; ok {
240 | return messageID
241 | }
242 | return 0
243 | }
244 |
245 | // StartTLS sends the command to start a TLS session and then creates a new TLS Client
246 | func (l *Conn) StartTLS(config *tls.Config) error {
247 | if l.isTLS {
248 | return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
249 | }
250 |
251 | packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
252 | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
253 | request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
254 | request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
255 | packet.AppendChild(request)
256 | l.Debug.PrintPacket(packet)
257 |
258 | msgCtx, err := l.sendMessageWithFlags(packet, startTLS)
259 | if err != nil {
260 | return err
261 | }
262 | defer l.finishMessage(msgCtx)
263 |
264 | l.Debug.Printf("%d: waiting for response", msgCtx.id)
265 |
266 | packetResponse, ok := <-msgCtx.responses
267 | if !ok {
268 | return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
269 | }
270 | packet, err = packetResponse.ReadPacket()
271 | l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
272 | if err != nil {
273 | return err
274 | }
275 |
276 | if l.Debug {
277 | if err := addLDAPDescriptions(packet); err != nil {
278 | l.Close()
279 | return err
280 | }
281 | ber.PrintPacket(packet)
282 | }
283 |
284 | if err := GetLDAPError(packet); err == nil {
285 | conn := tls.Client(l.conn, config)
286 |
287 | if connErr := conn.Handshake(); connErr != nil {
288 | l.Close()
289 | return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr))
290 | }
291 |
292 | l.isTLS = true
293 | l.conn = conn
294 | } else {
295 | return err
296 | }
297 | go l.reader()
298 |
299 | return nil
300 | }
301 |
302 | // TLSConnectionState returns the client's TLS connection state.
303 | // The return values are their zero values if StartTLS did
304 | // not succeed.
305 | func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) {
306 | tc, ok := l.conn.(*tls.Conn)
307 | if !ok {
308 | return
309 | }
310 | return tc.ConnectionState(), true
311 | }
312 |
313 | func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) {
314 | return l.sendMessageWithFlags(packet, 0)
315 | }
316 |
317 | func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) {
318 | if l.IsClosing() {
319 | return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
320 | }
321 | l.messageMutex.Lock()
322 | l.Debug.Printf("flags&startTLS = %d", flags&startTLS)
323 | if l.isStartingTLS {
324 | l.messageMutex.Unlock()
325 | return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase"))
326 | }
327 | if flags&startTLS != 0 {
328 | if l.outstandingRequests != 0 {
329 | l.messageMutex.Unlock()
330 | return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests"))
331 | }
332 | l.isStartingTLS = true
333 | }
334 | l.outstandingRequests++
335 |
336 | l.messageMutex.Unlock()
337 |
338 | responses := make(chan *PacketResponse)
339 | messageID := packet.Children[0].Value.(int64)
340 | message := &messagePacket{
341 | Op: MessageRequest,
342 | MessageID: messageID,
343 | Packet: packet,
344 | Context: &messageContext{
345 | id: messageID,
346 | done: make(chan struct{}),
347 | responses: responses,
348 | },
349 | }
350 | l.sendProcessMessage(message)
351 | return message.Context, nil
352 | }
353 |
354 | func (l *Conn) finishMessage(msgCtx *messageContext) {
355 | close(msgCtx.done)
356 |
357 | if l.IsClosing() {
358 | return
359 | }
360 |
361 | l.messageMutex.Lock()
362 | l.outstandingRequests--
363 | if l.isStartingTLS {
364 | l.isStartingTLS = false
365 | }
366 | l.messageMutex.Unlock()
367 |
368 | message := &messagePacket{
369 | Op: MessageFinish,
370 | MessageID: msgCtx.id,
371 | }
372 | l.sendProcessMessage(message)
373 | }
374 |
375 | func (l *Conn) sendProcessMessage(message *messagePacket) bool {
376 | l.messageMutex.Lock()
377 | defer l.messageMutex.Unlock()
378 | if l.IsClosing() {
379 | return false
380 | }
381 | l.chanMessage <- message
382 | return true
383 | }
384 |
385 | func (l *Conn) processMessages() {
386 | defer func() {
387 | if err := recover(); err != nil {
388 | log.Printf("ldap: recovered panic in processMessages: %v", err)
389 | }
390 | for messageID, msgCtx := range l.messageContexts {
391 | // If we are closing due to an error, inform anyone who
392 | // is waiting about the error.
393 | if l.IsClosing() && l.closeErr.Load() != nil {
394 | msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)})
395 | }
396 | l.Debug.Printf("Closing channel for MessageID %d", messageID)
397 | close(msgCtx.responses)
398 | delete(l.messageContexts, messageID)
399 | }
400 | close(l.chanMessageID)
401 | close(l.chanConfirm)
402 | }()
403 |
404 | var messageID int64 = 1
405 | for {
406 | select {
407 | case l.chanMessageID <- messageID:
408 | messageID++
409 | case message := <-l.chanMessage:
410 | switch message.Op {
411 | case MessageQuit:
412 | l.Debug.Printf("Shutting down - quit message received")
413 | return
414 | case MessageRequest:
415 | // Add to message list and write to network
416 | l.Debug.Printf("Sending message %d", message.MessageID)
417 |
418 | buf := message.Packet.Bytes()
419 | _, err := l.conn.Write(buf)
420 | if err != nil {
421 | l.Debug.Printf("Error Sending Message: %s", err.Error())
422 | message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)})
423 | close(message.Context.responses)
424 | break
425 | }
426 |
427 | // Only add to messageContexts if we were able to
428 | // successfully write the message.
429 | l.messageContexts[message.MessageID] = message.Context
430 |
431 | // Add timeout if defined
432 | requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout))
433 | if requestTimeout > 0 {
434 | go func() {
435 | defer func() {
436 | if err := recover(); err != nil {
437 | log.Printf("ldap: recovered panic in RequestTimeout: %v", err)
438 | }
439 | }()
440 | time.Sleep(requestTimeout)
441 | timeoutMessage := &messagePacket{
442 | Op: MessageTimeout,
443 | MessageID: message.MessageID,
444 | }
445 | l.sendProcessMessage(timeoutMessage)
446 | }()
447 | }
448 | case MessageResponse:
449 | l.Debug.Printf("Receiving message %d", message.MessageID)
450 | if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
451 | msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
452 | } else {
453 | log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
454 | ber.PrintPacket(message.Packet)
455 | }
456 | case MessageTimeout:
457 | // Handle the timeout by closing the channel
458 | // All reads will return immediately
459 | if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
460 | l.Debug.Printf("Receiving message timeout for %d", message.MessageID)
461 | msgCtx.sendResponse(&PacketResponse{message.Packet, errors.New("ldap: connection timed out")})
462 | delete(l.messageContexts, message.MessageID)
463 | close(msgCtx.responses)
464 | }
465 | case MessageFinish:
466 | l.Debug.Printf("Finished message %d", message.MessageID)
467 | if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
468 | delete(l.messageContexts, message.MessageID)
469 | close(msgCtx.responses)
470 | }
471 | }
472 | }
473 | }
474 | }
475 |
476 | func (l *Conn) reader() {
477 | cleanstop := false
478 | defer func() {
479 | if err := recover(); err != nil {
480 | log.Printf("ldap: recovered panic in reader: %v", err)
481 | }
482 | if !cleanstop {
483 | l.Close()
484 | }
485 | }()
486 |
487 | for {
488 | if cleanstop {
489 | l.Debug.Printf("reader clean stopping (without closing the connection)")
490 | return
491 | }
492 | packet, err := ber.ReadPacket(l.conn)
493 | if err != nil {
494 | // A read error is expected here if we are closing the connection...
495 | if !l.IsClosing() {
496 | l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err))
497 | l.Debug.Printf("reader error: %s", err)
498 | }
499 | return
500 | }
501 | if err := addLDAPDescriptions(packet); err != nil {
502 | l.Debug.Printf("descriptions error: %s", err)
503 | }
504 | if len(packet.Children) == 0 {
505 | l.Debug.Printf("Received bad ldap packet")
506 | continue
507 | }
508 | l.messageMutex.Lock()
509 | if l.isStartingTLS {
510 | cleanstop = true
511 | }
512 | l.messageMutex.Unlock()
513 | message := &messagePacket{
514 | Op: MessageResponse,
515 | MessageID: packet.Children[0].Value.(int64),
516 | Packet: packet,
517 | }
518 | if !l.sendProcessMessage(message) {
519 | return
520 | }
521 | }
522 | }
523 |
--------------------------------------------------------------------------------