Compare commits

...

15 commits

Author SHA1 Message Date
1d338a9cc1 fix consul version in ci 2024-03-20 17:02:45 +00:00
e9bf550e02 Woodpecker CI 2024-03-20 17:02:45 +00:00
55ef7530ae cleaned Docker process 2023-03-27 10:23:32 +02:00
a2be316d91
use legacy ssha algorithm, new one is incompatible 2023-03-26 09:41:22 +02:00
9cab98d2ce
Set correct package path 2022-12-01 22:43:06 +01:00
de71260bbf
Make repo a Nix flake 2022-12-01 22:40:13 +01:00
0ef8313a67
Add Nix packaging 2022-07-24 09:47:31 +02:00
Simon Beck
9ce0d22c99 Fix wrong handling of multi value attributes
While ldapsearch doesn't seem to mind, apps like keycloak seem to have
issues with adding multiple attributes with different values. While
the resulting ldif in ldapsearch is indistinguishable there seems to
be a slight different on the protocol level.

If adding multiple attributes with the same name and different values,
keycloak will only see the last entry. But adding a single attribute
a slice of values is seems to handle it correctly.
2022-02-14 12:13:31 +01:00
Simon Beck
f05e41c9aa Improve password hash handling
This adds support for more hash algorithms. Also a stored password will
be updated to SSHA512 upon a successful bind. It will also automatically
hash a cleartext password if the `userpassword` field is modified with
a cleartext one.

Hashes supported:
* SSHA
* SSHA256
* SSHA512
2022-02-10 20:51:01 +01:00
dbd9003714
Disable reconnect in tests + some cosmetic changes 2021-09-16 13:52:46 +02:00
a08be6b395
Patch ASN.1 BER encoding of integers and length + unit tests 2021-09-16 13:51:43 +02:00
2a844bd559
Encoding errors must be logged 2021-09-16 13:47:09 +02:00
2707dd77c5
Use vendored goldap 2021-09-16 13:46:18 +02:00
477d7014ed
Vendor goldap 2021-09-16 13:41:01 +02:00
MrArmonius
a53641e773 Correct the function GenerateName
The problem was the encode in `name += string(alphabet[])`
It takes only 1 byte but the characters like 'è','@' are encoding
on several bytes (1 to 4 bytes).
The better solution was to create a slice of string, like this
we don't have problem about take only one byte instead of 2,3 or 4
bytes.
2021-07-26 15:36:45 +02:00
100 changed files with 12461 additions and 89 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@ bottin
bottin.static bottin.static
config.json config.json
test/test test/test
result
ldap.json

View file

@ -1,7 +1,3 @@
---
kind: pipeline
name: bottin
steps: steps:
- name: build - name: build
image: golang:stretch image: golang:stretch
@ -12,14 +8,8 @@ steps:
- go test -i -c -o test - go test -i -c -o test
- name: test_bottin - name: test_bottin
image: consul:latest image: consul:1.15.4
environment: environment:
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
commands: commands:
- ash test/runner.sh - ash test/runner.sh
---
kind: signature
hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed
...

View file

@ -1,5 +0,0 @@
FROM scratch
ADD bottin.static /bottin
ENTRYPOINT ["/bottin"]

View file

@ -1,6 +1,6 @@
# Bottin # Bottin
[![Build Status](https://drone.deuxfleurs.fr/api/badges/Deuxfleurs/bottin/status.svg?ref=refs/heads/main)](https://drone.deuxfleurs.fr/Deuxfleurs/bottin) [![status-badge](https://woodpecker.deuxfleurs.fr/api/badges/38/status.svg)](https://woodpecker.deuxfleurs.fr/repos/38)
<img src="https://git.deuxfleurs.fr/Deuxfleurs/bottin/raw/branch/main/bottin.png" style="height: 150px; display: block; margin-left: auto; margin-right: auto" /> <img src="https://git.deuxfleurs.fr/Deuxfleurs/bottin/raw/branch/main/bottin.png" style="height: 150px; display: block; margin-left: auto; margin-right: auto" />
@ -24,7 +24,7 @@ Features:
- Access control through an ACL (hardcoded in the configuration file) - Access control through an ACL (hardcoded in the configuration file)
A Docker image is provided on the [Docker hub](https://hub.docker.com/r/lxpz/bottin_amd64). A Docker image is provided on the [Docker hub](https://hub.docker.com/r/dxflrs/bottin) (built in `default.nix`).
An example for running Bottin on a Nomad cluster can be found in `bottin.hcl.example`. An example for running Bottin on a Nomad cluster can be found in `bottin.hcl.example`.
Bottin takes a single command line argument, `-config <filename>`, which is the Bottin takes a single command line argument, `-config <filename>`, which is the
@ -44,6 +44,17 @@ Bottin requires go 1.13 or later.
To build Bottin, clone this repository outside of your `$GOPATH`. To build Bottin, clone this repository outside of your `$GOPATH`.
Then, run `make` in the root of the repo. Then, run `make` in the root of the repo.
## Releasing
```bash
nix-build -A bin
nix-build -A docker
```
```bash
docker load < $(nix-build -A docker)
docker push dxflrs/bottin:???
```
## Server initialization ## Server initialization

55
default.nix Normal file
View file

@ -0,0 +1,55 @@
let
pkgsSrc = fetchTarball {
# As of 2022-07-19
url = "https://github.com/NixOS/nixpkgs/archive/d2db10786f27619d5519b12b03fb10dc8ca95e59.tar.gz";
sha256 = "0s9gigs3ylnq5b94rfcmxvrmmr3kzhs497gksajf638d5bv7zcl5";
};
gomod2nix = fetchGit {
url = "https://github.com/tweag/gomod2nix.git";
ref = "master";
rev = "40d32f82fc60d66402eb0972e6e368aeab3faf58";
};
pkgs = import pkgsSrc {
overlays = [
(self: super: {
gomod = super.callPackage "${gomod2nix}/builder/" { };
})
];
};
in rec {
bin = pkgs.gomod.buildGoApplication {
pname = "bottin-bin";
version = "0.1.0";
src = builtins.filterSource
(path: type: (builtins.match ".*/test/.*\\.(go|sum|mod)" path) == null)
./.;
modules = ./gomod2nix.toml;
CGO_ENABLED=0;
meta = with pkgs.lib; {
description = "A cloud-native LDAP server backed by a Consul datastore";
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/bottin";
license = licenses.gpl3Plus;
platforms = platforms.linux;
};
};
pkg = pkgs.stdenv.mkDerivation {
pname = "bottin";
version = "0.1.0";
unpackPhase = "true";
installPhase = ''
mkdir -p $out/
cp ${bin}/bin/bottin $out/bottin
'';
};
docker = pkgs.dockerTools.buildImage {
name = "dxflrs/bottin";
config = {
Entrypoint = [ "${pkg}/bottin" ];
WorkingDir = "/";
};
};
}

79
flake.lock Normal file
View file

@ -0,0 +1,79 @@
{
"nodes": {
"gomod2nix": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
},
"locked": {
"lastModified": 1655245309,
"narHash": "sha256-d/YPoQ/vFn1+GTmSdvbSBSTOai61FONxB4+Lt6w/IVI=",
"owner": "tweag",
"repo": "gomod2nix",
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
"type": "github"
},
"original": {
"owner": "tweag",
"repo": "gomod2nix",
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1653581809,
"narHash": "sha256-Uvka0V5MTGbeOfWte25+tfRL3moECDh1VwokWSZUdoY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "83658b28fe638a170a19b8933aa008b30640fbd1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1669764884,
"narHash": "sha256-1qWR/5+WtqxSedrFbUbM3zPMO7Ec2CGWaxtK4z4DdvY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0244e143dc943bcf661fdaf581f01eb0f5000fcf",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0244e143dc943bcf661fdaf581f01eb0f5000fcf",
"type": "github"
}
},
"root": {
"inputs": {
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs_2"
}
},
"utils": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

39
flake.nix Normal file
View file

@ -0,0 +1,39 @@
{
description = "A cloud-native LDAP server backed by a Consul datastore";
inputs.nixpkgs.url = "github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
inputs.gomod2nix.url = "github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
outputs = { self, nixpkgs, gomod2nix }:
let
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [
(self: super: {
gomod = super.callPackage "${gomod2nix}/builder/" { };
})
];
};
bottin = pkgs.gomod.buildGoApplication {
pname = "bottin";
version = "0.1.0";
src = builtins.filterSource
(path: type: (builtins.match ".*/test/.*\\.(go|sum|mod)" path) == null)
./.;
modules = ./gomod2nix.toml;
CGO_ENABLED=0;
meta = with pkgs.lib; {
description = "A cloud-native LDAP server backed by a Consul datastore";
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/bottin";
license = licenses.gpl3Plus;
platforms = platforms.linux;
};
};
in
{
packages.x86_64-linux.bottin = bottin;
packages.x86_64-linux.default = self.packages.x86_64-linux.bottin;
};
}

4
go.mod
View file

@ -3,9 +3,9 @@ module bottin
go 1.13 go 1.13
require ( require (
github.com/go-ldap/ldap/v3 v3.3.0
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/hashicorp/consul/api v1.3.0 github.com/hashicorp/consul/api v1.3.0
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
) )

14
go.sum
View file

@ -1,5 +1,3 @@
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@ -9,11 +7,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap v2.5.1+incompatible h1:Opaoft5zMW8IU/VRULB0eGMBQ9P5buRvCW6sFTRmMn8=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@ -49,10 +42,10 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73 h1:ZhC4QngptYaGx53+ph1RjxcH8fkCozBaY+935TNX4i8=
github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73/go.mod h1:t0Q9JvoMTfTYdAWIk2MF69iz+Qpdk9D+PgVu6fVmaDI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
@ -84,14 +77,13 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

25
goldap/abandon_request.go Normal file
View file

@ -0,0 +1,25 @@
package message
import "fmt"
//
// AbandonRequest ::= [APPLICATION 16] MessageID
func readAbandonRequest(bytes *Bytes) (ret AbandonRequest, err error) {
var mes MessageID
mes, err = readTaggedMessageID(bytes, classApplication, TagAbandonRequest)
if err != nil {
err = LdapError{fmt.Sprintf("readAbandonRequest:\n%s", err.Error())}
return
}
ret = AbandonRequest(mes)
return
}
func (abandon AbandonRequest) size() int {
return MessageID(abandon).sizeTagged(TagAbandonRequest)
}
func (abandon AbandonRequest) write(bytes *Bytes) int {
return MessageID(abandon).writeTagged(bytes, classApplication, TagAbandonRequest)
}

53
goldap/add_request.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// AddRequest ::= [APPLICATION 8] SEQUENCE {
// entry LDAPDN,
// attributes AttributeList }
func (add *AddRequest) Entry() LDAPDN {
return add.entry
}
func (add *AddRequest) Attributes() AttributeList {
return add.attributes
}
func readAddRequest(bytes *Bytes) (ret AddRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagAddRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAddRequest:\n%s", err.Error())}
return
}
return
}
func (add *AddRequest) readComponents(bytes *Bytes) (err error) {
add.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
add.attributes, err = readAttributeList(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (add AddRequest) size() (size int) {
size += add.entry.size()
size += add.attributes.size()
size += sizeTagAndLength(TagAddRequest, size)
return
}
func (add AddRequest) write(bytes *Bytes) (size int) {
size += add.attributes.write(bytes)
size += add.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagAddRequest, size)
return
}

28
goldap/add_response.go Normal file
View file

@ -0,0 +1,28 @@
package message
import "fmt"
//
// AddResponse ::= [APPLICATION 9] LDAPResult
func (l *AddResponse) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}
func readAddResponse(bytes *Bytes) (ret AddResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagAddResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readAddResponse:\n%s", err.Error())}
return
}
ret = AddResponse(res)
return
}
func (a AddResponse) size() int {
return LDAPResult(a).sizeTagged(TagAddResponse)
}
func (a AddResponse) write(bytes *Bytes) int {
return LDAPResult(a).writeTagged(bytes, classApplication, TagAddResponse)
}

760
goldap/asn1.go Normal file
View file

@ -0,0 +1,760 @@
package message
// Below code is largely inspired from the standard golang library encoding/asn
// If put BEGIN / END tags in the comments to give the original library name
import (
// "errors"
"fmt"
"math/big"
// "strconv"
// "time"
)
//
// BEGIN: encoding/asn1/common.go
//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
const (
tagBoolean = 1
tagInteger = 2
// tagBitString = 3
tagOctetString = 4
// tagOID = 6
tagEnum = 10
// tagUTF8String = 12
tagSequence = 16
tagSet = 17
// tagPrintableString = 19
// tagT61String = 20
// tagIA5String = 22
// tagUTCTime = 23
// tagGeneralizedTime = 24
tagGeneralString = 27
)
var tagNames = map[int]string{
tagBoolean: "BOOLEAN",
tagInteger: "INTEGER",
tagOctetString: "OCTET STRING",
tagEnum: "ENUM",
tagSequence: "SEQUENCE",
tagSet: "SET",
}
const (
classUniversal = 0
classApplication = 1
classContextSpecific = 2
// classPrivate = 3
)
var classNames = map[int]string{
classUniversal: "UNIVERSAL",
classApplication: "APPLICATION",
classContextSpecific: "CONTEXT SPECIFIC",
}
const (
isCompound = true
isNotCompound = false
)
var compoundNames = map[bool]string{
isCompound: "COMPOUND",
isNotCompound: "NOT COMPOUND",
}
type TagAndLength struct {
Class, Tag, Length int
IsCompound bool
}
//
// END: encoding/asn1/common.go
//
func (t *TagAndLength) Expect(class int, tag int, isCompound bool) (err error) {
err = t.ExpectClass(class)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
err = t.ExpectTag(tag)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
err = t.ExpectCompound(isCompound)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
return
}
func (t *TagAndLength) ExpectClass(class int) (err error) {
if class != t.Class {
err = SyntaxError{fmt.Sprintf("ExpectClass: wrong tag class: got %d (%s), expected %d (%s)", t.Class, classNames[t.Class], class, classNames[class])}
}
return
}
func (t *TagAndLength) ExpectTag(tag int) (err error) {
if tag != t.Tag {
err = SyntaxError{fmt.Sprintf("ExpectTag: wrong tag value: got %d (%s), expected %d (%s)", t.Tag, tagNames[t.Tag], tag, tagNames[tag])}
}
return
}
func (t *TagAndLength) ExpectCompound(isCompound bool) (err error) {
if isCompound != t.IsCompound {
err = SyntaxError{fmt.Sprintf("ExpectCompound: wrong tag compound: got %t (%s), expected %t (%s)", t.IsCompound, compoundNames[t.IsCompound], isCompound, compoundNames[isCompound])}
}
return
}
func ParseTagAndLength(bytes []byte, initOffset int) (ret TagAndLength, offset int, err error) {
ret, offset, err = parseTagAndLength(bytes, initOffset)
return
}
//
// BEGIN encoding/asn1/asn1.go
//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package asn1 implements parsing of DER-encoded ASN.1 data structures,
// as defined in ITU-T Rec X.690.
//
// See also ``A Layman's Guide to a Subset of ASN.1, BER, and DER,''
// http://luca.ntop.org/Teaching/Appunti/asn1.html.
// package asn1
// ASN.1 is a syntax for specifying abstract objects and BER, DER, PER, XER etc
// are different encoding formats for those objects. Here, we'll be dealing
// with DER, the Distinguished Encoding Rules. DER is used in X.509 because
// it's fast to parse and, unlike BER, has a unique encoding for every object.
// When calculating hashes over objects, it's important that the resulting
// bytes be the same at both ends and DER removes this margin of error.
//
// ASN.1 is very complex and this package doesn't attempt to implement
// everything by any means.
//import (
// "fmt"
// "math/big"
// "reflect"
// "strconv"
// "time"
//)
// A StructuralError suggests that the ASN.1 data is valid, but the Go type
// which is receiving it doesn't match.
type StructuralError struct {
Msg string
}
func (e StructuralError) Error() string { return "asn1: structure error: " + e.Msg }
// A SyntaxError suggests that the ASN.1 data is invalid.
type SyntaxError struct {
Msg string
}
func (e SyntaxError) Error() string { return "asn1: syntax error: " + e.Msg }
// We start by dealing with each of the primitive types in turn.
// BOOLEAN
func parseBool(bytes []byte) (ret bool, err error) {
if len(bytes) > 1 {
err = SyntaxError{"invalid boolean: should be encoded on one byte only"}
return
} else if len(bytes) == 0 {
err = SyntaxError{"invalid boolean: no data to read"}
}
// DER demands that "If the encoding represents the boolean value TRUE,
// its single contents octet shall have all eight bits set to one."
// Thus only 0 and 255 are valid encoded values.
switch bytes[0] {
case 0:
ret = false
case 0xff:
ret = true
default:
err = SyntaxError{"invalid boolean: should be 0x00 of 0xFF"}
}
return
}
func sizeBool(b bool) int {
return 1
}
func writeBool(bytes *Bytes, b bool) int {
if b == false {
return bytes.writeBytes([]byte{0x00})
} else {
return bytes.writeBytes([]byte{0xff})
}
}
// INTEGER
// parseInt64 treats the given bytes as a big-endian, signed integer and
// returns the result.
func parseInt64(bytes []byte) (ret int64, err error) {
if len(bytes) > 8 {
// We'll overflow an int64 in this case.
err = StructuralError{"integer too large"}
return
}
for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
ret <<= 8
ret |= int64(bytes[bytesRead])
}
// Shift up and down in order to sign extend the result.
ret <<= 64 - uint8(len(bytes))*8
ret >>= 64 - uint8(len(bytes))*8
return
}
func sizeInt64(i int64) int {
n := 1
for i > 127 {
n++
i >>= 8
}
for i < -128 {
n++
i >>= 8
}
return n
}
func writeInt64(bytes *Bytes, i int64) int {
n := sizeInt64(i)
buf := [8]byte{}
for j := 0; j < n; j++ {
b := i >> uint((n-1-j)*8)
buf[j] = byte(b)
}
bytes.writeBytes(buf[:n])
return n
}
// parseInt treats the given bytes as a big-endian, signed integer and returns
// the result.
func parseInt32(bytes []byte) (int32, error) {
ret64, err := parseInt64(bytes)
if err != nil {
return 0, err
}
if ret64 != int64(int32(ret64)) {
return 0, StructuralError{"integer too large"}
}
return int32(ret64), nil
}
func sizeInt32(i int32) int {
return sizeInt64(int64(i))
}
func writeInt32(bytes *Bytes, i int32) int {
return writeInt64(bytes, int64(i))
}
var bigOne = big.NewInt(1)
// // parseBigInt treats the given bytes as a big-endian, signed integer and returns
// // the result.
// func parseBigInt(bytes []byte) *big.Int {
// ret := new(big.Int)
// if len(bytes) > 0 && bytes[0]&0x80 == 0x80 {
// // This is a negative number.
// notBytes := make([]byte, len(bytes))
// for i := range notBytes {
// notBytes[i] = ^bytes[i]
// }
// ret.SetBytes(notBytes)
// ret.Add(ret, bigOne)
// ret.Neg(ret)
// return ret
// }
// ret.SetBytes(bytes)
// return ret
// }
// // BIT STRING
// // BitString is the structure to use when you want an ASN.1 BIT STRING type. A
// // bit string is padded up to the nearest byte in memory and the number of
// // valid bits is recorded. Padding bits will be zero.
// type BitString struct {
// Bytes []byte // bits packed into bytes.
// BitLength int // length in bits.
// }
// // At returns the bit at the given index. If the index is out of range it
// // returns false.
// func (b BitString) At(i int) int {
// if i < 0 || i >= b.BitLength {
// return 0
// }
// x := i / 8
// y := 7 - uint(i%8)
// return int(b.Bytes[x]>>y) & 1
// }
// // RightAlign returns a slice where the padding bits are at the beginning. The
// // slice may share memory with the BitString.
// func (b BitString) RightAlign() []byte {
// shift := uint(8 - (b.BitLength % 8))
// if shift == 8 || len(b.Bytes) == 0 {
// return b.Bytes
// }
// a := make([]byte, len(b.Bytes))
// a[0] = b.Bytes[0] >> shift
// for i := 1; i < len(b.Bytes); i++ {
// a[i] = b.Bytes[i-1] << (8 - shift)
// a[i] |= b.Bytes[i] >> shift
// }
// return a
// }
// // parseBitString parses an ASN.1 bit string from the given byte slice and returns it.
// func parseBitString(bytes []byte) (ret BitString, err error) {
// if len(bytes) == 0 {
// err = SyntaxError{"zero length BIT STRING"}
// return
// }
// paddingBits := int(bytes[0])
// if paddingBits > 7 ||
// len(bytes) == 1 && paddingBits > 0 ||
// bytes[len(bytes)-1]&((1<<bytes[0])-1) != 0 {
// err = SyntaxError{"invalid padding bits in BIT STRING"}
// return
// }
// ret.BitLength = (len(bytes)-1)*8 - paddingBits
// ret.Bytes = bytes[1:]
// return
// }
// OBJECT IDENTIFIER
// An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.
// type ObjectIdentifier []int
// // Equal reports whether oi and other represent the same identifier.
// func (oi ObjectIdentifier) Equal(other ObjectIdentifier) bool {
// if len(oi) != len(other) {
// return false
// }
// for i := 0; i < len(oi); i++ {
// if oi[i] != other[i] {
// return false
// }
// }
// return true
// }
// func (oi ObjectIdentifier) String() string {
// var s string
// for i, v := range oi {
// if i > 0 {
// s += "."
// }
// s += strconv.Itoa(v)
// }
// return s
// }
// // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and
// // returns it. An object identifier is a sequence of variable length integers
// // that are assigned in a hierarchy.
// func parseObjectIdentifier(bytes []byte) (s []int, err error) {
// if len(bytes) == 0 {
// err = SyntaxError{"zero length OBJECT IDENTIFIER"}
// return
// }
// // In the worst case, we get two elements from the first byte (which is
// // encoded differently) and then every varint is a single byte long.
// s = make([]int, len(bytes)+1)
// // The first varint is 40*value1 + value2:
// // According to this packing, value1 can take the values 0, 1 and 2 only.
// // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
// // then there are no restrictions on value2.
// v, offset, err := parseBase128Int(bytes, 0)
// if err != nil {
// return
// }
// if v < 80 {
// s[0] = v / 40
// s[1] = v % 40
// } else {
// s[0] = 2
// s[1] = v - 80
// }
// i := 2
// for ; offset < len(bytes); i++ {
// v, offset, err = parseBase128Int(bytes, offset)
// if err != nil {
// return
// }
// s[i] = v
// }
// s = s[0:i]
// return
// }
// ENUMERATED
// An Enumerated is represented as a plain int.
type Enumerated int
// FLAG
// A Flag accepts any data and is set to true if present.
type Flag bool
// parseBase128Int parses a base-128 encoded int from the given offset in the
// given byte slice. It returns the value and the new offset.
func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) {
offset = initOffset
for shifted := 0; offset < len(bytes); shifted++ {
if shifted > 4 {
err = StructuralError{"base 128 integer too large"}
return
}
ret <<= 7
b := bytes[offset]
ret |= int(b & 0x7f)
offset++
if b&0x80 == 0 {
return
}
}
err = SyntaxError{"truncated base 128 integer"}
return
}
func sizeBase128Int(value int) (size int) {
for i := value; i > 0; i >>= 7 {
size++
}
return
}
// Write start as the end of the slice and goes back
// We assume we have enough size
func writeBase128Int(bytes *Bytes, value int) (size int) {
for ; value > 0 || size == 0; value >>= 7 { // Write at least one byte even if the value is 0
// Get the 7 lowest bits
b := byte(value) & 0x7f
if value < 128 {
b |= 0x80
}
bytes.writeBytes([]byte{b})
size++
}
return
}
// // UTCTime
// func parseUTCTime(bytes []byte) (ret time.Time, err error) {
// s := string(bytes)
// ret, err = time.Parse("0601021504Z0700", s)
// if err != nil {
// ret, err = time.Parse("060102150405Z0700", s)
// }
// if err == nil && ret.Year() >= 2050 {
// // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
// ret = ret.AddDate(-100, 0, 0)
// }
// return
// }
// // parseGeneralizedTime parses the GeneralizedTime from the given byte slice
// // and returns the resulting time.
// func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) {
// return time.Parse("20060102150405Z0700", string(bytes))
// }
// // PrintableString
// // parsePrintableString parses a ASN.1 PrintableString from the given byte
// // array and returns it.
// func parsePrintableString(bytes []byte) (ret string, err error) {
// for _, b := range bytes {
// if !isPrintable(b) {
// err = SyntaxError{"PrintableString contains invalid character"}
// return
// }
// }
// ret = string(bytes)
// return
// }
// // isPrintable returns true iff the given b is in the ASN.1 PrintableString set.
// func isPrintable(b byte) bool {
// return 'a' <= b && b <= 'z' ||
// 'A' <= b && b <= 'Z' ||
// '0' <= b && b <= '9' ||
// '\'' <= b && b <= ')' ||
// '+' <= b && b <= '/' ||
// b == ' ' ||
// b == ':' ||
// b == '=' ||
// b == '?' ||
// // This is technically not allowed in a PrintableString.
// // However, x509 certificates with wildcard strings don't
// // always use the correct string type so we permit it.
// b == '*'
// }
// // IA5String
// // parseIA5String parses a ASN.1 IA5String (ASCII string) from the given
// // byte slice and returns it.
// func parseIA5String(bytes []byte) (ret string, err error) {
// for _, b := range bytes {
// if b >= 0x80 {
// err = SyntaxError{"IA5String contains invalid character"}
// return
// }
// }
// ret = string(bytes)
// return
// }
// // T61String
// // parseT61String parses a ASN.1 T61String (8-bit clean string) from the given
// // byte slice and returns it.
// func parseT61String(bytes []byte) (ret string, err error) {
// return string(bytes), nil
// }
// UTF8String
// parseUTF8String parses a ASN.1 UTF8String (raw UTF-8) from the given byte
// array and returns it.
// func parseUTF8String(bytes []byte) (ret string, err error) {
// return string(bytes), nil
// }
// func sizeUTF8String(s string) int {
// return len(s)
// }
// func writeUTF8String(bytes *Bytes, s string) int {
// return bytes.writeString(s)
// }
// Octet string
func parseOctetString(bytes []byte) (ret []byte, err error) {
return bytes, nil
}
func sizeOctetString(s []byte) int {
return len(s)
}
func writeOctetString(bytes *Bytes, s []byte) int {
return bytes.writeBytes(s)
}
// A RawValue represents an undecoded ASN.1 object.
type RawValue struct {
Class, Tag int
IsCompound bool
Bytes []byte
FullBytes []byte // includes the tag and length
}
// RawContent is used to signal that the undecoded, DER data needs to be
// preserved for a struct. To use it, the first field of the struct must have
// this type. It's an error for any of the other fields to have this type.
type RawContent []byte
// Tagging
// parseTagAndLength parses an ASN.1 tag and length pair from the given offset
// into a byte slice. It returns the parsed data and the new offset. SET and
// SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we
// don't distinguish between ordered and unordered objects in this code.
func parseTagAndLength(bytes []byte, initOffset int) (ret TagAndLength, offset int, err error) {
offset = initOffset
b := bytes[offset]
offset++
ret.Class = int(b >> 6)
ret.IsCompound = b&0x20 == 0x20
ret.Tag = int(b & 0x1f)
// If the bottom five bits are set, then the tag number is actually base 128
// encoded afterwards
if ret.Tag == 0x1f {
ret.Tag, offset, err = parseBase128Int(bytes, offset)
if err != nil {
return
}
}
if offset >= len(bytes) {
err = SyntaxError{"truncated tag or length"}
return
}
b = bytes[offset]
offset++
if b&0x80 == 0 {
// The length is encoded in the bottom 7 bits.
ret.Length = int(b & 0x7f)
} else {
// Bottom 7 bits give the number of length bytes to follow.
numBytes := int(b & 0x7f)
if numBytes == 0 {
err = SyntaxError{"indefinite length found (not DER)"}
return
}
ret.Length = 0
for i := 0; i < numBytes; i++ {
if offset >= len(bytes) {
err = SyntaxError{"truncated tag or length"}
return
}
b = bytes[offset]
offset++
if ret.Length >= 1<<23 {
// We can't shift ret.length up without
// overflowing.
err = StructuralError{"length too large"}
return
}
ret.Length <<= 8
ret.Length |= int(b)
if ret.Length == 0 {
// DER requires that lengths be minimal.
err = StructuralError{"superfluous leading zeros in length"}
return
}
}
}
return
}
// func writeTagAndLength(out *forkableWriter, t tagAndLength) (err error) {
// b := uint8(t.class) << 6
// if t.isCompound {
// b |= 0x20
// }
// if t.tag >= 31 {
// b |= 0x1f
// err = out.WriteByte(b)
// if err != nil {
// return
// }
// err = marshalBase128Int(out, int64(t.tag))
// if err != nil {
// return
// }
// } else {
// b |= uint8(t.tag)
// err = out.WriteByte(b)
// if err != nil {
// return
// }
// }
// if t.length >= 128 {
// l := lengthLength(t.length)
// err = out.WriteByte(0x80 | byte(l))
// if err != nil {
// return
// }
// err = marshalLength(out, t.length)
// if err != nil {
// return
// }
// } else {
// err = out.WriteByte(byte(t.length))
// if err != nil {
// return
// }
// }
// return nil
// }
func sizeTagAndLength(tag int, length int) (size int) {
// Compute the size of the tag
size = 1
if tag >= 31 {
// Long-form identifier if the tag is greater than 30
// http://en.wikipedia.org/wiki/X.690#Identifier_tags_greater_than_30
size += sizeBase128Int(tag)
}
// Compute the size of the length using the definite form
// http://en.wikipedia.org/wiki/X.690#The_definite_form
size += 1
if length >= 128 {
size += 1
for length > 255 {
size++
length >>= 8
}
}
return
}
func writeTagAndLength(bytes *Bytes, t TagAndLength) (size int) {
// We are writing backward, so write the length bytes first
if t.Length < 0 {
panic("Can't have a negative length")
} else if t.Length >= 128 {
lengthBytes := 0
val := t.Length
for val > 0 {
lengthBytes++
bytes.writeBytes([]byte{byte(val & 0xff)})
val >>= 8
}
bytes.writeBytes([]byte{byte(0x80 | byte(lengthBytes))})
size += lengthBytes + 1
} else if t.Length < 128 {
size += bytes.writeBytes([]byte{byte(t.Length)})
}
// Then write the tag
b := uint8(t.Class) << 6
if t.IsCompound {
b |= 0x20
}
if t.Tag >= 31 {
b |= 0x1f
size += writeBase128Int(bytes, t.Tag)
} else {
b |= uint8(t.Tag)
}
size += bytes.writeBytes([]byte{byte(b)})
return
}
//
// END encoding/asn1/asn1.go
//

54
goldap/asn1_test.go Normal file
View file

@ -0,0 +1,54 @@
package message
import (
"testing"
"bytes"
)
func TestSizeInt64(t *testing.T) {
s := sizeInt64(0)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(127)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(128)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
s = sizeInt64(50000)
if s != 3 {
t.Errorf("computed size is %d, expected 3", s)
}
s = sizeInt64(-12345)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
}
func TestWriteInt64(t *testing.T) {
vtests := []int64{0, 127, 128, 50000, -12345}
expsize := []int{1, 1, 2, 3, 2}
expresult := [][]byte{{0x00}, {0x7F}, {0x00, 0x80}, {0x00, 0xc3, 0x50}, {0xcf, 0xc7}}
for idx, v := range vtests {
fs := sizeInt64(v)
b := NewBytes(fs, make([]byte, fs))
t.Log("computing", v)
s := writeInt64(b, v)
if s != expsize[idx] {
t.Errorf("computed size is %d, expected %d", s, expsize[idx])
}
if !bytes.Equal(b.Bytes(), expresult[idx]) {
t.Errorf("wrong computed bytes, got %v, expected %v", b.Bytes(), expresult[idx])
}
a, e := parseInt64(b.Bytes())
t.Log("parse", a, e)
}
}

44
goldap/assertion_value.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
//
// AssertionValue ::= OCTET STRING
func readAssertionValue(bytes *Bytes) (assertionvalue AssertionValue, err error) {
var octetstring OCTETSTRING
octetstring, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAssertionValue:\n%s", err.Error())}
return
}
assertionvalue = AssertionValue(octetstring)
return
}
func readTaggedAssertionValue(bytes *Bytes, class int, tag int) (assertionvalue AssertionValue, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAssertionValue:\n%s", err.Error())}
return
}
assertionvalue = AssertionValue(octetstring)
return
}
func (assertion AssertionValue) size() int {
return OCTETSTRING(assertion).size()
}
func (assertion AssertionValue) sizeTagged(tag int) int {
return OCTETSTRING(assertion).sizeTagged(tag)
}
func (assertion AssertionValue) write(bytes *Bytes) int {
return OCTETSTRING(assertion).write(bytes)
}
func (assertion AssertionValue) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(assertion).writeTagged(bytes, class, tag)
}

40
goldap/attribute.go Normal file
View file

@ -0,0 +1,40 @@
package message
import "fmt"
//
// Attribute ::= PartialAttribute(WITH COMPONENTS {
// ...,
// vals (SIZE(1..MAX))})
func (attribute *Attribute) Type_() AttributeDescription {
return attribute.type_
}
func (attribute *Attribute) Vals() []AttributeValue {
return attribute.vals
}
func readAttribute(bytes *Bytes) (ret Attribute, err error) {
var par PartialAttribute
par, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttribute:\n%s", err.Error())}
return
}
if len(par.vals) == 0 {
err = LdapError{"readAttribute: expecting at least one value"}
return
}
ret = Attribute(par)
return
}
func (attribute Attribute) size() (size int) {
return PartialAttribute(attribute).size()
}
func (attribute Attribute) write(bytes *Bytes) (size int) {
return PartialAttribute(attribute).write(bytes)
}

View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
func (description AttributeDescription) Pointer() *AttributeDescription { return &description }
func readAttributeDescription(bytes *Bytes) (ret AttributeDescription, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeDescription:\n%s", err.Error())}
return
}
// @TODO: check RFC4512
ret = AttributeDescription(ldapstring)
return
}
func readTaggedAttributeDescription(bytes *Bytes, class int, tag int) (ret AttributeDescription, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
// @TODO: check RFC4512
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAttributeDescription:\n%s", err.Error())}
return
}
ret = AttributeDescription(ldapstring)
return
}
func (description AttributeDescription) size() int {
return LDAPString(description).size()
}
func (description AttributeDescription) sizeTagged(tag int) int {
return LDAPString(description).sizeTagged(tag)
}
func (description AttributeDescription) write(bytes *Bytes) int {
return LDAPString(description).write(bytes)
}
func (description AttributeDescription) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(description).writeTagged(bytes, class, tag)
}

43
goldap/attribute_list.go Normal file
View file

@ -0,0 +1,43 @@
package message
import "fmt"
//
// AttributeList ::= SEQUENCE OF attribute Attribute
func readAttributeList(bytes *Bytes) (ret AttributeList, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeList:\n%s", err.Error())}
return
}
return
}
func (list *AttributeList) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var attr Attribute
attr, err = readAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*list = append(*list, attr)
}
return
}
func (list AttributeList) size() (size int) {
for _, att := range list {
size += att.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (list AttributeList) write(bytes *Bytes) (size int) {
for i := len(list) - 1; i >= 0; i-- {
size += list[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}

View file

@ -0,0 +1,46 @@
package message
import "fmt"
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
func readAttributeSelection(bytes *Bytes) (attributeSelection AttributeSelection, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, attributeSelection.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeSelection:\n%s", err.Error())}
return
}
return
}
func (selection *AttributeSelection) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
// @TOTO: check <attributeSelector> in Section 4.5.1.8
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*selection = append(*selection, ldapstring)
}
return
}
func (selection AttributeSelection) write(bytes *Bytes) (size int) {
for i := len(selection) - 1; i >= 0; i-- {
size += selection[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (selection AttributeSelection) size() (size int) {
for _, selector := range selection {
size += selector.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}

24
goldap/attribute_value.go Normal file
View file

@ -0,0 +1,24 @@
package message
import "fmt"
//
// AttributeValue ::= OCTET STRING
func readAttributeValue(bytes *Bytes) (ret AttributeValue, err error) {
octetstring, err := readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeValue:\n%s", err.Error())}
return
}
ret = AttributeValue(octetstring)
return
}
func (value AttributeValue) write(bytes *Bytes) int {
return OCTETSTRING(value).write(bytes)
}
func (value AttributeValue) size() int {
return OCTETSTRING(value).size()
}

View file

@ -0,0 +1,77 @@
package message
import "fmt"
//
// AttributeValueAssertion ::= SEQUENCE {
// attributeDesc AttributeDescription,
// assertionValue AssertionValue }
func (assertion *AttributeValueAssertion) AttributeDesc() AttributeDescription {
return assertion.attributeDesc
}
func (assertion *AttributeValueAssertion) AssertionValue() AssertionValue {
return assertion.assertionValue
}
func readAttributeValueAssertion(bytes *Bytes) (ret AttributeValueAssertion, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeValueAssertion:\n%s", err.Error())}
return
}
return
}
func readTaggedAttributeValueAssertion(bytes *Bytes, class int, tag int) (ret AttributeValueAssertion, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAttributeValueAssertion:\n%s", err.Error())}
return
}
return
}
func (assertion *AttributeValueAssertion) readComponents(bytes *Bytes) (err error) {
assertion.attributeDesc, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
assertion.assertionValue, err = readAssertionValue(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (assertion AttributeValueAssertion) write(bytes *Bytes) (size int) {
size += assertion.assertionValue.write(bytes)
size += assertion.attributeDesc.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (assertion AttributeValueAssertion) writeTagged(bytes *Bytes, class int, tag int) (size int) {
size += assertion.assertionValue.write(bytes)
size += assertion.attributeDesc.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
func (assertion AttributeValueAssertion) size() (size int) {
size += assertion.attributeDesc.size()
size += assertion.assertionValue.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (assertion AttributeValueAssertion) sizeTagged(tag int) (size int) {
size += assertion.attributeDesc.size()
size += assertion.assertionValue.size()
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,37 @@
package message
import "fmt"
//
// AuthenticationChoice ::= CHOICE {
// simple [0] OCTET STRING,
// -- 1 and 2 reserved
// sasl [3] SaslCredentials,
// ... }
func readAuthenticationChoice(bytes *Bytes) (ret AuthenticationChoice, err error) {
tagAndLength, err := bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
err = tagAndLength.ExpectClass(classContextSpecific)
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagAuthenticationChoiceSimple:
ret, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagAuthenticationChoiceSimple)
case TagAuthenticationChoiceSaslCredentials:
ret, err = readSaslCredentials(bytes)
default:
err = LdapError{fmt.Sprintf("readAuthenticationChoice: invalid tag value %d for AuthenticationChoice", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
return
}

93
goldap/bind_request.go Normal file
View file

@ -0,0 +1,93 @@
package message
import "fmt"
// BindRequest ::= [APPLICATION 0] SEQUENCE {
// version INTEGER (1 .. 127),
// name LDAPDN,
// authentication AuthenticationChoice }
func (request *BindRequest) Name() LDAPDN {
return request.name
}
func (request *BindRequest) Authentication() AuthenticationChoice {
return request.authentication
}
func (request *BindRequest) AuthenticationSimple() OCTETSTRING {
return request.Authentication().(OCTETSTRING)
}
func (request *BindRequest) AuthenticationChoice() string {
switch request.Authentication().(type) {
case OCTETSTRING:
return "simple"
case SaslCredentials:
return "sasl"
}
return ""
}
func readBindRequest(bytes *Bytes) (bindrequest BindRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagBindRequest, bindrequest.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readBindRequest:\n%s", err.Error())}
return
}
return
}
func (request *BindRequest) readComponents(bytes *Bytes) (err error) {
request.version, err = readINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if !(request.version >= BindRequestVersionMin && request.version <= BindRequestVersionMax) {
err = LdapError{fmt.Sprintf("readComponents: invalid version %d, must be between %d and %d", request.version, BindRequestVersionMin, BindRequestVersionMax)}
return
}
request.name, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
request.authentication, err = readAuthenticationChoice(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (request BindRequest) write(bytes *Bytes) (size int) {
switch request.authentication.(type) {
case OCTETSTRING:
size += request.authentication.(OCTETSTRING).writeTagged(bytes, classContextSpecific, TagAuthenticationChoiceSimple)
case SaslCredentials:
size += request.authentication.(SaslCredentials).writeTagged(bytes, classContextSpecific, TagAuthenticationChoiceSaslCredentials)
default:
panic(fmt.Sprintf("Unknown authentication choice: %#v", request.authentication))
}
size += request.name.write(bytes)
size += request.version.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagBindRequest, size)
return
}
func (request BindRequest) size() (size int) {
size += request.version.size()
size += request.name.size()
switch request.authentication.(type) {
case OCTETSTRING:
size += request.authentication.(OCTETSTRING).sizeTagged(TagAuthenticationChoiceSimple)
case SaslCredentials:
size += request.authentication.(SaslCredentials).sizeTagged(TagAuthenticationChoiceSaslCredentials)
default:
panic(fmt.Sprintf("Unknown authentication choice: %#v", request.authentication))
}
size += sizeTagAndLength(TagBindRequest, size)
return
}

56
goldap/bind_response.go Normal file
View file

@ -0,0 +1,56 @@
package message
import "fmt"
// BindResponse ::= [APPLICATION 1] SEQUENCE {
// COMPONENTS OF LDAPResult,
// serverSaslCreds [7] OCTET STRING OPTIONAL }
func readBindResponse(bytes *Bytes) (bindresponse BindResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagBindResponse, bindresponse.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readBindResponse:\n%s", err.Error())}
return
}
return
}
func (response *BindResponse) readComponents(bytes *Bytes) (err error) {
response.LDAPResult.readComponents(bytes)
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagBindResponseServerSaslCreds {
var serverSaslCreds OCTETSTRING
serverSaslCreds, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagBindResponseServerSaslCreds)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
response.serverSaslCreds = serverSaslCreds.Pointer()
}
}
return
}
func (response BindResponse) write(bytes *Bytes) (size int) {
if response.serverSaslCreds != nil {
size += response.serverSaslCreds.writeTagged(bytes, classContextSpecific, TagBindResponseServerSaslCreds)
}
size += response.LDAPResult.writeComponents(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagBindResponse, size)
return
}
func (response BindResponse) size() (size int) {
if response.serverSaslCreds != nil {
size += response.serverSaslCreds.sizeTagged(TagBindResponseServerSaslCreds)
}
size += response.LDAPResult.sizeComponents()
size += sizeTagAndLength(TagBindResponse, size)
return
}

62
goldap/boolean.go Normal file
View file

@ -0,0 +1,62 @@
package message
import "fmt"
func readBOOLEAN(bytes *Bytes) (ret BOOLEAN, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagBoolean, tagBoolean)
if err != nil {
err = LdapError{fmt.Sprintf("readBOOLEAN:\n%s", err.Error())}
return
}
ret = BOOLEAN(value.(bool))
return
}
func (boolean BOOLEAN) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagBoolean, boolean)
}
func (boolean BOOLEAN) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, boolean)
}
func readTaggedBOOLEAN(bytes *Bytes, class int, tag int) (ret BOOLEAN, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagBoolean)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedBOOLEAN:\n%s", err.Error())}
return
}
ret = BOOLEAN(value.(bool))
return
}
func SizePrimitiveSubBytes(tag int, value interface{}) (size int) {
switch value.(type) {
case BOOLEAN:
size = sizeBool(bool(value.(BOOLEAN)))
case INTEGER:
size = sizeInt32(int32(value.(INTEGER)))
case ENUMERATED:
size = sizeInt32(int32(value.(ENUMERATED)))
case OCTETSTRING:
size = sizeOctetString([]byte(string(value.(OCTETSTRING))))
default:
panic(fmt.Sprintf("SizePrimitiveSubBytes: invalid value type %v", value))
}
size += sizeTagAndLength(tag, size)
return
}
func (boolean BOOLEAN) size() int {
return SizePrimitiveSubBytes(tagBoolean, boolean)
}
func (boolean BOOLEAN) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, boolean)
}
func (boolean BOOLEAN) Bool() bool {
return bool(boolean)
}

199
goldap/bytes.go Normal file
View file

@ -0,0 +1,199 @@
package message
import (
"fmt"
)
type Bytes struct {
offset int
bytes []byte
}
func (bytes *Bytes) getBytes() []byte {
return bytes.bytes
}
func NewBytes(offset int, bytes []byte) (ret *Bytes) {
return &Bytes{offset: offset, bytes: bytes}
}
func (bytes Bytes) Debug() {
fmt.Printf("Offset: %d, Bytes: %+v\n", bytes.offset, bytes.bytes)
}
// Return a string with the hex dump of the bytes around the current offset
// The current offset byte is put in brackets
// Example: 0x01, [0x02], 0x03
func (bytes *Bytes) DumpCurrentBytes() (ret string) {
var strings [3]string
for i := -1; i <= 1; i++ {
if bytes.offset+i >= 0 && bytes.offset+i < len(bytes.bytes) {
strings[i+1] = fmt.Sprintf("%#x", bytes.bytes[bytes.offset+i])
}
}
ret = fmt.Sprintf("%s, [%s], %s", strings[0], strings[1], strings[2])
return
}
func (bytes *Bytes) HasMoreData() bool {
return bytes.offset < len(bytes.bytes)
}
func (bytes *Bytes) ParseTagAndLength() (ret TagAndLength, err error) {
var offset int
ret, offset, err = ParseTagAndLength(bytes.bytes, bytes.offset)
if err != nil {
err = LdapError{fmt.Sprintf("ParseTagAndLength: %s", err.Error())}
return
} else {
bytes.offset = offset
}
return
}
func (bytes *Bytes) ReadSubBytes(class int, tag int, callback func(bytes *Bytes) error) (err error) {
// Check tag
tagAndLength, err := bytes.ParseTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
}
err = tagAndLength.Expect(class, tag, isCompound)
if err != nil {
return LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
}
start := bytes.offset
end := bytes.offset + tagAndLength.Length
// Check we got enough bytes to process
if end > len(bytes.bytes) {
return LdapError{fmt.Sprintf("ReadSubBytes: data truncated: expecting %d bytes at offset %d", tagAndLength.Length, bytes.offset)}
}
// Process sub-bytes
subBytes := Bytes{offset: 0, bytes: bytes.bytes[start:end]}
err = callback(&subBytes)
if err != nil {
bytes.offset += subBytes.offset
err = LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
return
}
// Check we got no more bytes to process
if subBytes.HasMoreData() {
return LdapError{fmt.Sprintf("ReadSubBytes: data too long: %d more bytes to read at offset %d", end-bytes.offset, bytes.offset)}
}
// Move offset
bytes.offset = end
return
}
func SizeSubBytes(tag int, callback func() int) (size int) {
size = callback()
size += sizeTagAndLength(tag, size)
return
}
func (bytes *Bytes) WritePrimitiveSubBytes(class int, tag int, value interface{}) (size int) {
switch value.(type) {
case BOOLEAN:
size = writeBool(bytes, bool(value.(BOOLEAN)))
case INTEGER:
size = writeInt32(bytes, int32(value.(INTEGER)))
case ENUMERATED:
size = writeInt32(bytes, int32(value.(ENUMERATED)))
case OCTETSTRING:
size = writeOctetString(bytes, []byte(string(value.(OCTETSTRING))))
default:
panic(fmt.Sprintf("WritePrimitiveSubBytes: invalid value type %v", value))
}
size += bytes.WriteTagAndLength(class, isNotCompound, tag, size)
return
}
func (bytes *Bytes) WriteTagAndLength(class int, compound bool, tag int, length int) int {
return writeTagAndLength(bytes, TagAndLength{Class: class, IsCompound: compound, Tag: tag, Length: length})
}
func (bytes *Bytes) writeString(s string) (size int) {
size = len(s)
start := bytes.offset - size
if start < 0 {
panic("Not enough space for string")
}
copy(bytes.bytes[start:], s)
bytes.offset = start
return
}
func (bytes *Bytes) writeBytes(b []byte) (size int) {
size = len(b)
start := bytes.offset - size
if start < 0 {
panic("Not enough space for bytes")
}
copy(bytes.bytes[start:], b)
bytes.offset = start
return
}
//
// Parse tag, length and read the a primitive value
// Supported types are:
// - boolean
// - integer (parsed as int32)
// - enumerated (parsed as int32)
// - UTF8 string
// - Octet string
//
// Parameters:
// - class: the expected class value(classUniversal, classApplication, classContextSpecific)
// - tag: the expected tag value
// - typeTag: the real primitive type to parse (tagBoolean, tagInteger, tagEnym, tagUTF8String, tagOctetString)
//
func (bytes *Bytes) ReadPrimitiveSubBytes(class int, tag int, typeTag int) (value interface{}, err error) {
// Check tag
tagAndLength, err := bytes.ParseTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
err = tagAndLength.Expect(class, tag, isNotCompound)
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
start := bytes.offset
end := bytes.offset + tagAndLength.Length
// Check we got enough bytes to process
if end > len(bytes.bytes) {
// err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: data truncated: expecting %d bytes at offset %d but only %d bytes are remaining (start: %d, length: %d, end: %d, len(b): %d, bytes: %#+v)", tagAndLength.Length, *b.offset, len(b.bytes)-start, start, tagAndLength.Length, end, len(b.bytes), b.bytes)}
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: data truncated: expecting %d bytes at offset %d but only %d bytes are remaining", tagAndLength.Length, bytes.offset, len(bytes.bytes)-start)}
return
}
// Process sub-bytes
subBytes := bytes.bytes[start:end]
switch typeTag {
case tagBoolean:
value, err = parseBool(subBytes)
case tagInteger:
value, err = parseInt32(subBytes)
case tagEnum:
value, err = parseInt32(subBytes)
case tagOctetString:
value, err = parseOctetString(subBytes)
default:
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: invalid type tag value %d", typeTag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
// Move offset
bytes.offset = end
return
}
func (bytes *Bytes) Bytes() []byte {
return bytes.bytes
}

172
goldap/bytes_test.go Normal file
View file

@ -0,0 +1,172 @@
package message
import (
"reflect"
"testing"
)
func TestReadPrimitiveSubBytesTestData(t *testing.T) {
for i, test := range PrimitiveSubBytesTestData() {
value, err := test.bytes.ReadPrimitiveSubBytes(test.class, test.tag, test.typeTag)
if err != nil {
t.Errorf("#%d failed: %s", i+1, err)
} else if !reflect.DeepEqual(test.value, value) {
t.Errorf("#%d: Wrong value %#v, got %#v", i+1, test.value, value)
} else if test.offset != test.bytes.offset {
t.Errorf("#%d: Wrong Offset, value %#v, got %#v", i+1, test.offset, test.bytes.offset)
}
}
}
func TestSizePrimitiveSubBytesTestData(t *testing.T) {
for i, test := range PrimitiveSubBytesTestData() {
value, err := test.bytes.ReadPrimitiveSubBytes(test.class, test.tag, test.typeTag)
if err != nil {
t.Errorf("#%d failed: %s", i+1, err)
} else if !reflect.DeepEqual(test.value, value) {
t.Errorf("#%d: Wrong value %#v, got %#v", i+1, test.value, value)
} else if test.offset != test.bytes.offset {
t.Errorf("#%d: Wrong Offset, value %#v, got %#v", i+1, test.offset, test.bytes.offset)
}
}
}
func NewInt(value int) (ret *int) {
ret = &value
return
}
type PrimitiveSubBytesTestSingleData struct {
bytes Bytes // Input
class int // Expected class
tag int // Expected tag
typeTag int // Expected type
value interface{} // Expected output
offset int // Expected offset after processing
}
func PrimitiveSubBytesTestData() []PrimitiveSubBytesTestSingleData {
return []PrimitiveSubBytesTestSingleData{
// Test 1
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x01, 0x09},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x09),
offset: 3,
},
// Test 2
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x02, 0x09, 0x87},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0987),
offset: 4,
},
// Test 3
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x03, 0x09, 0x87, 0x65},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x098765),
offset: 5,
},
// Test 4
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x04, 0x09, 0x87, 0x65, 0x43},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x09876543),
offset: 6,
},
// Test 5
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x03, 0x02, 0x01, 0x0f},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0f),
offset: 5,
},
// Test 6
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x16, 0x02, 0x01, 0x0f, 0x60, 0x11, 0x02, 0x01, 0x03, 0x04, 0x00, 0xa3, 0x0a, 0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0f),
offset: 5,
},
// Test 7
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x19, 0x02, 0x04, 0x7f, 0xff, 0xff, 0xff, 0x60, 0x11, 0x02, 0x01, 0x03, 0x04, 0x00, 0xa3, 0x0a, 0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x07fffffff),
offset: 8,
},
// Test 8
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("CRAM-MD5"),
offset: 10,
},
// Test 9
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x04, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("Hello, 世界"),
offset: 15,
},
// Test 10
{
bytes: Bytes{
offset: 10,
bytes: []byte{0x30, 0x1d, 0x02, 0x01, 0x05, 0x60, 0x18, 0x02, 0x01, 0x03, 0x04, 0x07, 0x6d, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x80, 0x0a, 0x6d, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("myLogin"),
offset: 19,
},
}
}

53
goldap/compare_request.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
// entry LDAPDN,
// ava AttributeValueAssertion }
func (request *CompareRequest) Entry() LDAPDN {
return request.entry
}
func (request *CompareRequest) Ava() *AttributeValueAssertion {
return &request.ava
}
func readCompareRequest(bytes *Bytes) (ret CompareRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagCompareRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readCompareRequest:\n%s", err.Error())}
return
}
return
}
func (request *CompareRequest) readComponents(bytes *Bytes) (err error) {
request.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
request.ava, err = readAttributeValueAssertion(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (request CompareRequest) write(bytes *Bytes) (size int) {
size += request.ava.write(bytes)
size += request.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagCompareRequest, size)
return
}
func (request CompareRequest) size() (size int) {
size += request.entry.size()
size += request.ava.size()
size += sizeTagAndLength(TagCompareRequest, size)
return
}

View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// CompareResponse ::= [APPLICATION 15] LDAPResult
func (response *CompareResponse) SetResultCode(code int) {
response.resultCode = ENUMERATED(code)
}
func readCompareResponse(bytes *Bytes) (ret CompareResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagCompareResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readCompareResponse:\n%s", err.Error())}
return
}
ret = CompareResponse(res)
return
}
func (response CompareResponse) write(bytes *Bytes) int {
return LDAPResult(response).writeTagged(bytes, classApplication, TagCompareResponse)
}
func (response CompareResponse) size() int {
return LDAPResult(response).sizeTagged(TagCompareResponse)
}

94
goldap/control.go Normal file
View file

@ -0,0 +1,94 @@
package message
import (
"errors"
"fmt"
)
//
// Control ::= SEQUENCE {
// controlType LDAPOID,
// criticality BOOLEAN DEFAULT FALSE,
// controlValue OCTET STRING OPTIONAL }
func (control *Control) ControlType() LDAPOID {
return control.controlType
}
func (control *Control) Criticality() BOOLEAN {
return control.criticality
}
func (control *Control) ControlValue() *OCTETSTRING {
return control.controlValue
}
func readControl(bytes *Bytes) (control Control, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, control.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readControl:\n%s", err.Error())}
return
}
return
}
func (control *Control) readComponents(bytes *Bytes) (err error) {
control.controlType, err = readLDAPOID(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == tagBoolean {
control.criticality, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if control.criticality == false {
err = errors.New(fmt.Sprintf("readComponents: criticality default value FALSE should not be specified"))
return
}
}
}
if bytes.HasMoreData() {
var octetstring OCTETSTRING
octetstring, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
control.controlValue = octetstring.Pointer()
}
return
}
func (control Control) write(bytes *Bytes) (size int) {
if control.controlValue != nil {
size += control.controlValue.write(bytes)
}
if control.criticality != BOOLEAN(false) {
size += control.criticality.write(bytes)
}
size += control.controlType.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (control Control) size() (size int) {
if control.controlValue != nil {
size += control.controlValue.size()
}
if control.criticality != BOOLEAN(false) {
size += control.criticality.size()
}
size += control.controlType.size()
size += sizeTagAndLength(tagSequence, size)
return
}

44
goldap/controls.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
//
// Controls ::= SEQUENCE OF control Control
func readTaggedControls(bytes *Bytes, class int, tag int) (controls Controls, err error) {
err = bytes.ReadSubBytes(class, tag, controls.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedControls:\n%s", err.Error())}
return
}
return
}
func (controls *Controls) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var control Control
control, err = readControl(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*controls = append(*controls, control)
}
return
}
func (controls Controls) Pointer() *Controls { return &controls }
func (controls Controls) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(controls) - 1; i >= 0; i-- {
size += controls[i].write(bytes)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
func (controls Controls) sizeTagged(tag int) (size int) {
for _, control := range controls {
size += control.size()
}
size += sizeTagAndLength(tag, size)
return
}

24
goldap/del_request.go Normal file
View file

@ -0,0 +1,24 @@
package message
import "fmt"
//
// DelRequest ::= [APPLICATION 10] LDAPDN
func readDelRequest(bytes *Bytes) (ret DelRequest, err error) {
var res LDAPDN
res, err = readTaggedLDAPDN(bytes, classApplication, TagDelRequest)
if err != nil {
err = LdapError{fmt.Sprintf("readDelRequest:\n%s", err.Error())}
return
}
ret = DelRequest(res)
return
}
func (del DelRequest) write(bytes *Bytes) int {
return LDAPDN(del).writeTagged(bytes, classApplication, TagDelRequest)
}
func (del DelRequest) size() int {
return LDAPDN(del).sizeTagged(TagDelRequest)
}

29
goldap/del_response.go Normal file
View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// DelResponse ::= [APPLICATION 11] LDAPResult
func (del *DelResponse) SetResultCode(code int) {
del.resultCode = ENUMERATED(code)
}
func readDelResponse(bytes *Bytes) (ret DelResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagDelResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readDelResponse:\n%s", err.Error())}
return
}
ret = DelResponse(res)
return
}
func (del DelResponse) write(bytes *Bytes) int {
return LDAPResult(del).writeTagged(bytes, classApplication, TagDelResponse)
}
func (del DelResponse) size() int {
return LDAPResult(del).sizeTagged(TagDelResponse)
}

60
goldap/dn.go Normal file
View file

@ -0,0 +1,60 @@
package message
import "fmt"
//
// LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
// -- [RFC4514]
func readLDAPDN(bytes *Bytes) (ret LDAPDN, err error) {
var str LDAPString
str, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readLDAPDN:\n%s", err.Error())}
return
}
ret = LDAPDN(str)
return
}
func readTaggedLDAPDN(bytes *Bytes, class int, tag int) (ret LDAPDN, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPDN:\n%s", err.Error())}
return
}
// @TODO: check RFC4514
ret = LDAPDN(ldapstring)
return
}
func (l LDAPDN) Pointer() *LDAPDN { return &l }
func readRelativeLDAPDN(bytes *Bytes) (ret RelativeLDAPDN, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readRelativeLDAPDN:\n%s", err.Error())}
return
}
// @TODO: check RFC4514
ret = RelativeLDAPDN(ldapstring)
return
}
func (l LDAPDN) write(bytes *Bytes) int {
return LDAPString(l).write(bytes)
}
func (l LDAPDN) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(l).writeTagged(bytes, class, tag)
}
func (l LDAPDN) size() int {
return LDAPString(l).size()
}
func (l LDAPDN) sizeTagged(tag int) int {
return LDAPString(l).sizeTagged(tag)
}

34
goldap/enumerated.go Normal file
View file

@ -0,0 +1,34 @@
package message
import "fmt"
func (enum ENUMERATED) Int() int {
return int(enum)
}
func readENUMERATED(bytes *Bytes, allowedValues map[ENUMERATED]string) (ret ENUMERATED, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagEnum, tagEnum)
if err != nil {
err = LdapError{fmt.Sprintf("readENUMERATED:\n%s", err.Error())}
return
}
ret = ENUMERATED(value.(int32))
if _, ok := allowedValues[ret]; !ok {
err = LdapError{fmt.Sprintf("readENUMERATED: Invalid ENUMERATED VALUE %d", ret)}
return
}
return
}
func (enum ENUMERATED) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagEnum, enum)
}
func (enum ENUMERATED) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, enum)
}
func (enum ENUMERATED) size() int {
return SizePrimitiveSubBytes(tagEnum, enum)
}

9
goldap/error.go Normal file
View file

@ -0,0 +1,9 @@
package message
type LdapError struct {
Msg string
}
func (err LdapError) Error() string {
return err.Msg
}

View file

@ -0,0 +1,69 @@
package message
import "fmt"
//
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }
func (extended *ExtendedRequest) RequestName() LDAPOID {
return extended.requestName
}
func (extended *ExtendedRequest) RequestValue() *OCTETSTRING {
return extended.requestValue
}
func readExtendedRequest(bytes *Bytes) (ret ExtendedRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagExtendedRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readExtendedRequest:\n%s", err.Error())}
return
}
return
}
func (extended *ExtendedRequest) readComponents(bytes *Bytes) (err error) {
extended.requestName, err = readTaggedLDAPOID(bytes, classContextSpecific, TagExtendedRequestName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedRequestValue {
var requestValue OCTETSTRING
requestValue, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagExtendedRequestValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.requestValue = requestValue.Pointer()
}
}
return
}
func (extended ExtendedRequest) write(bytes *Bytes) (size int) {
if extended.requestValue != nil {
size += extended.requestValue.writeTagged(bytes, classContextSpecific, TagExtendedRequestValue)
}
size += extended.requestName.writeTagged(bytes, classContextSpecific, TagExtendedRequestName)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagExtendedRequest, size)
return
}
func (extended ExtendedRequest) size() (size int) {
size += extended.requestName.sizeTagged(TagExtendedRequestName)
if extended.requestValue != nil {
size += extended.requestValue.sizeTagged(TagExtendedRequestValue)
}
size += sizeTagAndLength(TagExtendedRequest, size)
return
}

View file

@ -0,0 +1,85 @@
package message
import "fmt"
//
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }
func (extended *ExtendedResponse) SetResponseName(name LDAPOID) {
extended.responseName = &name
}
func readExtendedResponse(bytes *Bytes) (ret ExtendedResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagExtendedResponse, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readExtendedResponse:\n%s", err.Error())}
return
}
return
}
func (extended *ExtendedResponse) readComponents(bytes *Bytes) (err error) {
extended.LDAPResult.readComponents(bytes)
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedResponseName {
var oid LDAPOID
oid, err = readTaggedLDAPOID(bytes, classContextSpecific, TagExtendedResponseName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.responseName = oid.Pointer()
}
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedResponseValue {
var responseValue OCTETSTRING
responseValue, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagExtendedResponseValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.responseValue = responseValue.Pointer()
}
}
return
}
func (extended ExtendedResponse) write(bytes *Bytes) (size int) {
if extended.responseValue != nil {
size += extended.responseValue.writeTagged(bytes, classContextSpecific, TagExtendedResponseValue)
}
if extended.responseName != nil {
size += extended.responseName.writeTagged(bytes, classContextSpecific, TagExtendedResponseName)
}
size += extended.LDAPResult.writeComponents(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagExtendedResponse, size)
return
}
func (extended ExtendedResponse) size() (size int) {
size += extended.LDAPResult.sizeComponents()
if extended.responseName != nil {
size += extended.responseName.sizeTagged(TagExtendedResponseName)
}
if extended.responseValue != nil {
size += extended.responseValue.sizeTagged(TagExtendedResponseValue)
}
size += sizeTagAndLength(TagExtendedResponse, size)
return
}

70
goldap/filter.go Normal file
View file

@ -0,0 +1,70 @@
package message
import "fmt"
//
// Filter ::= CHOICE {
// and [0] SET SIZE (1..MAX) OF filter Filter,
// or [1] SET SIZE (1..MAX) OF filter Filter,
// not [2] Filter,
// equalityMatch [3] AttributeValueAssertion,
//
//
//
//Sermersheim Standards Track [Page 57]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
func readFilter(bytes *Bytes) (filter Filter, err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
err = tagAndLength.ExpectClass(classContextSpecific)
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagFilterAnd:
filter, err = readFilterAnd(bytes)
case TagFilterOr:
filter, err = readFilterOr(bytes)
case TagFilterNot:
filter, err = readFilterNot(bytes)
case TagFilterEqualityMatch:
filter, err = readFilterEqualityMatch(bytes)
case TagFilterSubstrings:
filter, err = readFilterSubstrings(bytes)
case TagFilterGreaterOrEqual:
filter, err = readFilterGreaterOrEqual(bytes)
case TagFilterLessOrEqual:
filter, err = readFilterLessOrEqual(bytes)
case TagFilterPresent:
filter, err = readFilterPresent(bytes)
case TagFilterApproxMatch:
filter, err = readFilterApproxMatch(bytes)
case TagFilterExtensibleMatch:
filter, err = readFilterExtensibleMatch(bytes)
default:
err = LdapError{fmt.Sprintf("readFilter: invalid tag value %d for filter", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
return
}

54
goldap/filter_and.go Normal file
View file

@ -0,0 +1,54 @@
package message
import "fmt"
// and [0] SET SIZE (1..MAX) OF filter Filter,
func (filterAnd FilterAnd) getFilterTag() int {
return TagFilterAnd
}
func (filterAnd FilterAnd) size() (size int) {
for _, filter := range filterAnd {
size += filter.size()
}
size += sizeTagAndLength(TagFilterAnd, size)
return
}
func (filterAnd *FilterAnd) readComponents(bytes *Bytes) (err error) {
count := 0
for bytes.HasMoreData() {
count++
var filter Filter
filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents (filter %d):\n%s", count, err.Error())}
return
}
*filterAnd = append(*filterAnd, filter)
}
if len(*filterAnd) == 0 {
err = LdapError{"readComponents: expecting at least one Filter"}
return
}
return
}
func (filterAnd FilterAnd) write(bytes *Bytes) (size int) {
for i := len(filterAnd) - 1; i >= 0; i-- {
size += filterAnd[i].write(bytes)
}
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterAnd, size)
return
}
func readFilterAnd(bytes *Bytes) (filterand FilterAnd, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterAnd, filterand.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterAnd:\n%s", err.Error())}
return
}
return
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// approxMatch [8] AttributeValueAssertion,
func readFilterApproxMatch(bytes *Bytes) (ret FilterApproxMatch, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterApproxMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterApproxMatch:\n%s", err.Error())}
return
}
ret = FilterApproxMatch(attributevalueassertion)
return
}
// approxMatch [8] AttributeValueAssertion,
func (f FilterApproxMatch) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterApproxMatch)
}
func (filterAnd FilterApproxMatch) getFilterTag() int {
return TagFilterApproxMatch
}
// approxMatch [8] AttributeValueAssertion,
func (f FilterApproxMatch) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterApproxMatch)
}
func (a *FilterApproxMatch) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterApproxMatch) AssertionValue() AssertionValue {
return a.assertionValue
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// equalityMatch [3] AttributeValueAssertion,
func readFilterEqualityMatch(bytes *Bytes) (ret FilterEqualityMatch, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterEqualityMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterEqualityMatch:\n%s", err.Error())}
return
}
ret = FilterEqualityMatch(attributevalueassertion)
return
}
// equalityMatch [3] AttributeValueAssertion,
func (f FilterEqualityMatch) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterEqualityMatch)
}
func (filter FilterEqualityMatch) getFilterTag() int {
return TagFilterEqualityMatch
}
// equalityMatch [3] AttributeValueAssertion,
func (f FilterEqualityMatch) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterEqualityMatch)
}
func (a *FilterEqualityMatch) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterEqualityMatch) AssertionValue() AssertionValue {
return a.assertionValue
}

View file

@ -0,0 +1,28 @@
package message
import "fmt"
// extensibleMatch [9] MatchingRuleAssertion,
func readFilterExtensibleMatch(bytes *Bytes) (filterextensiblematch FilterExtensibleMatch, err error) {
var matchingruleassertion MatchingRuleAssertion
matchingruleassertion, err = readTaggedMatchingRuleAssertion(bytes, classContextSpecific, TagFilterExtensibleMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterExtensibleMatch:\n%s", err.Error())}
return
}
filterextensiblematch = FilterExtensibleMatch(matchingruleassertion)
return
}
// extensibleMatch [9] MatchingRuleAssertion,
func (f FilterExtensibleMatch) write(bytes *Bytes) int {
return MatchingRuleAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterExtensibleMatch)
}
func (filterAnd FilterExtensibleMatch) getFilterTag() int {
return TagFilterExtensibleMatch
}
// extensibleMatch [9] MatchingRuleAssertion,
func (f FilterExtensibleMatch) size() int {
return MatchingRuleAssertion(f).sizeTagged(TagFilterExtensibleMatch)
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// greaterOrEqual [5] AttributeValueAssertion,
func readFilterGreaterOrEqual(bytes *Bytes) (ret FilterGreaterOrEqual, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterGreaterOrEqual)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterGreaterOrEqual:\n%s", err.Error())}
return
}
ret = FilterGreaterOrEqual(attributevalueassertion)
return
}
// greaterOrEqual [5] AttributeValueAssertion,
func (filter FilterGreaterOrEqual) write(bytes *Bytes) int {
return AttributeValueAssertion(filter).writeTagged(bytes, classContextSpecific, TagFilterGreaterOrEqual)
}
func (filter FilterGreaterOrEqual) getFilterTag() int {
return TagFilterGreaterOrEqual
}
// greaterOrEqual [5] AttributeValueAssertion,
func (filter FilterGreaterOrEqual) size() int {
return AttributeValueAssertion(filter).sizeTagged(TagFilterGreaterOrEqual)
}
func (filter *FilterGreaterOrEqual) AttributeDesc() AttributeDescription {
return filter.attributeDesc
}
func (filter *FilterGreaterOrEqual) AssertionValue() AssertionValue {
return filter.assertionValue
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// lessOrEqual [6] AttributeValueAssertion,
func readFilterLessOrEqual(bytes *Bytes) (ret FilterLessOrEqual, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterLessOrEqual)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterLessOrEqual:\n%s", err.Error())}
return
}
ret = FilterLessOrEqual(attributevalueassertion)
return
}
// lessOrEqual [6] AttributeValueAssertion,
func (f FilterLessOrEqual) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterLessOrEqual)
}
func (filterAnd FilterLessOrEqual) getFilterTag() int {
return TagFilterLessOrEqual
}
// lessOrEqual [6] AttributeValueAssertion,
func (f FilterLessOrEqual) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterLessOrEqual)
}
func (a *FilterLessOrEqual) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterLessOrEqual) AssertionValue() AssertionValue {
return a.assertionValue
}

40
goldap/filter_not.go Normal file
View file

@ -0,0 +1,40 @@
package message
import "fmt"
func (filterNot FilterNot) getFilterTag() int {
return TagFilterNot
}
// not [2] Filter,
func (filterNot FilterNot) size() (size int) {
size = filterNot.Filter.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (filterNot *FilterNot) readComponents(bytes *Bytes) (err error) {
filterNot.Filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
// not [2] Filter,
func (filterNot FilterNot) write(bytes *Bytes) (size int) {
size = filterNot.Filter.write(bytes)
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterNot, size)
return
}
// not [2] Filter,
func readFilterNot(bytes *Bytes) (filternot FilterNot, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterNot, filternot.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterNot:\n%s", err.Error())}
return
}
return
}

52
goldap/filter_or.go Normal file
View file

@ -0,0 +1,52 @@
package message
import "fmt"
// or [1] SET SIZE (1..MAX) OF filter Filter,
func readFilterOr(bytes *Bytes) (filteror FilterOr, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterOr, filteror.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterOr:\n%s", err.Error())}
return
}
return
}
func (filteror *FilterOr) readComponents(bytes *Bytes) (err error) {
count := 0
for bytes.HasMoreData() {
count++
var filter Filter
filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents (filter %d): %s", count, err.Error())}
return
}
*filteror = append(*filteror, filter)
}
if len(*filteror) == 0 {
err = LdapError{"readComponents: expecting at least one Filter"}
return
}
return
}
// or [1] SET SIZE (1..MAX) OF filter Filter,
func (f FilterOr) write(bytes *Bytes) (size int) {
for i := len(f) - 1; i >= 0; i-- {
size += f[i].write(bytes)
}
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterOr, size)
return
}
func (filter FilterOr) getFilterTag() int {
return TagFilterOr
}
// or [1] SET SIZE (1..MAX) OF filter Filter,
func (f FilterOr) size() (size int) {
for _, filter := range f {
size += filter.size()
}
size += sizeTagAndLength(TagFilterOr, size)
return
}

28
goldap/filter_present.go Normal file
View file

@ -0,0 +1,28 @@
package message
import "fmt"
// present [7] AttributeDescription,
func readFilterPresent(bytes *Bytes) (ret FilterPresent, err error) {
var attributedescription AttributeDescription
attributedescription, err = readTaggedAttributeDescription(bytes, classContextSpecific, TagFilterPresent)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterPresent:\n%s", err.Error())}
return
}
ret = FilterPresent(attributedescription)
return
}
// present [7] AttributeDescription,
func (f FilterPresent) write(bytes *Bytes) int {
return AttributeDescription(f).writeTagged(bytes, classContextSpecific, TagFilterPresent)
}
func (filterAnd FilterPresent) getFilterTag() int {
return TagFilterPresent
}
// present [7] AttributeDescription,
func (f FilterPresent) size() int {
return AttributeDescription(f).sizeTagged(TagFilterPresent)
}

187
goldap/filter_substring.go Normal file
View file

@ -0,0 +1,187 @@
package message
import "fmt"
// substrings [4] SubstringFilter,
func readFilterSubstrings(bytes *Bytes) (filtersubstrings FilterSubstrings, err error) {
var substringfilter SubstringFilter
substringfilter, err = readTaggedSubstringFilter(bytes, classContextSpecific, TagFilterSubstrings)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterSubstrings:\n%s", err.Error())}
return
}
filtersubstrings = FilterSubstrings(substringfilter)
return
}
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
func readTaggedSubstringFilter(bytes *Bytes, class int, tag int) (substringfilter SubstringFilter, err error) {
err = bytes.ReadSubBytes(class, tag, substringfilter.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedSubstringFilter:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readComponents(bytes *Bytes) (err error) {
substringfilter.type_, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = substringfilter.readSubstrings(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readSubstrings(bytes *Bytes) (err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, substringfilter.readSubstringsComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstrings:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readSubstringsComponents(bytes *Bytes) (err error) {
var foundInitial = 0
var foundFinal = 0
var tagAndLength TagAndLength
for bytes.HasMoreData() {
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
var assertionvalue AssertionValue
switch tagAndLength.Tag {
case TagSubstringInitial:
foundInitial++
if foundInitial > 1 {
err = LdapError{"readSubstringsComponents: initial can occur at most once"}
return
}
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringInitial)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringInitial(assertionvalue))
case TagSubstringAny:
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringAny)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringAny(assertionvalue))
case TagSubstringFinal:
foundFinal++
if foundFinal > 1 {
err = LdapError{"readSubstringsComponents: final can occur at most once"}
return
}
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringFinal)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringFinal(assertionvalue))
default:
err = LdapError{fmt.Sprintf("readSubstringsComponents: invalid tag %d", tagAndLength.Tag)}
return
}
}
if len(substringfilter.substrings) == 0 {
err = LdapError{"readSubstringsComponents: expecting at least one substring"}
return
}
return
}
// substrings [4] SubstringFilter,
func (f FilterSubstrings) write(bytes *Bytes) int {
return SubstringFilter(f).writeTagged(bytes, classContextSpecific, TagFilterSubstrings)
}
func (s SubstringFilter) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(s.substrings) - 1; i >= 0; i-- {
substring := s.substrings[i]
switch substring.(type) {
case SubstringInitial:
size += AssertionValue(substring.(SubstringInitial)).writeTagged(bytes, classContextSpecific, TagSubstringInitial)
case SubstringAny:
size += AssertionValue(substring.(SubstringAny)).writeTagged(bytes, classContextSpecific, TagSubstringAny)
case SubstringFinal:
size += AssertionValue(substring.(SubstringFinal)).writeTagged(bytes, classContextSpecific, TagSubstringFinal)
default:
panic("Unknown type for SubstringFilter substring")
}
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
size += s.type_.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
func (s SubstringFilter) write(bytes *Bytes) (size int) {
return s.writeTagged(bytes, classUniversal, tagSequence)
}
func (filter FilterSubstrings) getFilterTag() int {
return TagFilterSubstrings
}
// substrings [4] SubstringFilter,
func (f FilterSubstrings) size() int {
return SubstringFilter(f).sizeTagged(TagFilterSubstrings)
}
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
func (s SubstringFilter) size() (size int) {
return s.sizeTagged(tagSequence)
}
func (s SubstringFilter) sizeTagged(tag int) (size int) {
for _, substring := range s.substrings {
switch substring.(type) {
case SubstringInitial:
size += AssertionValue(substring.(SubstringInitial)).sizeTagged(TagSubstringInitial)
case SubstringAny:
size += AssertionValue(substring.(SubstringAny)).sizeTagged(TagSubstringAny)
case SubstringFinal:
size += AssertionValue(substring.(SubstringFinal)).sizeTagged(TagSubstringFinal)
default:
panic("Unknown type for SubstringFilter substring")
}
}
size += sizeTagAndLength(tagSequence, size)
size += s.type_.size()
size += sizeTagAndLength(tag, size)
return
}
func (s *FilterSubstrings) Type_() AttributeDescription {
return s.type_
}
func (s *FilterSubstrings) Substrings() []Substring {
return s.substrings
}

53
goldap/integer.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
func readINTEGER(bytes *Bytes) (ret INTEGER, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagInteger, tagInteger)
if err != nil {
err = LdapError{fmt.Sprintf("readINTEGER:\n%s", err.Error())}
return
}
ret = INTEGER(value.(int32))
return
}
func readTaggedINTEGER(bytes *Bytes, class int, tag int) (ret INTEGER, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagInteger)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedINTEGER:\n%s", err.Error())}
return
}
ret = INTEGER(value.(int32))
return
}
func readTaggedPositiveINTEGER(bytes *Bytes, class int, tag int) (ret INTEGER, err error) {
ret, err = readTaggedINTEGER(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedPositiveINTEGER:\n%s", err.Error())}
return
}
if !(ret >= 0 && ret <= maxInt) {
err = LdapError{fmt.Sprintf("readTaggedPositiveINTEGER: Invalid INTEGER value %d ! Expected value between 0 and %d", ret, maxInt)}
}
return
}
func readPositiveINTEGER(bytes *Bytes) (ret INTEGER, err error) {
return readTaggedPositiveINTEGER(bytes, classUniversal, tagInteger)
}
func (i INTEGER) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagInteger, i)
}
func (i INTEGER) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, i)
}
func (i INTEGER) size() int {
return SizePrimitiveSubBytes(tagInteger, i)
}
func (i INTEGER) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, i)
}
func (l INTEGER) Int() int {
return int(l)
}

View file

@ -0,0 +1,89 @@
package message
import "fmt"
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func readIntermediateResponse(bytes *Bytes) (ret IntermediateResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagIntermediateResponse, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readIntermediateResponse:\n%s", err.Error())}
return
}
return
}
func (bytes *Bytes) PreviewTagAndLength() (tagAndLength TagAndLength, err error) {
previousOffset := bytes.offset // Save offset
tagAndLength, err = bytes.ParseTagAndLength()
bytes.offset = previousOffset // Restore offset
return
}
func (res *IntermediateResponse) readComponents(bytes *Bytes) (err error) {
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagIntermediateResponseName {
var oid LDAPOID
oid, err = readTaggedLDAPOID(bytes, classContextSpecific, TagIntermediateResponseName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
res.responseName = oid.Pointer()
}
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagIntermediateResponseValue {
var str OCTETSTRING
str, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagIntermediateResponseValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
res.responseValue = str.Pointer()
}
}
return
}
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func (i IntermediateResponse) write(bytes *Bytes) (size int) {
if i.responseValue != nil {
size += i.responseValue.writeTagged(bytes, classContextSpecific, TagIntermediateResponseValue)
}
if i.responseName != nil {
size += i.responseName.writeTagged(bytes, classContextSpecific, TagIntermediateResponseName)
}
size += bytes.WriteTagAndLength(classApplication, isCompound, TagIntermediateResponse, size)
return
}
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func (i IntermediateResponse) size() (size int) {
if i.responseName != nil {
size += i.responseName.sizeTagged(TagIntermediateResponseName)
}
if i.responseValue != nil {
size += i.responseValue.sizeTagged(TagIntermediateResponseValue)
}
size += sizeTagAndLength(TagIntermediateResponse, size)
return
}

View file

@ -0,0 +1,117 @@
package message
import "fmt"
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func readTaggedMatchingRuleAssertion(bytes *Bytes, class int, tag int) (ret MatchingRuleAssertion, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMatchingRuleAssertion:\n%s", err.Error())}
return
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readComponents(bytes *Bytes) (err error) {
err = matchingruleassertion.readMatchingRule(bytes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
err = matchingruleassertion.readType(bytes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
matchingruleassertion.matchValue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagMatchingRuleAssertionMatchValue)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
matchingruleassertion.dnAttributes, err = readTaggedBOOLEAN(bytes, classContextSpecific, TagMatchingRuleAssertionDnAttributes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readMatchingRule(bytes *Bytes) (err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("readMatchingRule: %s", err.Error())}
}
if tagAndLength.Tag == TagMatchingRuleAssertionMatchingRule {
var matchingRule MatchingRuleId
matchingRule, err = readTaggedMatchingRuleId(bytes, classContextSpecific, TagMatchingRuleAssertionMatchingRule)
if err != nil {
return LdapError{fmt.Sprintf("readMatchingRule: %s", err.Error())}
}
matchingruleassertion.matchingRule = matchingRule.Pointer()
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readType(bytes *Bytes) (err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("readType: %s", err.Error())}
}
if tagAndLength.Tag == TagMatchingRuleAssertionType {
var attributedescription AttributeDescription
attributedescription, err = readTaggedAttributeDescription(bytes, classContextSpecific, TagMatchingRuleAssertionType)
if err != nil {
return LdapError{fmt.Sprintf("readType: %s", err.Error())}
}
matchingruleassertion.type_ = &attributedescription
}
return
}
func (m MatchingRuleAssertion) writeTagged(bytes *Bytes, class int, tag int) (size int) {
if m.dnAttributes != BOOLEAN(false) {
size += m.dnAttributes.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionDnAttributes)
}
size += m.matchValue.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionMatchValue)
if m.type_ != nil {
size += m.type_.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionType)
}
if m.matchingRule != nil {
size += m.matchingRule.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionMatchingRule)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func (m MatchingRuleAssertion) write(bytes *Bytes) (size int) {
return m.writeTagged(bytes, classUniversal, tagSequence)
}
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func (m MatchingRuleAssertion) size() (size int) {
return m.sizeTagged(tagSequence)
}
func (m MatchingRuleAssertion) sizeTagged(tag int) (size int) {
if m.matchingRule != nil {
size += m.matchingRule.sizeTagged(TagMatchingRuleAssertionMatchingRule)
}
if m.type_ != nil {
size += m.type_.sizeTagged(TagMatchingRuleAssertionType)
}
size += m.matchValue.sizeTagged(TagMatchingRuleAssertionMatchValue)
if m.dnAttributes != BOOLEAN(false) {
size += m.dnAttributes.sizeTagged(TagMatchingRuleAssertionDnAttributes)
}
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// MatchingRuleId ::= LDAPString
func readTaggedMatchingRuleId(bytes *Bytes, class int, tag int) (matchingruleid MatchingRuleId, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMatchingRuleId:\n%s", err.Error())}
return
}
matchingruleid = MatchingRuleId(ldapstring)
return
}
func (m MatchingRuleId) Pointer() *MatchingRuleId { return &m }
//
// MatchingRuleId ::= LDAPString
func (m MatchingRuleId) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(m).writeTagged(bytes, class, tag)
}
//
// MatchingRuleId ::= LDAPString
func (m MatchingRuleId) sizeTagged(tag int) int {
return LDAPString(m).sizeTagged(tag)
}

139
goldap/message.go Normal file
View file

@ -0,0 +1,139 @@
package message
import (
"fmt"
"reflect"
)
// This appendix is normative.
//
// Lightweight-Directory-Access-Protocol-V3 {1 3 6 1 1 18}
// -- Copyright (C) The Internet Society (2006). This version of
// -- this ASN.1 module is part of RFC 4511; see the RFC itself
// -- for full legal notices.
// DEFINITIONS
// IMPLICIT TAGS
// EXTENSIBILITY IMPLIED ::=
//
// BEGIN
//
// LDAPMessage ::= SEQUENCE {
// messageID MessageID,
// protocolOp CHOICE {
// bindRequest BindRequest,
// bindResponse BindResponse,
// unbindRequest UnbindRequest,
// searchRequest SearchRequest,
// searchResEntry SearchResultEntry,
// searchResDone SearchResultDone,
// searchResRef SearchResultReference,
// modifyRequest ModifyRequest,
// modifyResponse ModifyResponse,
// addRequest AddRequest,
// addResponse AddResponse,
// delRequest DelRequest,
// delResponse DelResponse,
// modDNRequest ModifyDNRequest,
// modDNResponse ModifyDNResponse,
// compareRequest CompareRequest,
// compareResponse CompareResponse,
// abandonRequest AbandonRequest,
// extendedReq ExtendedRequest,
// extendedResp ExtendedResponse,
// ...,
// intermediateResponse IntermediateResponse },
// controls [0] Controls OPTIONAL }
//
func NewLDAPMessage() *LDAPMessage { return &LDAPMessage{} }
func (message *LDAPMessage) readComponents(bytes *Bytes) (err error) {
message.messageID, err = readMessageID(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
message.protocolOp, err = readProtocolOp(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagLDAPMessageControls {
var controls Controls
controls, err = readTaggedControls(bytes, classContextSpecific, TagLDAPMessageControls)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
message.controls = controls.Pointer()
}
}
return
}
func (m *LDAPMessage) Write() (bytes *Bytes, err error) {
defer func() {
if e := recover(); e != nil {
err = LdapError{fmt.Sprintf("Error in LDAPMessage.Write: %s", e)}
}
}()
// Compute the needed size
totalSize := m.size()
// Initialize the structure
bytes = &Bytes{
bytes: make([]byte, totalSize),
offset: totalSize,
}
// Go !
size := 0
if m.controls != nil {
size += m.controls.writeTagged(bytes, classContextSpecific, TagLDAPMessageControls)
}
size += m.protocolOp.write(bytes)
size += m.messageID.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
// Check
if size != totalSize || bytes.offset != 0 {
err = LdapError{fmt.Sprintf("Something went wrong while writing the message ! Size is %d instead of %d, final offset is %d instead of 0", size, totalSize, bytes.offset)}
}
return
}
func (m *LDAPMessage) size() (size int) {
size += m.messageID.size()
size += m.protocolOp.size()
if m.controls != nil {
size += m.controls.sizeTagged(TagLDAPMessageControls)
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (l *LDAPMessage) MessageID() MessageID {
return l.messageID
}
func (l *LDAPMessage) SetMessageID(ID int) {
l.messageID = MessageID(ID)
}
func (l *LDAPMessage) Controls() *Controls {
return l.controls
}
func (l *LDAPMessage) ProtocolOp() ProtocolOp {
return l.protocolOp
}
func (l *LDAPMessage) ProtocolOpName() string {
return reflect.TypeOf(l.ProtocolOp()).Name()
}
func (l *LDAPMessage) ProtocolOpType() int {
switch l.protocolOp.(type) {
case BindRequest:
return TagBindRequest
}
return 0
}

46
goldap/message_id.go Normal file
View file

@ -0,0 +1,46 @@
package message
import "fmt"
func readTaggedMessageID(bytes *Bytes, class int, tag int) (ret MessageID, err error) {
var integer INTEGER
integer, err = readTaggedPositiveINTEGER(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMessageID:\n%s", err.Error())}
return
}
return MessageID(integer), err
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func readMessageID(bytes *Bytes) (ret MessageID, err error) {
return readTaggedMessageID(bytes, classUniversal, tagInteger)
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func (m MessageID) write(bytes *Bytes) int {
return INTEGER(m).write(bytes)
}
func (m MessageID) writeTagged(bytes *Bytes, class int, tag int) int {
return INTEGER(m).writeTagged(bytes, class, tag)
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func (m MessageID) size() int {
return INTEGER(m).size()
}
func (m MessageID) sizeTagged(tag int) int {
return INTEGER(m).sizeTagged(tag)
}
func (l MessageID) Int() int {
return int(l)
}

76
goldap/message_test.go Normal file
View file

@ -0,0 +1,76 @@
package message
import (
"testing"
"fmt"
)
func toHex(b []byte) (r string) {
r = "[ "
for _, e := range b {
r += fmt.Sprintf("0x%x ", e)
}
return r + "]"
}
func TestMessageID(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(UnbindRequest{})
m.SetMessageID(128)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
t.Logf("%v", toHex(buf.Bytes()))
ret, err := ReadLDAPMessage(NewBytes(0, buf.Bytes()))
if err != nil {
t.Errorf("unmarshalling failed with %v", err)
}
if _, ok := ret.ProtocolOp().(UnbindRequest); !ok {
t.Errorf("should be an unbind request")
}
if ret.MessageID() != 128 {
t.Errorf("Expect message id 128, got %d", ret.MessageID())
}
t.Log("Done, marshal/unmarshall worked")
}
func TestSearchEntry(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(SearchResultEntry{
objectName:"cn=êige€nbgtz,ou=users,dc=deuxfleurs,dc=fr",
attributes: PartialAttributeList{
PartialAttribute{
type_:"displayname",
vals:[]AttributeValue{"êiGe€NBgTZ"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"organizationalPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"person"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"top"},
},
PartialAttribute{
type_:"structuralobjectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
},
})
m.SetMessageID(24)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
if buf.Bytes()[0] != 0x30 {
t.Logf("Malformed message: %v", toHex(buf.Bytes()))
}
}

View file

@ -0,0 +1,87 @@
package message
import "fmt"
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func readModifyDNRequest(bytes *Bytes) (ret ModifyDNRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagModifyDNRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyDNRequest:\n%s", err.Error())}
return
}
return
}
func (req *ModifyDNRequest) readComponents(bytes *Bytes) (err error) {
req.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.newrdn, err = readRelativeLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.deleteoldrdn, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagModifyDNRequestNewSuperior {
var ldapdn LDAPDN
ldapdn, err = readTaggedLDAPDN(bytes, classContextSpecific, TagModifyDNRequestNewSuperior)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.newSuperior = ldapdn.Pointer()
}
}
return
}
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func (m ModifyDNRequest) write(bytes *Bytes) (size int) {
if m.newSuperior != nil {
size += m.newSuperior.writeTagged(bytes, classContextSpecific, TagModifyDNRequestNewSuperior)
}
size += m.deleteoldrdn.write(bytes)
size += m.newrdn.write(bytes)
size += m.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagModifyDNRequest, size)
return
}
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func (m ModifyDNRequest) size() (size int) {
size += m.entry.size()
size += m.newrdn.size()
size += m.deleteoldrdn.size()
if m.newSuperior != nil {
size += m.newSuperior.sizeTagged(TagModifyDNRequestNewSuperior)
}
size += sizeTagAndLength(TagModifyDNRequest, size)
return
}

View file

@ -0,0 +1,28 @@
package message
import "fmt"
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func readModifyDNResponse(bytes *Bytes) (ret ModifyDNResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagModifyDNResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyDNResponse:\n%s", err.Error())}
return
}
ret = ModifyDNResponse(res)
return
}
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func (m ModifyDNResponse) write(bytes *Bytes) int {
return LDAPResult(m).writeTagged(bytes, classApplication, TagModifyDNResponse)
}
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func (m ModifyDNResponse) size() int {
return LDAPResult(m).sizeTagged(TagModifyDNResponse)
}

89
goldap/modify_request.go Normal file
View file

@ -0,0 +1,89 @@
package message
import "fmt"
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func readModifyRequest(bytes *Bytes) (ret ModifyRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagModifyRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyRequest:\n%s", err.Error())}
return
}
return
}
func (m *ModifyRequest) readComponents(bytes *Bytes) (err error) {
m.object, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = bytes.ReadSubBytes(classUniversal, tagSequence, m.readChanges)
return
}
func (m *ModifyRequest) readChanges(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var c ModifyRequestChange
c, err = readModifyRequestChange(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readChanges:\n%s", err.Error())}
return
}
m.changes = append(m.changes, c)
}
return
}
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func (m ModifyRequest) write(bytes *Bytes) (size int) {
for i := len(m.changes) - 1; i >= 0; i-- {
size += m.changes[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
size += m.object.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagModifyRequest, size)
return
}
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func (m ModifyRequest) size() (size int) {
for _, change := range m.changes {
size += change.size()
}
size += sizeTagAndLength(tagSequence, size)
size += m.object.size()
size += sizeTagAndLength(TagModifyRequest, size)
return
}
func (m *ModifyRequest) Object() LDAPDN {
return m.object
}
func (m *ModifyRequest) Changes() []ModifyRequestChange {
return m.changes
}

View file

@ -0,0 +1,43 @@
package message
import "fmt"
func readModifyRequestChange(bytes *Bytes) (ret ModifyRequestChange, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyRequestChange:\n%s", err.Error())}
return
}
return
}
func (m *ModifyRequestChange) readComponents(bytes *Bytes) (err error) {
m.operation, err = readENUMERATED(bytes, EnumeratedModifyRequestChangeOperation)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
m.modification, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (m ModifyRequestChange) write(bytes *Bytes) (size int) {
size += m.modification.write(bytes)
size += m.operation.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (m ModifyRequestChange) size() (size int) {
size += m.operation.size()
size += m.modification.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (m *ModifyRequestChange) Operation() ENUMERATED {
return m.operation
}
func (m *ModifyRequestChange) Modification() *PartialAttribute {
return &m.modification
}

36
goldap/modify_response.go Normal file
View file

@ -0,0 +1,36 @@
package message
import "fmt"
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func readModifyResponse(bytes *Bytes) (ret ModifyResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagModifyResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyResponse:\n%s", err.Error())}
return
}
ret = ModifyResponse(res)
return
}
func (l LDAPResult) writeTagged(bytes *Bytes, class int, tag int) (size int) {
size += l.writeComponents(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func (m ModifyResponse) write(bytes *Bytes) int {
return LDAPResult(m).writeTagged(bytes, classApplication, TagModifyResponse)
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func (m ModifyResponse) size() int {
return LDAPResult(m).sizeTagged(TagModifyResponse)
}
func (l *ModifyResponse) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}

44
goldap/octetstring.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
func readOCTETSTRING(bytes *Bytes) (ret OCTETSTRING, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagOctetString, tagOctetString)
if err != nil {
err = LdapError{fmt.Sprintf("readOCTETSTRING:\n%s", err.Error())}
return
}
ret = OCTETSTRING(value.([]byte))
return
}
func readTaggedOCTETSTRING(bytes *Bytes, class int, tag int) (ret OCTETSTRING, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagOctetString)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedOCTETSTRING:\n%s", err.Error())}
return
}
ret = OCTETSTRING(value.([]byte))
return
}
func (o OCTETSTRING) Pointer() *OCTETSTRING { return &o }
func (o OCTETSTRING) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagOctetString, o)
}
func (o OCTETSTRING) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, o)
}
func (o OCTETSTRING) size() int {
return SizePrimitiveSubBytes(tagOctetString, o)
}
func (o OCTETSTRING) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, o)
}
func (l OCTETSTRING) String() string {
return string(l)
}
func (l OCTETSTRING) Bytes() []byte {
return []byte(l)
}

50
goldap/oid.go Normal file
View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
//
// LDAPOID ::= OCTET STRING -- Constrained to <numericoid>
// -- [RFC4512]
func (l LDAPOID) String() string {
return string(l)
}
func (l LDAPOID) Bytes() []byte {
return []byte(l)
}
func (l LDAPOID) Pointer() *LDAPOID { return &l }
func readTaggedLDAPOID(bytes *Bytes, class int, tag int) (ret LDAPOID, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPOID:\n%s", err.Error())}
return
}
// @TODO: check RFC4512 for <numericoid>
ret = LDAPOID(octetstring)
return
}
func readLDAPOID(bytes *Bytes) (ret LDAPOID, err error) {
return readTaggedLDAPOID(bytes, classUniversal, tagOctetString)
}
func (l LDAPOID) write(bytes *Bytes) int {
return OCTETSTRING(l).write(bytes)
}
func (l LDAPOID) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(l).writeTagged(bytes, class, tag)
}
func (l LDAPOID) size() int {
return OCTETSTRING(l).size()
}
func (l LDAPOID) sizeTagged(tag int) int {
return OCTETSTRING(l).sizeTagged(tag)
}

View file

@ -0,0 +1,76 @@
package message
import "fmt"
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func readPartialAttribute(bytes *Bytes) (ret PartialAttribute, err error) {
ret = PartialAttribute{vals: make([]AttributeValue, 0, 10)}
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readPartialAttribute:\n%s", err.Error())}
return
}
return
}
func (partialattribute *PartialAttribute) readComponents(bytes *Bytes) (err error) {
partialattribute.type_, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = bytes.ReadSubBytes(classUniversal, tagSet, partialattribute.readValsComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (partialattribute *PartialAttribute) readValsComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var attributevalue AttributeValue
attributevalue, err = readAttributeValue(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readValsComponents:\n%s", err.Error())}
return
}
partialattribute.vals = append(partialattribute.vals, attributevalue)
}
return
}
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func (p PartialAttribute) write(bytes *Bytes) (size int) {
for i := len(p.vals) - 1; i >= 0; i-- {
size += p.vals[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSet, size)
size += p.type_.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func (p PartialAttribute) size() (size int) {
for _, value := range p.vals {
size += value.size()
}
size += sizeTagAndLength(tagSet, size)
size += p.type_.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (p *PartialAttribute) Type_() AttributeDescription {
return p.type_
}
func (p *PartialAttribute) Vals() []AttributeValue {
return p.vals
}

View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func readPartialAttributeList(bytes *Bytes) (ret PartialAttributeList, err error) {
ret = PartialAttributeList(make([]PartialAttribute, 0, 10))
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readPartialAttributeList:\n%s", err.Error())}
return
}
return
}
func (partialattributelist *PartialAttributeList) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var partialattribute PartialAttribute
partialattribute, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*partialattributelist = append(*partialattributelist, partialattribute)
}
return
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func (p PartialAttributeList) write(bytes *Bytes) (size int) {
for i := len(p) - 1; i >= 0; i-- {
size += p[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func (p PartialAttributeList) size() (size int) {
for _, att := range p {
size += att.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (p *PartialAttributeList) add(a PartialAttribute) {
*p = append(*p, a)
}

63
goldap/protocol_op.go Normal file
View file

@ -0,0 +1,63 @@
package message
import "fmt"
func readProtocolOp(bytes *Bytes) (ret ProtocolOp, err error) {
tagAndLength, err := bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readProtocolOp:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagBindRequest:
ret, err = readBindRequest(bytes)
case TagBindResponse:
ret, err = readBindResponse(bytes)
case TagUnbindRequest:
ret, err = readUnbindRequest(bytes)
case TagSearchRequest:
ret, err = readSearchRequest(bytes)
case TagSearchResultEntry:
ret, err = readSearchResultEntry(bytes)
case TagSearchResultDone:
ret, err = readSearchResultDone(bytes)
case TagSearchResultReference:
ret, err = readSearchResultReference(bytes)
case TagModifyRequest:
ret, err = readModifyRequest(bytes)
case TagModifyResponse:
ret, err = readModifyResponse(bytes)
case TagAddRequest:
ret, err = readAddRequest(bytes)
case TagAddResponse:
ret, err = readAddResponse(bytes)
case TagDelRequest:
ret, err = readDelRequest(bytes)
case TagDelResponse:
ret, err = readDelResponse(bytes)
case TagModifyDNRequest:
ret, err = readModifyDNRequest(bytes)
case TagModifyDNResponse:
ret, err = readModifyDNResponse(bytes)
case TagCompareRequest:
ret, err = readCompareRequest(bytes)
case TagCompareResponse:
ret, err = readCompareResponse(bytes)
case TagAbandonRequest:
ret, err = readAbandonRequest(bytes)
case TagExtendedRequest:
ret, err = readExtendedRequest(bytes)
case TagExtendedResponse:
ret, err = readExtendedResponse(bytes)
case TagIntermediateResponse:
ret, err = readIntermediateResponse(bytes)
default:
err = LdapError{fmt.Sprintf("readProtocolOp: invalid tag value %d for protocolOp", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readProtocolOp:\n%s", err.Error())}
return
}
return
}

18
goldap/read.go Normal file
View file

@ -0,0 +1,18 @@
package message
import (
"fmt"
)
func ReadLDAPMessage(bytes *Bytes) (message LDAPMessage, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, message.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("ReadLDAPMessage:\n%s", err.Error())}
return
}
return
}
//
// END
//

3146
goldap/read_error_test.go Normal file

File diff suppressed because one or more lines are too long

2936
goldap/read_test.go Normal file

File diff suppressed because one or more lines are too long

50
goldap/referral.go Normal file
View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func readTaggedReferral(bytes *Bytes, class int, tag int) (referral Referral, err error) {
err = bytes.ReadSubBytes(class, tag, referral.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedReferral:\n%s", err.Error())}
return
}
return
}
func (referral *Referral) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var uri URI
uri, err = readURI(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*referral = append(*referral, uri)
}
if len(*referral) == 0 {
return LdapError{"readComponents: expecting at least one URI"}
}
return
}
func (referral Referral) Pointer() *Referral { return &referral }
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func (r Referral) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(r) - 1; i >= 0; i-- {
size += r[i].write(bytes)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func (r Referral) sizeTagged(tag int) (size int) {
for _, uri := range r {
size += uri.size()
}
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,15 @@
package message
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
func (r RelativeLDAPDN) write(bytes *Bytes) int {
return LDAPString(r).write(bytes)
}
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
func (r RelativeLDAPDN) size() int {
return LDAPString(r).size()
}

282
goldap/result.go Normal file
View file

@ -0,0 +1,282 @@
package message
import "fmt"
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func readTaggedLDAPResult(bytes *Bytes, class int, tag int) (ret LDAPResult, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPResult:\n%s", err.Error())}
}
return
}
func readLDAPResult(bytes *Bytes) (ldapresult LDAPResult, err error) {
return readTaggedLDAPResult(bytes, classUniversal, tagSequence)
}
func (ldapresult *LDAPResult) readComponents(bytes *Bytes) (err error) {
ldapresult.resultCode, err = readENUMERATED(bytes, EnumeratedLDAPResultCode)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.matchedDN, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.diagnosticMessage, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagLDAPResultReferral {
var referral Referral
referral, err = readTaggedReferral(bytes, classContextSpecific, TagLDAPResultReferral)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.referral = referral.Pointer()
}
}
return
}
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func (l LDAPResult) write(bytes *Bytes) (size int) {
size += l.writeComponents(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (l LDAPResult) writeComponents(bytes *Bytes) (size int) {
if l.referral != nil {
size += l.referral.writeTagged(bytes, classContextSpecific, TagLDAPResultReferral)
}
size += l.diagnosticMessage.write(bytes)
size += l.matchedDN.write(bytes)
size += l.resultCode.write(bytes)
return
}
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func (l LDAPResult) size() (size int) {
size += l.sizeComponents()
size += sizeTagAndLength(tagSequence, size)
return
}
func (l LDAPResult) sizeTagged(tag int) (size int) {
size += l.sizeComponents()
size += sizeTagAndLength(tag, size)
return
}
func (l LDAPResult) sizeComponents() (size int) {
if l.referral != nil {
size += l.referral.sizeTagged(TagLDAPResultReferral)
}
size += l.diagnosticMessage.size()
size += l.matchedDN.size()
size += l.resultCode.size()
return
}
func (l *LDAPResult) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}
func (l *LDAPResult) SeMatchedDN(code string) {
l.matchedDN = LDAPDN(code)
}
func (l *LDAPResult) SetDiagnosticMessage(code string) {
l.diagnosticMessage = LDAPString(code)
}
func (l *LDAPResult) SetReferral(r *Referral) {
l.referral = r
}

View file

@ -0,0 +1,63 @@
package message
import "fmt"
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func readSaslCredentials(bytes *Bytes) (authentication SaslCredentials, err error) {
authentication = SaslCredentials{}
err = bytes.ReadSubBytes(classContextSpecific, TagAuthenticationChoiceSaslCredentials, authentication.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSaslCredentials:\n%s", err.Error())}
return
}
return
}
func (authentication *SaslCredentials) readComponents(bytes *Bytes) (err error) {
authentication.mechanism, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var credentials OCTETSTRING
credentials, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
authentication.credentials = credentials.Pointer()
}
return
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func (s SaslCredentials) writeTagged(bytes *Bytes, class int, tag int) (size int) {
if s.credentials != nil {
size += s.credentials.write(bytes)
}
size += s.mechanism.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func (s SaslCredentials) sizeTagged(tag int) (size int) {
if s.credentials != nil {
size += s.credentials.size()
}
size += s.mechanism.size()
size += sizeTagAndLength(tag, size)
return
}

246
goldap/search_request.go Normal file
View file

@ -0,0 +1,246 @@
package message
import (
"errors"
"fmt"
)
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
func readSearchRequest(bytes *Bytes) (searchrequest SearchRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchRequest, searchrequest.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchRequest:\n%s", err.Error())}
return
}
return
}
func (searchrequest *SearchRequest) readComponents(bytes *Bytes) (err error) {
searchrequest.baseObject, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.scope, err = readENUMERATED(bytes, EnumeratedSearchRequestScope)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.derefAliases, err = readENUMERATED(bytes, EnumeratedSearchRequestDerefAliases)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.sizeLimit, err = readPositiveINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.timeLimit, err = readPositiveINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.typesOnly, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.attributes, err = readAttributeSelection(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
func (s SearchRequest) write(bytes *Bytes) (size int) {
size += s.attributes.write(bytes)
size += s.filter.write(bytes)
size += s.typesOnly.write(bytes)
size += s.timeLimit.write(bytes)
size += s.sizeLimit.write(bytes)
size += s.derefAliases.write(bytes)
size += s.scope.write(bytes)
size += s.baseObject.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchRequest, size)
return
}
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
func (s SearchRequest) size() (size int) {
size += s.baseObject.size()
size += s.scope.size()
size += s.derefAliases.size()
size += s.sizeLimit.size()
size += s.timeLimit.size()
size += s.typesOnly.size()
size += s.filter.size()
size += s.attributes.size()
size += sizeTagAndLength(TagSearchRequest, size)
return
}
func (s *SearchRequest) BaseObject() LDAPDN {
return s.baseObject
}
func (s *SearchRequest) Scope() ENUMERATED {
return s.scope
}
func (s *SearchRequest) DerefAliases() ENUMERATED {
return s.derefAliases
}
func (s *SearchRequest) SizeLimit() INTEGER {
return s.sizeLimit
}
func (s *SearchRequest) TimeLimit() INTEGER {
return s.timeLimit
}
func (s *SearchRequest) TypesOnly() BOOLEAN {
return s.typesOnly
}
func (s *SearchRequest) Attributes() AttributeSelection {
return s.attributes
}
func (s *SearchRequest) Filter() Filter {
return s.filter
}
func (s *SearchRequest) FilterString() string {
str, _ := s.decompileFilter(s.Filter())
return str
}
func (s *SearchRequest) decompileFilter(packet Filter) (ret string, err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("error decompiling filter")
}
}()
ret = "("
err = nil
childStr := ""
switch f := packet.(type) {
case FilterAnd:
ret += "&"
for _, child := range f {
childStr, err = s.decompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterOr:
ret += "|"
for _, child := range f {
childStr, err = s.decompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterNot:
ret += "!"
childStr, err = s.decompileFilter(f.Filter)
if err != nil {
return
}
ret += childStr
case FilterSubstrings:
ret += string(f.Type_())
ret += "="
for _, fs := range f.Substrings() {
switch fsv := fs.(type) {
case SubstringInitial:
ret += string(fsv) + "*"
case SubstringAny:
ret += "*" + string(fsv) + "*"
case SubstringFinal:
ret += "*" + string(fsv)
}
}
case FilterEqualityMatch:
ret += string(f.AttributeDesc())
ret += "="
ret += string(f.AssertionValue())
case FilterGreaterOrEqual:
ret += string(f.AttributeDesc())
ret += ">="
ret += string(f.AssertionValue())
case FilterLessOrEqual:
ret += string(f.AttributeDesc())
ret += "<="
ret += string(f.AssertionValue())
case FilterPresent:
// if 0 == len(packet.Children) {
// ret += ber.DecodeString(packet.Data.Bytes())
// } else {
// ret += ber.DecodeString(packet.Children[0].Data.Bytes())
// }
ret += string(f)
ret += "=*"
case FilterApproxMatch:
ret += string(f.AttributeDesc())
ret += "~="
ret += string(f.AssertionValue())
}
ret += ")"
return
}

View file

@ -0,0 +1,31 @@
package message
import "fmt"
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func readSearchResultDone(bytes *Bytes) (ret SearchResultDone, err error) {
var ldapresult LDAPResult
ldapresult, err = readTaggedLDAPResult(bytes, classApplication, TagSearchResultDone)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultDone:\n%s", err.Error())}
return
}
ret = SearchResultDone(ldapresult)
return
}
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func (s SearchResultDone) write(bytes *Bytes) int {
return LDAPResult(s).writeTagged(bytes, classApplication, TagSearchResultDone)
}
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func (s SearchResultDone) size() int {
return LDAPResult(s).sizeTagged(TagSearchResultDone)
}
func (l *SearchResultDone) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}

View file

@ -0,0 +1,58 @@
package message
import "fmt"
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func readSearchResultEntry(bytes *Bytes) (searchresultentry SearchResultEntry, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchResultEntry, searchresultentry.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultEntry:\n%s", err.Error())}
return
}
return
}
func (searchresultentry *SearchResultEntry) readComponents(bytes *Bytes) (err error) {
searchresultentry.objectName, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchresultentry.attributes, err = readPartialAttributeList(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func (s SearchResultEntry) write(bytes *Bytes) (size int) {
size += s.attributes.write(bytes)
size += s.objectName.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchResultEntry, size)
return
}
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func (s SearchResultEntry) size() (size int) {
size += s.objectName.size()
size += s.attributes.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (s *SearchResultEntry) SetObjectName(on string) {
s.objectName = LDAPDN(on)
}
func (s *SearchResultEntry) AddAttribute(name AttributeDescription, values ...AttributeValue) {
var ea = PartialAttribute{type_: name, vals: values}
s.attributes.add(ea)
}

View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func readSearchResultReference(bytes *Bytes) (ret SearchResultReference, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchResultReference, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultReference:\n%s", err.Error())}
return
}
return
}
func (s *SearchResultReference) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var uri URI
uri, err = readURI(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*s = append(*s, uri)
}
if len(*s) == 0 {
err = LdapError{"readComponents: expecting at least one URI"}
return
}
return
}
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func (s SearchResultReference) write(bytes *Bytes) (size int) {
for i := len(s) - 1; i >= 0; i-- {
size += s[i].write(bytes)
}
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchResultReference, size)
return
}
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func (s SearchResultReference) size() (size int) {
for _, uri := range s {
size += uri.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}

85
goldap/size_test.go Normal file
View file

@ -0,0 +1,85 @@
package message
import (
"testing"
)
func TestSizeLDAPMessage(t *testing.T) {
var testData = getLDAPMessageTestData()
for i, test := range testData {
message, err := ReadLDAPMessage(&test.bytes)
if err != nil {
t.Errorf("#%d error at offset %d (%s): %s", i, test.bytes.offset, test.bytes.DumpCurrentBytes(), err)
}
size := message.size()
expected := len(test.bytes.bytes)
if size != expected {
t.Errorf("#%d: wrong size, GOT: %d, EXPECTED: %d", i, size, expected)
}
}
}
type tagAndLengthTestData struct {
tag int
length int
expectedSize int
}
func getSizeTagAndLengthTestData() (ret []tagAndLengthTestData) {
return []tagAndLengthTestData{
// Length between 0 and 127 are encoded on one byte
{
tag: tagSequence,
length: 0,
expectedSize: 2,
},
{
tag: tagSequence,
length: 127,
expectedSize: 2,
},
// Length between 128 and 255 are encoded on two bytes
{
tag: tagSequence,
length: 128,
expectedSize: 3,
},
{
tag: tagSequence,
length: 255,
expectedSize: 3,
},
// Length between 256 (2^8) and 65535 (2^16-1) are encoded on three bytes
{
tag: tagSequence,
length: 256,
expectedSize: 4,
},
{
tag: tagSequence,
length: 65535,
expectedSize: 4,
},
// Length between 65536 (2^16) and 16777215 (2^24-1) are encoded on four bytes
{
tag: tagSequence,
length: 65536,
expectedSize: 5,
},
{
tag: tagSequence,
length: 16777215,
expectedSize: 5,
},
}
}
func TestSizeTagAndLength(t *testing.T) {
for i, test := range getSizeTagAndLengthTestData() {
size := sizeTagAndLength(test.tag, test.length)
if test.expectedSize != size {
t.Errorf("#%d: wrong size, GOT: %d, EXPECTED: %d", i, size, test.expectedSize)
}
}
}

38
goldap/string.go Normal file
View file

@ -0,0 +1,38 @@
package message
import "fmt"
func readTaggedLDAPString(bytes *Bytes, class int, tag int) (ldapstring LDAPString, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPString:\n%s", err.Error())}
return
}
ldapstring = LDAPString(octetstring)
return
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func readLDAPString(bytes *Bytes) (ldapstring LDAPString, err error) {
return readTaggedLDAPString(bytes, classUniversal, tagOctetString)
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func (s LDAPString) write(bytes *Bytes) int {
return OCTETSTRING(s).write(bytes)
}
func (s LDAPString) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(s).writeTagged(bytes, class, tag)
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func (s LDAPString) size() int {
return OCTETSTRING(s).size()
}
func (s LDAPString) sizeTagged(tag int) int {
return OCTETSTRING(s).sizeTagged(tag)
}

739
goldap/struct.go Normal file
View file

@ -0,0 +1,739 @@
package message
type OCTETSTRING string
type INTEGER int32 // In this RFC the max INTEGER value is 2^31 - 1, so int32 is enough
type BOOLEAN bool
type ENUMERATED int32
// This appendix is normative.
//
// Lightweight-Directory-Access-Protocol-V3 {1 3 6 1 1 18}
// -- Copyright (C) The Internet Society (2006). This version of
// -- this ASN.1 module is part of RFC 4511; see the RFC itself
// -- for full legal notices.
// DEFINITIONS
// IMPLICIT TAGS
// EXTENSIBILITY IMPLIED ::=
//
// BEGIN
//
// LDAPMessage ::= SEQUENCE {
// messageID MessageID,
// protocolOp CHOICE {
// bindRequest BindRequest,
// bindResponse BindResponse,
// unbindRequest UnbindRequest,
// searchRequest SearchRequest,
// searchResEntry SearchResultEntry,
// searchResDone SearchResultDone,
// searchResRef SearchResultReference,
// modifyRequest ModifyRequest,
// modifyResponse ModifyResponse,
// addRequest AddRequest,
// addResponse AddResponse,
// delRequest DelRequest,
// delResponse DelResponse,
// modDNRequest ModifyDNRequest,
// modDNResponse ModifyDNResponse,
// compareRequest CompareRequest,
// compareResponse CompareResponse,
// abandonRequest AbandonRequest,
// extendedReq ExtendedRequest,
// extendedResp ExtendedResponse,
// ...,
// intermediateResponse IntermediateResponse },
// controls [0] Controls OPTIONAL }
//
type LDAPMessage struct {
messageID MessageID
protocolOp ProtocolOp
controls *Controls
}
const TagLDAPMessageControls = 0
type ProtocolOp interface {
size() int
write(*Bytes) int
}
// MessageID ::= INTEGER (0 .. maxInt)
//
type MessageID INTEGER
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
const maxInt = INTEGER(2147483647)
//
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
type LDAPString OCTETSTRING
//
//
//
//
//Sermersheim Standards Track [Page 54]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// LDAPOID ::= OCTET STRING -- Constrained to <numericoid>
// -- [RFC4512]
type LDAPOID OCTETSTRING
//
// LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
// -- [RFC4514]
type LDAPDN LDAPString
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
type RelativeLDAPDN LDAPString
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
type AttributeDescription LDAPString
//
// AttributeValue ::= OCTET STRING
type AttributeValue OCTETSTRING
//
// AttributeValueAssertion ::= SEQUENCE {
// attributeDesc AttributeDescription,
// assertionValue AssertionValue }
type AttributeValueAssertion struct {
attributeDesc AttributeDescription
assertionValue AssertionValue
}
//
// AssertionValue ::= OCTET STRING
type AssertionValue OCTETSTRING
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
type PartialAttribute struct {
type_ AttributeDescription
vals []AttributeValue
}
//
// Attribute ::= PartialAttribute(WITH COMPONENTS {
// ...,
// vals (SIZE(1..MAX))})
type Attribute PartialAttribute
//
// MatchingRuleId ::= LDAPString
type MatchingRuleId LDAPString
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
//
type LDAPResult struct {
resultCode ENUMERATED
matchedDN LDAPDN
diagnosticMessage LDAPString
referral *Referral
}
const TagLDAPResultReferral = 3
const ResultCodeSuccess = 0
const ResultCodeOperationsError = 1
const ResultCodeProtocolError = 2
const ResultCodeTimeLimitExceeded = 3
const ResultCodeSizeLimitExceeded = 4
const ResultCodeCompareFalse = 5
const ResultCodeCompareTrue = 6
const ResultCodeAuthMethodNotSupported = 7
const ResultCodeStrongerAuthRequired = 8
const ResultCodeReferral = 10
const ResultCodeAdminLimitExceeded = 11
const ResultCodeUnavailableCriticalExtension = 12
const ResultCodeConfidentialityRequired = 13
const ResultCodeSaslBindInProgress = 14
const ResultCodeNoSuchAttribute = 16
const ResultCodeUndefinedAttributeType = 17
const ResultCodeInappropriateMatching = 18
const ResultCodeConstraintViolation = 19
const ResultCodeAttributeOrValueExists = 20
const ResultCodeInvalidAttributeSyntax = 21
const ResultCodeNoSuchObject = 32
const ResultCodeAliasProblem = 33
const ResultCodeInvalidDNSyntax = 34
const ResultCodeAliasDereferencingProblem = 36
const ResultCodeInappropriateAuthentication = 48
const ResultCodeInvalidCredentials = 49
const ResultCodeInsufficientAccessRights = 50
const ResultCodeBusy = 51
const ResultCodeUnavailable = 52
const ResultCodeUnwillingToPerform = 53
const ResultCodeLoopDetect = 54
const ResultCodeNamingViolation = 64
const ResultCodeObjectClassViolation = 65
const ResultCodeNotAllowedOnNonLeaf = 66
const ResultCodeNotAllowedOnRDN = 67
const ResultCodeEntryAlreadyExists = 68
const ResultCodeObjectClassModsProhibited = 69
const ResultCodeAffectsMultipleDSAs = 71
const ResultCodeOther = 80
var EnumeratedLDAPResultCode = map[ENUMERATED]string{
ResultCodeSuccess: "success",
ResultCodeOperationsError: "operationsError",
ResultCodeProtocolError: "protocolError",
ResultCodeTimeLimitExceeded: "timeLimitExceeded",
ResultCodeSizeLimitExceeded: "sizeLimitExceeded",
ResultCodeCompareFalse: "compareFalse",
ResultCodeCompareTrue: "compareTrue",
ResultCodeAuthMethodNotSupported: "authMethodNotSupported",
ResultCodeStrongerAuthRequired: "strongerAuthRequired",
// -- 9 reserved --
ResultCodeReferral: "referral",
ResultCodeAdminLimitExceeded: "adminLimitExceeded",
ResultCodeUnavailableCriticalExtension: "unavailableCriticalExtension",
ResultCodeConfidentialityRequired: "confidentialityRequired",
ResultCodeSaslBindInProgress: "saslBindInProgress",
ResultCodeNoSuchAttribute: "noSuchAttribute",
ResultCodeUndefinedAttributeType: "undefinedAttributeType",
ResultCodeInappropriateMatching: "inappropriateMatching",
ResultCodeConstraintViolation: "constraintViolation",
ResultCodeAttributeOrValueExists: "attributeOrValueExists",
ResultCodeInvalidAttributeSyntax: "invalidAttributeSyntax",
// -- 22-31 unused --
ResultCodeNoSuchObject: "noSuchObject",
ResultCodeAliasProblem: "aliasProblem",
ResultCodeInvalidDNSyntax: "invalidDNSyntax",
// -- 35 reserved for undefined isLeaf --
ResultCodeAliasDereferencingProblem: "aliasDereferencingProblem",
// -- 37-47 unused --
ResultCodeInappropriateAuthentication: "inappropriateAuthentication",
ResultCodeInvalidCredentials: "invalidCredentials",
ResultCodeInsufficientAccessRights: "insufficientAccessRights",
ResultCodeBusy: "busy",
ResultCodeUnavailable: "unavailable",
ResultCodeUnwillingToPerform: "unwillingToPerform",
ResultCodeLoopDetect: "loopDetect",
// -- 55-63 unused --
ResultCodeNamingViolation: "namingViolation",
ResultCodeObjectClassViolation: "objectClassViolation",
ResultCodeNotAllowedOnNonLeaf: "notAllowedOnNonLeaf",
ResultCodeNotAllowedOnRDN: "notAllowedOnRDN",
ResultCodeEntryAlreadyExists: "entryAlreadyExists",
ResultCodeObjectClassModsProhibited: "objectClassModsProhibited",
// -- 70 reserved for CLDAP --
ResultCodeAffectsMultipleDSAs: "affectsMultipleDSAs",
// -- 72-79 unused --
ResultCodeOther: "other",
}
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
type Referral []URI
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
type URI LDAPString
//
// Controls ::= SEQUENCE OF control Control
type Controls []Control
//
// Control ::= SEQUENCE {
// controlType LDAPOID,
// criticality BOOLEAN DEFAULT FALSE,
// controlValue OCTET STRING OPTIONAL }
type Control struct {
controlType LDAPOID
criticality BOOLEAN
controlValue *OCTETSTRING
}
//
//
//
//
//Sermersheim Standards Track [Page 56]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// BindRequest ::= [APPLICATION 0] SEQUENCE {
// version INTEGER (1 .. 127),
// name LDAPDN,
// authentication AuthenticationChoice }
const TagBindRequest = 0
const BindRequestVersionMin = 1
const BindRequestVersionMax = 127
type BindRequest struct {
version INTEGER
name LDAPDN
authentication AuthenticationChoice
}
//
// AuthenticationChoice ::= CHOICE {
// simple [0] OCTET STRING,
// -- 1 and 2 reserved
// sasl [3] SaslCredentials,
// ... }
const TagAuthenticationChoiceSimple = 0
const TagAuthenticationChoiceSaslCredentials = 3
type AuthenticationChoice interface {
sizeTagged(int) int
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
type SaslCredentials struct {
mechanism LDAPString
credentials *OCTETSTRING
}
//
// BindResponse ::= [APPLICATION 1] SEQUENCE {
// COMPONENTS OF LDAPResult,
// serverSaslCreds [7] OCTET STRING OPTIONAL }
const TagBindResponse = 1
const TagBindResponseServerSaslCreds = 7
type BindResponse struct {
LDAPResult
serverSaslCreds *OCTETSTRING
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
const TagUnbindRequest = 2
type UnbindRequest struct {
}
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
const TagSearchRequest = 3
type SearchRequest struct {
baseObject LDAPDN
scope ENUMERATED
derefAliases ENUMERATED
sizeLimit INTEGER
timeLimit INTEGER
typesOnly BOOLEAN
filter Filter
attributes AttributeSelection
}
const SearchRequestScopeBaseObject = 0
const SearchRequestSingleLevel = 1
const SearchRequestHomeSubtree = 2
var EnumeratedSearchRequestScope = map[ENUMERATED]string{
SearchRequestScopeBaseObject: "baseObject",
SearchRequestSingleLevel: "singleLevel",
SearchRequestHomeSubtree: "homeSubtree",
}
const SearchRequetDerefAliasesNeverDerefAliases = 0
const SearchRequetDerefAliasesDerefInSearching = 1
const SearchRequetDerefAliasesDerefFindingBaseObj = 2
const SearchRequetDerefAliasesDerefAlways = 3
var EnumeratedSearchRequestDerefAliases = map[ENUMERATED]string{
SearchRequetDerefAliasesNeverDerefAliases: "neverDerefAliases",
SearchRequetDerefAliasesDerefInSearching: "derefInSearching",
SearchRequetDerefAliasesDerefFindingBaseObj: "derefFindingBaseObj",
SearchRequetDerefAliasesDerefAlways: "derefAlways",
}
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
type AttributeSelection []LDAPString
//
// Filter ::= CHOICE {
// and [0] SET SIZE (1..MAX) OF filter Filter,
// or [1] SET SIZE (1..MAX) OF filter Filter,
// not [2] Filter,
// equalityMatch [3] AttributeValueAssertion,
//
//
//
//Sermersheim Standards Track [Page 57]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
const TagFilterAnd = 0
const TagFilterOr = 1
const TagFilterNot = 2
const TagFilterEqualityMatch = 3
const TagFilterSubstrings = 4
const TagFilterGreaterOrEqual = 5
const TagFilterLessOrEqual = 6
const TagFilterPresent = 7
const TagFilterApproxMatch = 8
const TagFilterExtensibleMatch = 9
type Filter interface {
size() int
write(*Bytes) int
getFilterTag() int
}
type FilterAnd []Filter
type FilterOr []Filter
type FilterNot struct {
Filter
}
type FilterEqualityMatch AttributeValueAssertion
type FilterSubstrings SubstringFilter
type FilterGreaterOrEqual AttributeValueAssertion
type FilterLessOrEqual AttributeValueAssertion
type FilterPresent AttributeDescription
type FilterApproxMatch AttributeValueAssertion
type FilterExtensibleMatch MatchingRuleAssertion
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
type SubstringFilter struct {
type_ AttributeDescription
substrings []Substring
}
type Substring interface{}
const TagSubstringInitial = 0
const TagSubstringAny = 1
const TagSubstringFinal = 2
type SubstringInitial AssertionValue
type SubstringAny AssertionValue
type SubstringFinal AssertionValue
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
type MatchingRuleAssertion struct {
matchingRule *MatchingRuleId
type_ *AttributeDescription
matchValue AssertionValue
dnAttributes BOOLEAN
}
const TagMatchingRuleAssertionMatchingRule = 1
const TagMatchingRuleAssertionType = 2
const TagMatchingRuleAssertionMatchValue = 3
const TagMatchingRuleAssertionDnAttributes = 4
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
const TagSearchResultEntry = 4
type SearchResultEntry struct {
objectName LDAPDN
attributes PartialAttributeList
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
type PartialAttributeList []PartialAttribute
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
const TagSearchResultReference = 19
type SearchResultReference []URI
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
const TagSearchResultDone = 5
type SearchResultDone LDAPResult
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
const TagModifyRequest = 6
type ModifyRequest struct {
object LDAPDN
changes []ModifyRequestChange
}
type ModifyRequestChange struct {
operation ENUMERATED
modification PartialAttribute
}
const ModifyRequestChangeOperationAdd = 0
const ModifyRequestChangeOperationDelete = 1
const ModifyRequestChangeOperationReplace = 2
var EnumeratedModifyRequestChangeOperation = map[ENUMERATED]string{
ModifyRequestChangeOperationAdd: "add",
ModifyRequestChangeOperationDelete: "delete",
ModifyRequestChangeOperationReplace: "replace",
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
const TagModifyResponse = 7
type ModifyResponse LDAPResult
//
//
//
//
//
//
//Sermersheim Standards Track [Page 58]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// AddRequest ::= [APPLICATION 8] SEQUENCE {
// entry LDAPDN,
// attributes AttributeList }
const TagAddRequest = 8
type AddRequest struct {
entry LDAPDN
attributes AttributeList
}
//
// AttributeList ::= SEQUENCE OF attribute Attribute
type AttributeList []Attribute
//
// AddResponse ::= [APPLICATION 9] LDAPResult
const TagAddResponse = 9
type AddResponse LDAPResult
//
// DelRequest ::= [APPLICATION 10] LDAPDN
const TagDelRequest = 10
type DelRequest LDAPDN
//
// DelResponse ::= [APPLICATION 11] LDAPResult
const TagDelResponse = 11
type DelResponse LDAPResult
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
const TagModifyDNRequest = 12
type ModifyDNRequest struct {
entry LDAPDN
newrdn RelativeLDAPDN
deleteoldrdn BOOLEAN
newSuperior *LDAPDN
}
const TagModifyDNRequestNewSuperior = 0
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
const TagModifyDNResponse = 13
type ModifyDNResponse LDAPResult
//
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
// entry LDAPDN,
// ava AttributeValueAssertion }
const TagCompareRequest = 14
type CompareRequest struct {
entry LDAPDN
ava AttributeValueAssertion
}
// CompareResponse ::= [APPLICATION 15] LDAPResult
const TagCompareResponse = 15
type CompareResponse LDAPResult
//
// AbandonRequest ::= [APPLICATION 16] MessageID
const TagAbandonRequest = 16
type AbandonRequest MessageID
//
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }
const TagExtendedRequest = 23
type ExtendedRequest struct {
requestName LDAPOID
requestValue *OCTETSTRING
}
const TagExtendedRequestName = 0
const TagExtendedRequestValue = 1
//
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }
const TagExtendedResponse = 24
type ExtendedResponse struct {
LDAPResult
responseName *LDAPOID
responseValue *OCTETSTRING
}
const TagExtendedResponseName = 10
const TagExtendedResponseValue = 11
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
const TagIntermediateResponse = 25
type IntermediateResponse struct {
responseName *LDAPOID
responseValue *OCTETSTRING
}
const TagIntermediateResponseName = 0
const TagIntermediateResponseValue = 1
//
// END
//

7
goldap/struct_methods.go Normal file
View file

@ -0,0 +1,7 @@
package message
func NewLDAPMessageWithProtocolOp(po ProtocolOp) *LDAPMessage {
m := NewLDAPMessage()
m.protocolOp = po
return m
}

38
goldap/unbind_request.go Normal file
View file

@ -0,0 +1,38 @@
package message
import "fmt"
//
// UnbindRequest ::= [APPLICATION 2] NULL
func readUnbindRequest(bytes *Bytes) (unbindrequest UnbindRequest, err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.ParseTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readUnbindRequest:\n%s", err.Error())}
return
}
err = tagAndLength.Expect(classApplication, TagUnbindRequest, isNotCompound)
if err != nil {
err = LdapError{fmt.Sprintf("readUnbindRequest:\n%s", err.Error())}
return
}
if tagAndLength.Length != 0 {
err = LdapError{"readUnbindRequest: expecting NULL"}
return
}
return
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
func (u UnbindRequest) write(bytes *Bytes) (size int) {
size += bytes.WriteTagAndLength(classApplication, isNotCompound, TagUnbindRequest, 0)
return
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
func (u UnbindRequest) size() (size int) {
size = sizeTagAndLength(TagUnbindRequest, 0)
return
}

32
goldap/uri.go Normal file
View file

@ -0,0 +1,32 @@
package message
import "fmt"
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func readURI(bytes *Bytes) (uri URI, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
// @TODO: check permitted chars in URI
if err != nil {
err = LdapError{fmt.Sprintf("readURI:\n%s", err.Error())}
return
}
uri = URI(ldapstring)
return
}
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func (u URI) write(bytes *Bytes) int {
return LDAPString(u).write(bytes)
}
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func (u URI) size() int {
return LDAPString(u).size()
}

6
goldap/write.go Normal file
View file

@ -0,0 +1,6 @@
package message
type Writable interface {
write(bytes *Bytes) int
writeTagged(bytes *Bytes, class int, tag int) int
}

19
goldap/write_test.go Normal file
View file

@ -0,0 +1,19 @@
package message
import (
"reflect"
"testing"
)
func TestWriteLDAPMessage(t *testing.T) {
var testData = getLDAPMessageTestData()
for i, test := range testData {
bytes, err := test.out.Write()
if err != nil {
t.Errorf("#%d error at offset %d (%s): %s\nEXPECTED BYTES: %#v\nWRITTEN BYTES: %#v\n", i, test.bytes.offset, test.bytes.DumpCurrentBytes(), err, test.bytes.getBytes(), bytes.getBytes())
} else if !reflect.DeepEqual(bytes.getBytes(), test.bytes.getBytes()) {
t.Errorf("#%d:\nGOT:\n%#+v\nEXPECTED:\n%#+v", i, bytes.getBytes(), test.bytes.getBytes())
}
}
}

153
gomod2nix.toml Normal file
View file

@ -0,0 +1,153 @@
schema = 3
[mod]
[mod."github.com/armon/circbuf"]
version = "v0.0.0-20150827004946-bbbad097214e"
hash = "sha256-klQjllsJZqZ2KPNx1mZT9XP+UAJkuBhmTnZdNlAflEM="
[mod."github.com/armon/go-metrics"]
version = "v0.0.0-20180917152333-f0300d1749da"
hash = "sha256-+zqX1hlJgc+IrXRzBQDMhR8GYQdc0Oj6PiIDfctgh44="
[mod."github.com/armon/go-radix"]
version = "v0.0.0-20180808171621-7fddfc383310"
hash = "sha256-ZHU4pyBqHHRuQJuYr2K+LqeAnLX9peX07cmSYK+GDHk="
[mod."github.com/bgentry/speakeasy"]
version = "v0.1.0"
hash = "sha256-Gt1vj6CFovLnO6wX5u2O4UfecY9V2J9WGw1ez4HMrgk="
[mod."github.com/davecgh/go-spew"]
version = "v1.1.1"
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
[mod."github.com/fatih/color"]
version = "v1.7.0"
hash = "sha256-4In7ef7it7d+6oGUJ3pkD0V+lsL40hVtYdy2KD2ovn0="
[mod."github.com/google/btree"]
version = "v0.0.0-20180813153112-4030bb1f1f0c"
hash = "sha256-5gr0RMnlvrzCke3kwpkf92WvW3x5nnKZesoulyoYRC0="
[mod."github.com/google/uuid"]
version = "v1.1.1"
hash = "sha256-66PXC/RCPUyhS9PhkIPQFR3tbM2zZYDNPGXN7JJj3UE="
[mod."github.com/hashicorp/consul/api"]
version = "v1.3.0"
hash = "sha256-fMORNFAWK2GkRbBl/KzNHrvtCfwz/7P66HzDaE4GnuE="
[mod."github.com/hashicorp/consul/sdk"]
version = "v0.3.0"
hash = "sha256-lF47JPGfmeGjpuQw9VSNs2Mi+G7FQLKrrHteX3iXfpU="
[mod."github.com/hashicorp/errwrap"]
version = "v1.0.0"
hash = "sha256-LGSLrefkABG1kH1i+GUWiD2/ggJxiZEJ+D2YNbhZjmo="
[mod."github.com/hashicorp/go-cleanhttp"]
version = "v0.5.1"
hash = "sha256-c54zcHr9THj3MQk7hrDQcpjOcQi1MvXZ4Kpin6EbfR4="
[mod."github.com/hashicorp/go-immutable-radix"]
version = "v1.0.0"
hash = "sha256-JmNxdGaJG63Ty/sVnPjqvTyA4/k5wkzZ/QvpMK2uduw="
[mod."github.com/hashicorp/go-msgpack"]
version = "v0.5.3"
hash = "sha256-2OUYjD/Jt12TFBrtH0wRqg+lzRljDxSIhk2CqBLUczo="
[mod."github.com/hashicorp/go-multierror"]
version = "v1.0.0"
hash = "sha256-iXzjerl96o7QDiSwQjbak8R/t+YzZeoUqm59TCmy3gI="
[mod."github.com/hashicorp/go-rootcerts"]
version = "v1.0.0"
hash = "sha256-4NZJAT5/vocyto+dv6FmW4kFiYldmNvewowsYK/LiTI="
[mod."github.com/hashicorp/go-sockaddr"]
version = "v1.0.0"
hash = "sha256-orG+SHVsp5lgNRCErmhMLABVFQ3ZWfYIJ/4LTFzlvao="
[mod."github.com/hashicorp/go-syslog"]
version = "v1.0.0"
hash = "sha256-YRuq6oPMwAFVY7mvwpMDvZqGwNnb5CjBYyKI/x5mbCc="
[mod."github.com/hashicorp/go-uuid"]
version = "v1.0.1"
hash = "sha256-s1wIvBu37z4U3qK9sdUR1CtbD39N6RwfX4HgDCpCa0s="
[mod."github.com/hashicorp/go.net"]
version = "v0.0.1"
hash = "sha256-JKal3E+wPO+Hk838opKV4HHKB4O72Xy+77ncXlLkWRk="
[mod."github.com/hashicorp/golang-lru"]
version = "v0.5.0"
hash = "sha256-Lo0UyOZQ89iK0Ui3Hfk5VkWqxEzLw6Lclq4EM8VlYoo="
[mod."github.com/hashicorp/logutils"]
version = "v1.0.0"
hash = "sha256-e8t8Dm8sp/PzKClN1TOmFcrTAWNh4mZHSW7cAjVx3Bw="
[mod."github.com/hashicorp/mdns"]
version = "v1.0.0"
hash = "sha256-ravx4tklQG43OEjPiJn68iJM9ODZ6hgU0idFCEOiJGM="
[mod."github.com/hashicorp/memberlist"]
version = "v0.1.3"
hash = "sha256-IsxqevYulPt+2VGtlq068Jyq1YfIk4Ohh9TgakIGxnc="
[mod."github.com/hashicorp/serf"]
version = "v0.8.2"
hash = "sha256-diRxWOouFLTO75f2E9NlrKgie/qsT+gOOrrbf4tACHw="
[mod."github.com/jsimonetti/pwscheme"]
version = "v0.0.0-20220125093853-4d9895f5db73"
hash = "sha256-YF3RKU/4CWvLPgGzUd7Hk/2+41OUFuRWZgzQuqcsKg0="
[mod."github.com/konsorten/go-windows-terminal-sequences"]
version = "v1.0.1"
hash = "sha256-Nwp+Cza9dIu3ogVGip6wyOjWwwaq+2hU3eYIe4R7kNE="
[mod."github.com/mattn/go-colorable"]
version = "v0.0.9"
hash = "sha256-fVPF8VxbdggLAZnaexMl6Id1WjXKImzVySxKfa+ukts="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.3"
hash = "sha256-9ogEEd8/kl/dImldzdBmTFdepvB0dVXEzN4o8bEqhBs="
[mod."github.com/miekg/dns"]
version = "v1.0.14"
hash = "sha256-OeijUgBaEmDapclTxfvjIqrjh4qZu3+DQpHelGGI4aA="
[mod."github.com/mitchellh/cli"]
version = "v1.0.0"
hash = "sha256-4nG7AhRcjTRCwUCdnaNaFrAKDxEEoiihaCA4lk+uM8U="
[mod."github.com/mitchellh/go-homedir"]
version = "v1.0.0"
hash = "sha256-eGmBNNTuDSMwicjTNgB54IEJuK1tgOLDJ3NHTpQCHzg="
[mod."github.com/mitchellh/go-testing-interface"]
version = "v1.0.0"
hash = "sha256-/Dpv/4i5xuK8hDH+q8YTdF6Jg6NNtfO4Wqig2JCWgrY="
[mod."github.com/mitchellh/gox"]
version = "v0.4.0"
hash = "sha256-GV3LYxzJt8YVbnSac2orlj2QR3MX/YIDrLkSkPhsjuA="
[mod."github.com/mitchellh/iochan"]
version = "v1.0.0"
hash = "sha256-b5Tp7cw/e8mL++IjsebbmKWXtb9Hrzu4Fc6M4tZKFhU="
[mod."github.com/mitchellh/mapstructure"]
version = "v1.1.2"
hash = "sha256-OU9HZYHtl0qaqMFd84w7snkkRuRY6UMSsfCnL5HYdw0="
[mod."github.com/pascaldekloe/goe"]
version = "v0.0.0-20180627143212-57f6aae5913c"
hash = "sha256-2KUjqrEC/BwkTZRxImazcI/C3H7QmXfNrlt8slwdDbc="
[mod."github.com/pkg/errors"]
version = "v0.8.1"
hash = "sha256-oe3iddfoLRwpC3ki5fifHf2ZFprtg99iNak50shiuDw="
[mod."github.com/pmezard/go-difflib"]
version = "v1.0.0"
hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
[mod."github.com/posener/complete"]
version = "v1.1.1"
hash = "sha256-heyPMSBzVlx7ZKgTyzl/xmUfZw3EZCcvGFGrRMIbIr8="
[mod."github.com/ryanuber/columnize"]
version = "v0.0.0-20160712163229-9b3edd62028f"
hash = "sha256-RLUQcU6Z03upKe08v6rjn9/tkyrQsgmpdEmBtWaLQfk="
[mod."github.com/sean-/seed"]
version = "v0.0.0-20170313163322-e2103e2c3529"
hash = "sha256-RQQTjvf8Y91jP5FGOyEnGMFw7zCrcSnUU4eH2CXKkT4="
[mod."github.com/sirupsen/logrus"]
version = "v1.4.2"
hash = "sha256-3QzWUsapCmg3F7JqUuINT3/UG097uzLff6iCcCgQ43o="
[mod."github.com/stretchr/objx"]
version = "v0.1.1"
hash = "sha256-HdGVZCuy7VprC5W9UxGbDmXqsKADMjpEDht7ilGVLco="
[mod."github.com/stretchr/testify"]
version = "v1.3.0"
hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA="
[mod."golang.org/x/crypto"]
version = "v0.0.0-20200604202706-70a84ac30bf9"
hash = "sha256-I5ov2lV6ubB3H3pn6DFN1VPLyxeLfPUaOx2PJ9K1KH8="
[mod."golang.org/x/net"]
version = "v0.0.0-20190404232315-eb5bcb51f2a3"
hash = "sha256-kwP+BDfPsZEeeceyg4H5LgdTDT8O8YWbkpCkpyuPJJo="
[mod."golang.org/x/sync"]
version = "v0.0.0-20181221193216-37e7f081c4d4"
hash = "sha256-FUpv9bmGLDEjCxXdvR0CRDqOKRY0xrHRPzOsyQyvYK0="
[mod."golang.org/x/sys"]
version = "v0.0.0-20190422165155-953cdadca894"
hash = "sha256-YkQjSZaiVFxrmXYSKmqrGeIO3RZ95fc3dWyCaR+SosY="
[mod."golang.org/x/text"]
version = "v0.3.0"
hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY="

View file

@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
ldap "github.com/lor00x/goldap/message" ldap "bottin/goldap"
) )
type UserState interface{} type UserState interface{}
@ -206,7 +206,10 @@ func (c *client) close() {
} }
func (c *client) writeMessage(m *ldap.LDAPMessage) { func (c *client) writeMessage(m *ldap.LDAPMessage) {
data, _ := m.Write() data, err := m.Write()
if err != nil {
Logger.Errorf("bottin: unable to marshal response message: %v", err)
}
//Logger.Printf(">>> %d - %s - hex=%x", c.Numero, m.ProtocolOpName(), data.Bytes()) //Logger.Printf(">>> %d - %s - hex=%x", c.Numero, m.ProtocolOpName(), data.Bytes())
Logger.Tracef(">>> [%d] %#v", c.Numero, m) Logger.Tracef(">>> [%d] %#v", c.Numero, m)

View file

@ -1,6 +1,6 @@
package ldapserver package ldapserver
import ldap "github.com/lor00x/goldap/message" import ldap "bottin/goldap"
// LDAP Application Codes // LDAP Application Codes
const ( const (

View file

@ -3,7 +3,7 @@ package ldapserver
import ( import (
"fmt" "fmt"
ldap "github.com/lor00x/goldap/message" ldap "bottin/goldap"
) )
type Message struct { type Message struct {

View file

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
ldap "github.com/lor00x/goldap/message" ldap "bottin/goldap"
) )
type messagePacket struct { type messagePacket struct {

View file

@ -1,6 +1,6 @@
package ldapserver package ldapserver
import ldap "github.com/lor00x/goldap/message" import ldap "bottin/goldap"
func NewBindResponse(resultCode int) ldap.BindResponse { func NewBindResponse(resultCode int) ldap.BindResponse {
r := ldap.BindResponse{} r := ldap.BindResponse{}

View file

@ -3,7 +3,7 @@ package ldapserver
import ( import (
"strings" "strings"
ldap "github.com/lor00x/goldap/message" ldap "bottin/goldap"
) )
// Constant to LDAP Request protocol Type names // Constant to LDAP Request protocol Type names

39
main.go
View file

@ -12,10 +12,10 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
message "bottin/goldap"
ldap "bottin/ldapserver" ldap "bottin/ldapserver"
consul "github.com/hashicorp/consul/api" consul "github.com/hashicorp/consul/api"
message "github.com/lor00x/goldap/message"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -320,7 +320,6 @@ func (server *Server) init() error {
return err return err
} }
admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW") admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if !environnement_variable_exist { if !environnement_variable_exist {
admin_pass := make([]byte, 8) admin_pass := make([]byte, 8)
@ -329,11 +328,15 @@ func (server *Server) init() error {
return err return err
} }
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass) admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
} else { } else {
server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password") server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password")
} }
admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) admin_pass_hash, err := SSHAEncode(admin_pass_str)
if err != nil {
server.logger.Error("can't create admin password")
panic(err)
}
admin_dn := "cn=admin," + server.config.Suffix admin_dn := "cn=admin," + server.config.Suffix
admin_attributes := Entry{ admin_attributes := Entry{
@ -434,8 +437,8 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
} }
for _, hash := range passwd { for _, hash := range passwd {
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple())) valid, err := SSHAMatches(hash, string(r.AuthenticationSimple()))
if valid { if valid && err == nil {
groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF) groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
if err != nil { if err != nil {
return ldap.LDAPResultOperationsError, err return ldap.LDAPResultOperationsError, err
@ -444,8 +447,32 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
user: string(r.Name()), user: string(r.Name()),
groups: groups, groups: groups,
} }
updatePasswordHash(string(r.AuthenticationSimple()), hash, server, string(r.Name()))
return ldap.LDAPResultSuccess, nil return ldap.LDAPResultSuccess, nil
} else {
return ldap.LDAPResultInvalidCredentials, fmt.Errorf("can't authenticate: %w", err)
} }
} }
return ldap.LDAPResultInvalidCredentials, fmt.Errorf("No password match") return ldap.LDAPResultInvalidCredentials, fmt.Errorf("No password match")
} }
// Update the hash if it's not already SSHA512
func updatePasswordHash(password string, currentHash string, server *Server, dn string) {
hashType, err := determineHashType(currentHash)
if err != nil {
server.logger.Errorf("can't determine hash type of password")
return
}
if hashType != SSHA512 {
reencodedPassword, err := SSHAEncode(password)
if err != nil {
server.logger.Errorf("can't encode password")
return
}
server.putAttributes(dn, Entry{
ATTR_USERPASSWORD: []string{reencodedPassword},
})
}
}

View file

@ -6,7 +6,7 @@ import (
ldap "bottin/ldapserver" ldap "bottin/ldapserver"
message "github.com/lor00x/goldap/message" message "bottin/goldap"
) )
// Generic read utility functions ---------- // Generic read utility functions ----------
@ -213,10 +213,11 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter,
continue continue
} }
// Send result // Send result
resultVals := []message.AttributeValue{}
for _, v := range val { for _, v := range val {
e.AddAttribute(message.AttributeDescription(attr), resultVals = append(resultVals, message.AttributeValue(v))
message.AttributeValue(v))
} }
e.AddAttribute(message.AttributeDescription(attr), resultVals...)
} }
w.Write(e) w.Write(e)
} }

70
ssha.go
View file

@ -1,25 +1,79 @@
package main package main
import ( import (
"errors"
"strings"
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
//"github.com/jsimonetti/pwscheme/ssha"
"github.com/jsimonetti/pwscheme/ssha256"
"github.com/jsimonetti/pwscheme/ssha512"
) )
const (
SSHA = "{SSHA}"
SSHA256 = "{SSHA256}"
SSHA512 = "{SSHA512}"
)
// Encode encodes the string to ssha512
func SSHAEncode(rawPassPhrase string) (string, error) {
return ssha512.Generate(rawPassPhrase, 16)
}
// Matches matches the encoded password and the raw password
func SSHAMatches(encodedPassPhrase string, rawPassPhrase string) (bool, error) {
hashType, err := determineHashType(encodedPassPhrase)
if err != nil {
return false, errors.New("invalid password hash stored")
}
var is_ok bool
switch hashType {
case SSHA:
is_ok = LegacySSHAMatches(encodedPassPhrase, []byte(rawPassPhrase))
return is_ok, nil
//return ssha.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA256:
return ssha256.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA512:
return ssha512.Validate(rawPassPhrase, encodedPassPhrase)
}
return false, errors.New("no matching hash type found")
}
func determineHashType(hash string) (string, error) {
if len(hash) >= 7 && strings.ToUpper(string(hash[0:6])) == SSHA {
return SSHA, nil
}
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA256 {
return SSHA256, nil
}
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA512 {
return SSHA512, nil
}
return "", errors.New("no valid hash found")
}
// --- legacy
// Encode encodes the []byte of raw password // Encode encodes the []byte of raw password
func SSHAEncode(rawPassPhrase []byte) string { func LegacySSHAEncode(rawPassPhrase []byte) string {
hash := makeSSHAHash(rawPassPhrase, makeSalt()) hash := legacyMakeSSHAHash(rawPassPhrase, legacyMakeSalt())
b64 := base64.StdEncoding.EncodeToString(hash) b64 := base64.StdEncoding.EncodeToString(hash)
return fmt.Sprintf("{ssha}%s", b64) return fmt.Sprintf("{ssha}%s", b64)
} }
// Matches matches the encoded password and the raw password // Matches matches the encoded password and the raw password
func SSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool { func LegacySSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
if !strings.EqualFold(encodedPassPhrase[:6], "{ssha}") { if !strings.EqualFold(encodedPassPhrase[:6], "{ssha}") {
return false return false
} }
@ -30,7 +84,7 @@ func SSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
} }
salt := bhash[20:] salt := bhash[20:]
newssha := makeSSHAHash(rawPassPhrase, salt) newssha := legacyMakeSSHAHash(rawPassPhrase, salt)
if bytes.Compare(newssha, bhash) != 0 { if bytes.Compare(newssha, bhash) != 0 {
return false return false
@ -39,7 +93,7 @@ func SSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
} }
// makeSalt make a 32 byte array containing random bytes. // makeSalt make a 32 byte array containing random bytes.
func makeSalt() []byte { func legacyMakeSalt() []byte {
sbytes := make([]byte, 32) sbytes := make([]byte, 32)
_, err := rand.Read(sbytes) _, err := rand.Read(sbytes)
if err != nil { if err != nil {
@ -49,7 +103,7 @@ func makeSalt() []byte {
} }
// makeSSHAHash make hasing using SHA-1 with salt. This is not the final output though. You need to append {SSHA} string with base64 of this hash. // makeSSHAHash make hasing using SHA-1 with salt. This is not the final output though. You need to append {SSHA} string with base64 of this hash.
func makeSSHAHash(passphrase, salt []byte) []byte { func legacyMakeSSHAHash(passphrase, salt []byte) []byte {
sha := sha1.New() sha := sha1.New()
sha.Write(passphrase) sha.Write(passphrase)
sha.Write(salt) sha.Write(salt)

View file

@ -147,14 +147,12 @@ func TestDeleteGroupsAfterAddedUsers(t *testing.T) {
//Example of paralellism Test //Example of paralellism Test
func TestPrincipal(t *testing.T) { func TestPrincipal(t *testing.T) {
t.Run("A=Add and delete", TestAddThenDelete)
t.Run("A=1", TestAddThenDelete) t.Run("A=Modify", TestModifyRequest)
t.Run("A=2", TestModifyRequest)
if !testing.Short() { if !testing.Short() {
t.Run("B=1", TestConfirmAddAttributes) t.Run("B=Add attributes", TestConfirmAddAttributes)
t.Run("B=2", TestModifyRequestAndCheck) t.Run("B=Modify and check", TestModifyRequestAndCheck)
t.Run("C=1", TestAddUserInGroup) t.Run("C=Add user in group", TestAddUserInGroup)
t.Run("C=2", TestDeleteGroupsAfterAddedUsers) t.Run("C=Delete group", TestDeleteGroupsAfterAddedUsers)
} }
} }

View file

@ -32,7 +32,9 @@ func PrintError(LDAPError error) {
//Generate an unique name, which store in all_names //Generate an unique name, which store in all_names
func (inst *instance) GenerateName() (name string) { func (inst *instance) GenerateName() (name string) {
alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
"Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "é", "è", "ê", "ë", "à", "@", "â", "ä", "û", "ü", "ù", "$", "£", "%", "ø", "€"}
length := R.Intn(maxlength_generateName) + minlength_generateName length := R.Intn(maxlength_generateName) + minlength_generateName
//Check if this name not exist already //Check if this name not exist already
@ -41,7 +43,7 @@ func (inst *instance) GenerateName() (name string) {
for only_one := true; only_one; _, only_one = allNames.cn[name] { for only_one := true; only_one; _, only_one = allNames.cn[name] {
//Create the name //Create the name
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
name += string(alphabet[R.Intn(len(alphabet))]) name += alphabet[R.Intn(len(alphabet))]
} }
} }
@ -52,18 +54,6 @@ func (inst *instance) GenerateName() (name string) {
return return
} }
//Handler to around the bug with MessageId
func (inst *instance) Reconnect() (err error) {
inst.logging.Close()
inst.logging, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port))
if err != nil {
return
}
err = inst.logging.Bind(bindusername, bindpassword)
//logging.Debug("Reconnect succesful")
return
}
//Transform attributes in map format to the struct attributes //Transform attributes in map format to the struct attributes
func MapAttToStruct(att map[string][]string) []attributes { func MapAttToStruct(att map[string][]string) []attributes {
resultat := []attributes{} resultat := []attributes{}
@ -86,7 +76,7 @@ func Connect() (*ldap.Conn, error) {
if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok { if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok {
bindpassword = key bindpassword = key
} }
//l.Debug.Enable(true)
err = l.Bind(bindusername, bindpassword) err = l.Bind(bindusername, bindpassword)
logging.Debug("Connection succesful") logging.Debug("Connection succesful")
return l, err return l, err

View file

@ -13,8 +13,6 @@ func (inst *instance) Add_Request(dn string, attributes map[string][]string) err
//Send the request //Send the request
err := inst.logging.Add(req) err := inst.logging.Add(req)
//@FIXME: Remove when you try to correct the bug MessageID
inst.Reconnect()
return err return err
} }
@ -36,8 +34,6 @@ func (inst *instance) Modify_Request(dn string, add_attributes, delete_attribute
} }
err := inst.logging.Modify(modifyReq) err := inst.logging.Modify(modifyReq)
//@FIXME: Remove when you try to correct the bug MessageID
inst.Reconnect()
return err return err
} }
@ -45,8 +41,6 @@ func (inst *instance) Delete_Request(dn string) error {
del := ldap.NewDelRequest(dn, nil) del := ldap.NewDelRequest(dn, nil)
err := inst.logging.Del(del) err := inst.logging.Del(del)
//@FIXME: Remove when you try to correct the bug MessageID
inst.Reconnect()
return err return err
} }
@ -61,7 +55,5 @@ func (inst *instance) Search_Request(dn, filter string, name_attributes []string
res, err := inst.logging.Search(searchReq) res, err := inst.logging.Search(searchReq)
logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes) logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes)
//@FIXME: Remove when you try to correct the bug MessageID
inst.Reconnect()
return res, err return res, err
} }

View file

@ -6,7 +6,11 @@ echo $BOTTIN_DEFAULT_ADMIN_PW
consul agent -dev > /dev/null 2>&1 & consul agent -dev > /dev/null 2>&1 &
sleep 2 sleep 2
cp test/config.json.test config.json cp test/config.json.test config.json
./bottin > /dev/null 2>&1 & ./bottin > /tmp/bottin.log 2>&1 &
sleep 1 sleep 1
./test/test -test.v -test.failfast -test.short -test.run TestPrincipal ./test/test -test.v -test.failfast -test.short -test.run TestPrincipal
./test/test -test.v -test.failfast -test.run TestPrincipal/B= ./test/test -test.v -test.failfast -test.run TestPrincipal/B=
jobs
kill %2
kill %1

View file

@ -7,8 +7,9 @@ import (
ldap "bottin/ldapserver" ldap "bottin/ldapserver"
message "bottin/goldap"
consul "github.com/hashicorp/consul/api" consul "github.com/hashicorp/consul/api"
message "github.com/lor00x/goldap/message"
) )
// Generic item modification function -------- // Generic item modification function --------
@ -38,7 +39,7 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
// Retreieve previously existing attributes, which we will use to delete // Retreieve previously existing attributes, which we will use to delete
// entries with the wrong case // entries with the wrong case
previous_pairs, _, err := server.kv.List(prefix + "/attribute=", &server.readOpts) previous_pairs, _, err := server.kv.List(prefix+"/attribute=", &server.readOpts)
if err != nil { if err != nil {
return err return err
} }
@ -65,6 +66,24 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
} }
} }
// if the password is not yet hashed we hash it
if k == ATTR_USERPASSWORD {
tmpValues := []string{}
for _, pw := range values {
_, err := determineHashType(pw)
if err != nil {
encodedPassword, err := SSHAEncode(pw)
if err != nil {
return err
}
tmpValues = append(tmpValues, encodedPassword)
} else {
tmpValues = append(tmpValues, pw)
}
}
values = tmpValues
}
// If we have zero values, delete associated k/v pair // If we have zero values, delete associated k/v pair
// Otherwise, write new values // Otherwise, write new values
if len(values) == 0 { if len(values) == 0 {