├── 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 | [![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](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 | [![GoDoc](https://godoc.org/github.com/go-ldap/ldap?status.svg)](https://godoc.org/github.com/go-ldap/ldap) 2 | [![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](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 | --------------------------------------------------------------------------------