1package ldap
2
3import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "log/slog"
8 "net"
9 "time"
10
11 "tug/internal/platform/ratelimit"
12 "tug/internal/service/refusalcap"
13 "tug/internal/storage"
14 fk "tug/internal/transport/ldap/internal/glauthldap"
15)
16
17const (
18 readTimeout = 2 * time.Minute
19 writeTimeout = 30 * time.Second
20 maxRequestBytes = 64 << 10
21)
22
23var errNoStore = errors.New("ldap: no store")
24
25type Config struct {
26 BaseDN string
27 PrimaryGroup string
28 TLSCert string
29 TLSKey string
30 Observe func(result string)
31 Refusals *refusalcap.Cap
32}
33
34type Server struct {
35 fork *fk.Server
36 dir *directory
37 tls *tls.Config
38 logger *slog.Logger
39 backoff *backoff
40 peers *peerLimit
41 observe func(string)
42 cursors *cursors
43 binds *bindWindow
44 refusal *refusalcap.Cap
45 ctx context.Context
46 cancel context.CancelFunc
47}
48
49type refuser struct{}
50
51func (refuser) Add(string, fk.AddRequest, net.Conn) (fk.LDAPResultCode, error) {
52 return fk.LDAPResultUnwillingToPerform, nil
53}
54
55func (refuser) Modify(string, fk.ModifyRequest, net.Conn) (fk.LDAPResultCode, error) {
56 return fk.LDAPResultUnwillingToPerform, nil
57}
58
59func (refuser) Delete(string, string, net.Conn) (fk.LDAPResultCode, error) {
60 return fk.LDAPResultUnwillingToPerform, nil
61}
62
63func (refuser) ModifyDN(string, fk.ModifyDNRequest, net.Conn) (fk.LDAPResultCode, error) {
64 return fk.LDAPResultUnwillingToPerform, nil
65}
66
67func (refuser) Compare(string, fk.CompareRequest, net.Conn) (fk.LDAPResultCode, error) {
68 return fk.LDAPResultUnwillingToPerform, nil
69}
70
71func (refuser) Extended(string, fk.ExtendedRequest, net.Conn) (fk.LDAPResultCode, error) {
72 return fk.LDAPResultProtocolError, nil
73}
74
75func New(store *storage.Store, cfg Config, logger *slog.Logger) (*Server, error) {
76 if store == nil {
77 return nil, errNoStore
78 }
79 dir, err := newDirectory(store, cfg.BaseDN, cfg.PrimaryGroup)
80 if err != nil {
81 return nil, err
82 }
83 var tlsConfig *tls.Config
84 if cfg.TLSCert != "" || cfg.TLSKey != "" {
85 pair, err := loadKeyPair(cfg.TLSCert, cfg.TLSKey)
86 if err != nil {
87 return nil, err
88 }
89 tlsConfig = &tls.Config{Certificates: []tls.Certificate{pair}, MinVersion: tls.VersionTLS12}
90 }
91 ctx, cancel := context.WithCancel(context.Background())
92 s := &Server{
93 dir: dir,
94 tls: tlsConfig,
95 logger: logger,
96 backoff: newBackoff(time.Now, backoffEntries),
97 peers: newPeerLimit(time.Now, ratelimit.Rate{Burst: peerBurst, Every: peerEvery}, peerEntries),
98 observe: observeOrDiscard(cfg.Observe),
99 cursors: &cursors{byConn: map[net.Conn][]*cursor{}},
100 binds: newBindWindow(time.Now, bindWindowEntries),
101 refusal: refusalsOrOwn(cfg.Refusals, store, logger),
102 ctx: ctx,
103 cancel: cancel,
104 }
105 f := fk.NewServer()
106 f.ReadTimeout = readTimeout
107 f.WriteTimeout = writeTimeout
108 f.MaxRequestBytes = maxRequestBytes
109 f.BindFunc("", handler{s})
110 f.StructuredSearchFunc("", handler{s})
111 f.AddFunc("", refuser{})
112 f.ModifyFunc("", refuser{})
113 f.DeleteFunc("", refuser{})
114 f.ModifyDNFunc("", refuser{})
115 f.CompareFunc("", refuser{})
116 f.ExtendedFunc("", refuser{})
117 f.CloseFunc("", handler{s})
118 s.fork = f
119 return s, nil
120}
121
122type handler struct{ s *Server }
123
124func (h handler) Bind(bindDN, bindSimplePw string, conn net.Conn) (fk.LDAPResultCode, error) {
125 code, result := h.s.bindFrom(peerAddr(conn), bindDN, bindSimplePw)
126 h.s.observe(result)
127 return code, nil
128}
129
130func (h handler) SearchStructured(boundDN string, req fk.StructuredSearchRequest, conn net.Conn) (fk.ServerSearchResult, error) {
131 return h.s.searchStructured(boundDN, req, conn)
132}
133
134func (h handler) Close(boundDN string, conn net.Conn) error {
135 h.s.cursors.drop(conn)
136 return conn.Close()
137}
138
139func (s *Server) Serve(ln net.Listener) error {
140 if s.tls != nil {
141 ln = tls.NewListener(ln, s.tls)
142 }
143 return s.fork.Serve(ln)
144}
145
146func (s *Server) Close() {
147 s.cancel()
148 s.fork.Close()
149}
150
151func (s *Server) Ready(ctx context.Context) error {
152 _, err := s.dir.store.GroupByName(ctx, s.dir.primaryGroup)
153 if err != nil && !errors.Is(err, storage.ErrNotFound) {
154 return err
155 }
156 return nil
157}
158
159func observeOrDiscard(observe func(string)) func(string) {
160 if observe == nil {
161 return func(string) {}
162 }
163 return observe
164}