mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
auth: Implement LDAP BindDN lookups
Currently connection management code is rather native, there is definitely a room for improvements (e.g. pooling?) Closes #273
This commit is contained in:
parent
3ce9279f39
commit
e0792c2dbb
5 changed files with 411 additions and 12 deletions
|
@ -257,3 +257,117 @@ format.
|
|||
|
||||
tcp://10.0.0.1:2222 for TCP, unix:///var/lib/dovecot/auth.sock for Unix
|
||||
domain sockets.
|
||||
|
||||
# LDAP BindDN authentication (EXPERIMENTAL) (auth.ldap)
|
||||
|
||||
maddy supports authentication via LDAP using DN binding. Passwords are verified
|
||||
by the LDAP server.
|
||||
|
||||
maddy needs to know the DN to use for binding. It can be obtained either by
|
||||
directory search or template .
|
||||
|
||||
Note that storage backends conventionally use email addresses, if you use
|
||||
non-email identifiers as usernames then you should map them onto
|
||||
emails on delivery by using auth_map (see *maddy-storage*(5)).
|
||||
|
||||
auth.ldap also can be a used as a table module. This way you can check
|
||||
whether the account exists. It works only if DN template is not used.
|
||||
|
||||
```
|
||||
auth.ldap {
|
||||
urls ldap://maddy.test:389
|
||||
|
||||
# Specify initial bind credentials. Not required ('bind off')
|
||||
# if DN template is used.
|
||||
bind plain "cn=maddy,ou=people,dc=maddy,dc=test" "123456"
|
||||
|
||||
# Specify DN template to skip lookup.
|
||||
dn_template "cn={username},ou=people,dc=maddy,dc=test"
|
||||
|
||||
# Specify base_dn and filter to lookup DN.
|
||||
base_dn "ou=people,dc=maddy,dc=test"
|
||||
filter "(&(objectClass=posixAccount)(uid={username}))"
|
||||
|
||||
tls_client { ... }
|
||||
starttls off
|
||||
debug off
|
||||
connect_timeout 1m
|
||||
}
|
||||
```
|
||||
```
|
||||
auth.ldap ldap://maddy.test.389 {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* urls _servers..._
|
||||
|
||||
REQUIRED.
|
||||
|
||||
URLs of the directory servers to use. First available server
|
||||
is used - no load-balancing is done.
|
||||
|
||||
URLs should use 'ldap://', 'ldaps://', 'ldapi://' schemes.
|
||||
|
||||
*Syntax:* bind off ++
|
||||
bind unauth ++
|
||||
bind external ++
|
||||
bind plain _username_ _password_ ++
|
||||
*Default:* off
|
||||
|
||||
Credentials to use for initial binding. Required if DN lookup is used.
|
||||
|
||||
'unauth' performs unauthenticated bind. 'external' performs external binding
|
||||
which is useful for Unix socket connections (ldapi://) or TLS client certificate
|
||||
authentication (cert. is set using tls_client directive). 'plain' performs a
|
||||
simple bind using provided credentials.
|
||||
|
||||
*Syntax:* dn_template _template_
|
||||
|
||||
DN template to use for binding. '{username}' is replaced with the
|
||||
username specified by the user.
|
||||
|
||||
*Syntax:* base_dn _dn_
|
||||
|
||||
Base DN to use for lookup.
|
||||
|
||||
*Syntax:* filter _str_
|
||||
|
||||
DN lookup filter. '{username}' is replaced with the username specified
|
||||
by the user.
|
||||
|
||||
Example:
|
||||
```
|
||||
(&(objectClass=posixAccount)(uid={username}))
|
||||
```
|
||||
|
||||
Example (using ActiveDirectory):
|
||||
```
|
||||
(&(objectCategory=Person)(memberOf=CN=user-group,OU=example,DC=example,DC=org)(sAMAccountName={username})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
(&(objectClass=Person)(mail={username}))
|
||||
```
|
||||
|
||||
*Syntax:* starttls _bool_ ++
|
||||
*Default:* off
|
||||
|
||||
Whether to upgrade connection to TLS using STARTTLS.
|
||||
|
||||
*Syntax:* tls_client { ... }
|
||||
|
||||
Advanced TLS client configuration. See *maddy-tls*(5) for details.
|
||||
|
||||
*Syntax:* connect_timeout _duration_ ++
|
||||
*Default:* 1m
|
||||
|
||||
Timeout for initial connection to the directory server.
|
||||
|
||||
*Syntax:* request_timeout _duration_ ++
|
||||
*Default:* 1m
|
||||
|
||||
Timeout for each request (binding, lookup).
|
2
go.mod
2
go.mod
|
@ -26,8 +26,8 @@ require (
|
|||
github.com/foxcpp/go-imap-sql v0.4.1-0.20200823124337-2f57903a7ed0
|
||||
github.com/foxcpp/go-mockdns v0.0.0-20201212160233-ede2f9158d15
|
||||
github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/klauspost/compress v1.11.13 // indirect
|
||||
github.com/lib/pq v1.10.0
|
||||
|
|
30
go.sum
30
go.sum
|
@ -38,8 +38,11 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
|||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
|
||||
|
@ -181,12 +184,16 @@ github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3g
|
|||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
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/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
|
@ -234,6 +241,7 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
|
|||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
|
@ -252,9 +260,11 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
|
@ -289,6 +299,7 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
|
@ -327,6 +338,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
|
@ -398,8 +410,6 @@ github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk=
|
|||
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
|
||||
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
|
@ -508,10 +518,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
|
@ -563,10 +573,9 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -592,6 +601,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
|
|||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
|
@ -603,6 +613,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -647,8 +658,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -733,9 +742,6 @@ golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -811,11 +817,12 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
@ -958,6 +965,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
|
|
276
internal/auth/ldap/ldap.go
Normal file
276
internal/auth/ldap/ldap.go
Normal file
|
@ -0,0 +1,276 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
const modName = "auth.ldap"
|
||||
|
||||
type Auth struct {
|
||||
instName string
|
||||
|
||||
urls []string
|
||||
readBind func(*ldap.Conn) error
|
||||
startls bool
|
||||
tlsCfg tls.Config
|
||||
dialer *net.Dialer
|
||||
requestTimeout time.Duration
|
||||
|
||||
dnTemplate string
|
||||
// or
|
||||
baseDN string
|
||||
filterTemplate string
|
||||
|
||||
conn *ldap.Conn
|
||||
connLock sync.Mutex
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func New(modName, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
return &Auth{
|
||||
instName: instName,
|
||||
log: log.Logger{Name: modName},
|
||||
urls: inlineArgs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Auth) Init(cfg *config.Map) error {
|
||||
a.dialer = &net.Dialer{}
|
||||
|
||||
cfg.Bool("debug", true, false, &a.log.Debug)
|
||||
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
|
||||
return tls.Config{}, nil
|
||||
}, tls2.TLSClientBlock, &a.tlsCfg)
|
||||
cfg.Callback("urls", func(m *config.Map, node config.Node) error {
|
||||
a.urls = append(a.urls, node.Args...)
|
||||
return nil
|
||||
})
|
||||
cfg.Custom("bind", false, false, func() (interface{}, error) {
|
||||
return func(*ldap.Conn) error {
|
||||
return nil
|
||||
}, nil
|
||||
}, readBindDirective, &a.readBind)
|
||||
cfg.Bool("starttls", false, false, &a.startls)
|
||||
cfg.Duration("connect_timeout", false, false, time.Minute, &a.dialer.Timeout)
|
||||
cfg.Duration("request_timeout", false, false, time.Minute, &a.requestTimeout)
|
||||
cfg.String("dn_template", false, false, "", &a.dnTemplate)
|
||||
cfg.String("base_dn", false, false, "", &a.baseDN)
|
||||
cfg.String("filter", false, false, "", &a.filterTemplate)
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.dnTemplate == "" {
|
||||
if a.baseDN == "" {
|
||||
return fmt.Errorf("auth.ldap: base_dn not set")
|
||||
}
|
||||
if a.filterTemplate == "" {
|
||||
return fmt.Errorf("auth.ldap: filter not set")
|
||||
}
|
||||
} else {
|
||||
if a.baseDN != "" || a.filterTemplate != "" {
|
||||
return fmt.Errorf("auth.ldap: search directives set when dn_template is used")
|
||||
}
|
||||
}
|
||||
|
||||
if module.NoRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
a.conn, err = a.newConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBindDirective(c *config.Map, n config.Node) (interface{}, error) {
|
||||
if len(n.Args) == 0 {
|
||||
return nil, fmt.Errorf("auth.ldap: auth expects at least one argument")
|
||||
}
|
||||
switch n.Args[0] {
|
||||
case "off":
|
||||
return func(*ldap.Conn) error { return nil }, nil
|
||||
case "unauth":
|
||||
return (*ldap.Conn).UnauthenticatedBind, nil
|
||||
case "plain":
|
||||
if len(n.Args) != 3 {
|
||||
return nil, fmt.Errorf("auth.ldap: username and password expected for plaintext bind")
|
||||
}
|
||||
return func(c *ldap.Conn) error {
|
||||
return c.Bind(n.Args[1], n.Args[2])
|
||||
}, nil
|
||||
case "external":
|
||||
return (*ldap.Conn).ExternalBind, nil
|
||||
}
|
||||
return nil, fmt.Errorf("auth.ldap: unknown bind authentication: %v", n.Args[0])
|
||||
}
|
||||
|
||||
func (a *Auth) Name() string {
|
||||
return modName
|
||||
}
|
||||
|
||||
func (a *Auth) InstanceName() string {
|
||||
return a.instName
|
||||
}
|
||||
|
||||
func (a *Auth) newConn() (*ldap.Conn, error) {
|
||||
var (
|
||||
conn *ldap.Conn
|
||||
tlsCfg *tls.Config
|
||||
)
|
||||
for _, u := range a.urls {
|
||||
parsedURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: invalid server URL: %w", err)
|
||||
}
|
||||
hostname := parsedURL.Host
|
||||
tlsCfg = a.tlsCfg.Clone()
|
||||
a.tlsCfg.ServerName = hostname
|
||||
|
||||
conn, err = ldap.DialURL(u, ldap.DialWithDialer(a.dialer), ldap.DialWithTLSConfig(tlsCfg))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if a.requestTimeout != 0 {
|
||||
conn.SetTimeout(a.requestTimeout)
|
||||
}
|
||||
|
||||
if a.startls {
|
||||
if err := conn.StartTLS(tlsCfg); err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.readBind(conn); err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (a *Auth) getConn() (*ldap.Conn, error) {
|
||||
a.connLock.Lock()
|
||||
if a.conn == nil {
|
||||
conn, err := a.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
if a.conn.IsClosing() {
|
||||
a.conn.Close()
|
||||
conn, err := a.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
return a.conn, nil
|
||||
}
|
||||
|
||||
func (a *Auth) returnConn(conn *ldap.Conn) {
|
||||
defer a.connLock.Unlock()
|
||||
if err := a.readBind(conn); err != nil {
|
||||
a.log.Error("failed to rebind for reading", err)
|
||||
conn.Close()
|
||||
a.conn = nil
|
||||
}
|
||||
if a.conn != conn {
|
||||
a.conn.Close()
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
|
||||
func (a *Auth) Lookup(_ context.Context, username string) (string, bool, error) {
|
||||
conn, err := a.getConn()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer a.returnConn(conn)
|
||||
|
||||
var userDN string
|
||||
if a.dnTemplate != "" {
|
||||
return "", false, fmt.Errorf("auth.ldap: lookups require search config but dn_template is used")
|
||||
} else {
|
||||
req := ldap.NewSearchRequest(
|
||||
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
2, 0, false,
|
||||
strings.ReplaceAll(a.filterTemplate, "{username}", username),
|
||||
[]string{"dn"}, nil)
|
||||
res, err := conn.Search(req)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("auth.ldap: search: %w", err)
|
||||
}
|
||||
if len(res.Entries) > 1 {
|
||||
return "", false, fmt.Errorf("auth.ldap: too manu entries returned (%d)", len(res.Entries))
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
userDN = res.Entries[0].DN
|
||||
}
|
||||
|
||||
return userDN, true, nil
|
||||
}
|
||||
|
||||
func (a *Auth) AuthPlain(username, password string) error {
|
||||
conn, err := a.getConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.returnConn(conn)
|
||||
|
||||
var userDN string
|
||||
if a.dnTemplate != "" {
|
||||
userDN = strings.ReplaceAll(a.dnTemplate, "{username}", username)
|
||||
} else {
|
||||
req := ldap.NewSearchRequest(
|
||||
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
2, 0, false,
|
||||
strings.ReplaceAll(a.filterTemplate, "{username}", username),
|
||||
[]string{"dn"}, nil)
|
||||
res, err := conn.Search(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth.ldap: search: %w", err)
|
||||
}
|
||||
if len(res.Entries) > 1 {
|
||||
return fmt.Errorf("auth.ldap: too manu entries returned (%d)", len(res.Entries))
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return module.ErrUnknownCredentials
|
||||
}
|
||||
userDN = res.Entries[0].DN
|
||||
}
|
||||
|
||||
if err := conn.Bind(userDN, password); err != nil {
|
||||
return module.ErrUnknownCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
var _ module.PlainAuth = &Auth{}
|
||||
var _ module.Table = &Auth{}
|
||||
module.Register(modName, New)
|
||||
module.Register("table.ldap", New)
|
||||
}
|
1
maddy.go
1
maddy.go
|
@ -41,6 +41,7 @@ import (
|
|||
// Import packages for side-effect of module registration.
|
||||
_ "github.com/foxcpp/maddy/internal/auth/dovecot_sasl"
|
||||
_ "github.com/foxcpp/maddy/internal/auth/external"
|
||||
_ "github.com/foxcpp/maddy/internal/auth/ldap"
|
||||
_ "github.com/foxcpp/maddy/internal/auth/pam"
|
||||
_ "github.com/foxcpp/maddy/internal/auth/pass_table"
|
||||
_ "github.com/foxcpp/maddy/internal/auth/plain_separate"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue