authclient

package
v2.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package authclient is the Go client for the yarilo-auth master protocol. It opens a single mTLS TCP connection to the master listener (see internal/auth/protocol/master.go), drains the handshake, and provides typed methods for the wire commands:

One Client owns one TCP conn. Commands serialise behind a mutex, so multiple goroutines may call methods concurrently; wire-side ordering is enforced internally. For higher throughput, open additional Clients; there is no pool.

Index

Constants

This section is empty.

Variables

View Source
var ErrClosed = errors.New("authclient: client closed")

ErrClosed signals that a Client was Closed or its connection terminated. Subsequent calls return it so callers can re-Dial rather than hang.

View Source
var ErrNotImplemented = errors.New("authclient: PASS not implemented (Phase AUTH-2)")

ErrNotImplemented is returned by Client.PassdbLookup while the master PASS handler still replies `FAIL reason=PASS not implemented`. Callers can errors.Is against it to gate fall-through behaviour.

View Source
var ErrUnavailable = errors.New("authclient: service unavailable")

ErrUnavailable marks a failure to REACH yarilo-auth, as opposed to an answer from it. The distinction is the whole point: a protocol above can say "try again in a moment" for a service being redeployed, where a client told "this server is broken" stops retrying a request that would work seconds later.

The same seam as locks.ErrUnavailable (#1339), for the other dependency: the jmap and fts user resolvers reach auth on the request path, and without a marker their transport failures arrived at the classifiers as ordinary errors and became serverFail / a bare NO (#1402).

Carried only by transport failures. An answer from the service -- NOTFOUND, FAIL, a malformed or unexpected response -- is not unavailability, however unwelcome it is.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client speaks the yarilo-auth master protocol over a single TCP (optionally mTLS-wrapped) connection. Methods are safe for concurrent use: a mutex enforces one in-flight request at a time on the underlying conn.

When the connection breaks (e.g. yarilo-auth restart) the next call redials and retries once. Client.Close disables reconnect permanently.

Construct with Dial / DialContext. Always Client.Close on shutdown so the FIN reaches the server promptly.

func Dial

func Dial(addr string, tlsCfg *tls.Config) (*Client, error)

Dial opens a connection to the yarilo-auth master listener at addr and consumes the handshake. A non-nil tlsCfg wraps the connection in (m)TLS. Only major version 1 is accepted; other versions return a typed error.

func DialContext

func DialContext(ctx context.Context, addr string, tlsCfg *tls.Config) (*Client, error)

DialContext is Dial with a context that bounds the TCP + TLS handshake. After the handshake the context no longer affects the returned Client.

func DialWaiting

func DialWaiting(ctx context.Context, addr string, tlsCfg *tls.Config, wait time.Duration) (*Client, error)

DialWaiting is Dial with a bounded wait for the service to come up.

The two contracts differ and this is the startup one. A process starting while auth is rolling has nobody to tell: exiting turns a few seconds of dependency downtime into a restart loop (#1369). On a REQUEST the opposite holds -- a client is waiting for an answer, and a fast refusal beats a hang -- which is why the per-request dials deliberately do not use this.

A wait of zero or less is a plain Dial, so the waiting can be turned off without a second code path.

func (*Client) CacheFlush

func (c *Client) CacheFlush(ctx context.Context, masks []string) (uint32, error)

CacheFlush invokes the CACHE-FLUSH verb. An empty masks slice flushes everything; otherwise each mask is sent as a tab-separated argument and entries whose username matches any mask are evicted (glob: `*`, `?`). Returns the evicted-entry count reported by the server.

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying connection. Idempotent. After Close every other method returns ErrClosed.

func (*Client) IssueSession

func (c *Client) IssueSession(ctx context.Context, username, sid, ip string) (string, error)

IssueSession sends a SESSION command and returns a one-time token bound to username. The token is scoped to the "lmtp" service and must be consumed by a VERIFY on the client listener within its TTL.

sid is the warden session ID for this delivery; ip is the originating MTA address. Both are audit-logged only and do not affect token validity.

func (*Client) IterateUsers

func (c *Client) IterateUsers(ctx context.Context) ([]string, error)

IterateUsers runs a LIST and collects streamed usernames until the DONE marker. Backends without enumeration reply FAIL, surfaced as an error.

func (*Client) PassdbLookup

func (c *Client) PassdbLookup(ctx context.Context, username string) (*protocol.UserInfo, error)

PassdbLookup runs a PASS lookup. The master PASS handler still replies `FAIL reason=PASS not implemented`, so this method always returns ErrNotImplemented; the typed call surface is stable for when it lands.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping asks the master for a sign of life on this connection.

ANY single-line reply counts, including a FAIL: a server too old to know NOOP answers "unknown command", and a server that answers at all is exactly what the probe is asking about. That makes the probe safe against a mixed rollout with no ordering requirement -- the reply's content is not the signal, its arrival is.

func (*Client) Userdb

func (c *Client) Userdb(ctx context.Context, username string) (*protocol.UserInfo, error)

Userdb runs a USER lookup for username. Returns:

  • (ui, nil) - backend hit; ui populated from the wire fields
  • (nil, nil) - backend miss (master responded NOTFOUND)
  • (nil, err) - wire / parse / backend error

Mirrors the protocol.Userdb "not found is nil/nil" contract.

func (*Client) UserdbFor

func (c *Client) UserdbFor(ctx context.Context, username, service string) (*protocol.UserInfo, error)

UserdbFor is Userdb on behalf of a protocol: a login or a delivery names itself, so a userdb rule on %s can refuse it (#2227). Empty sends none.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool keeps a small number of master-protocol connections open for userdb lookups, instead of dialling one per lookup.

The dial is about seven times the work it carries: 3.1ms of TCP, TLS and the master's greeting against a 0.44ms USER lookup, measured against the real service (#1402). It is also a connection per lookup arriving at yarilo-auth -- ~10/s from a modest sandbox load, each with a full handshake on the server side too, which is a cost that lands on the dependency rather than on us.

Modelled on pkg/locks: fixed slots handed out through a channel, so a slot is owned exclusively by whoever holds it and lookups on one connection stay serialised, which the master protocol requires.

func NewPool

func NewPool(addr string, tlsCfg *tls.Config, size int, idleTimeout time.Duration) *Pool

NewPool returns a pool of size connections to addr. A size of zero or less is not an error and not a pool: callers get a fresh connection per lookup, which is the behaviour this replaces and the rollback for it.

Nothing is dialled here. A pod that starts while auth is rolling must not fail because of it (#1369); the first lookup pays for the first connection.

func (*Pool) Close

func (p *Pool) Close() error

Close releases every pooled connection. Idempotent.

func (*Pool) IssueSession

func (p *Pool) IssueSession(ctx context.Context, username, sid, ip string) (string, error)

IssueSession issues a session token, reusing a pooled connection when one is available. Same contract as Userdb: the call travels to a connection rather than a connection being opened for the call.

func (*Pool) Userdb

func (p *Pool) Userdb(ctx context.Context, username string) (*protocol.UserInfo, error)

Userdb resolves username, reusing a pooled connection when one is available.

func (*Pool) UserdbFor

func (p *Pool) UserdbFor(ctx context.Context, username, service string) (*protocol.UserInfo, error)

UserdbFor is Userdb on behalf of a protocol, so a userdb rule on %s applies.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL