Sovite.Policy.Client (sovite v0.6.0)

Copy Markdown View Source

Asks a policy server what to do, over TCP, a Unix socket, or the standard input and output of a program.

{:ok, conn} = Client.connect({:inet, "127.0.0.1", 10023})

{:ok, "DEFER_IF_PERMIT Greylisted", conn} =
  Client.request(conn, protocol_state: "RCPT", client_address: {192, 0, 2, 7}, ...)

:ok = Client.close(conn)

check/3 connects, asks once, and closes. A connection can carry any number of requests, one after the other: keep it to save the connection setup, as Postfix does. Only the process that opened a connection may use it.

The reply's action is returned as text: parse it with Sovite.Policy.parse_action/1. Other attributes of the reply are ignored. Requests are encoded with Sovite.Policy.encode_request/1.

Addresses

  • {:inet, host, port} - TCP. host is a name, an IP address string, or an IP address tuple.
  • {:unix, path} - a Unix socket.
  • {:command, [program | args]} - a program, started for each connection with the protocol on its standard input and output, as Postfix's spawn(8) runs policyd-spf. It runs directly, never through a shell, with the environment of the VM; its standard error is not read. program is an absolute path or found in PATH. Closing the connection closes its standard input, which ends such programs.

parse_address/1 reads them from Postfix-style strings.

Errors

A failed request closes the connection; connect again for the next request. Reasons:

  • :closed - the server closed the connection or the program exited, maybe because it was idle for too long, or the connection was closed already. Reconnecting usually works.
  • :timeout - no complete reply in time.
  • :response_too_large - the reply is over :max_response bytes.
  • :invalid_response - the reply is malformed or has no action.

connect/2 returns :timeout or the socket error, such as :econnrefused or :enoent, and :enoent or :eacces when the program cannot be run.

Options

For connect/2 and check/3; request/3 takes :timeout and :max_response, overriding those given to connect/2.

  • :timeout - milliseconds for each request, to send it and get the whole reply. Defaults to 100 seconds, like Postfix's smtpd_policy_service_timeout.
  • :connect_timeout - milliseconds. Defaults to 10 seconds.
  • :max_response - bytes of a reply. Defaults to 4096.

Telemetry

  • [:sovite, :policy, :client, :request, :stop] - %{duration}, %{address, action}, for each reply. action is the text.
  • [:sovite, :policy, :client, :error] - %{duration}, %{address, reason}, when connecting or a request fails. reason is an error above.

Summary

Types

Where the policy server is, see the module docs.

Why a request failed, see the module docs.

t()

Functions

Connects, sends one request, and closes. See connect/2 and request/3.

Closes the connection. Closing it again does nothing.

Connects to a policy server. See the module docs for options.

Parses an address string

Sends a request and waits for the reply's action. See Sovite.Policy.encode_request/1 for attrs, and the module docs for errors and options. After an error the connection is closed.

Types

address()

@type address() ::
  {:inet, host :: String.t() | :inet.ip_address(), :inet.port_number()}
  | {:unix, Path.t()}
  | {:command, [String.t(), ...]}

Where the policy server is, see the module docs.

error()

@type error() :: :closed | :timeout | :response_too_large | :invalid_response

Why a request failed, see the module docs.

t()

@opaque t()

Functions

check(address, attrs, opts \\ [])

@spec check(address(), Sovite.Policy.attributes(), keyword()) ::
  {:ok, action :: String.t()} | {:error, term()}

Connects, sends one request, and closes. See connect/2 and request/3.

{:ok, action} = Client.check({:unix, "/run/postgrey.sock"}, attrs)

close(client)

@spec close(t()) :: :ok

Closes the connection. Closing it again does nothing.

connect(address, opts \\ [])

@spec connect(address(), keyword()) :: {:ok, t()} | {:error, term()}

Connects to a policy server. See the module docs for options.

parse_address(arg1)

@spec parse_address(String.t()) :: {:ok, address()} | {:error, :invalid_address}

Parses an address string:

  • inet:host:port, or inet:[ipv6]:port for an IPv6 address
  • unix:path
  • spawn:program args... - the program and its arguments, split on whitespace (not a Postfix syntax)

Examples

iex> Sovite.Policy.Client.parse_address("inet:127.0.0.1:10023")
{:ok, {:inet, "127.0.0.1", 10023}}

iex> Sovite.Policy.Client.parse_address("inet:[::1]:9998")
{:ok, {:inet, "::1", 9998}}

iex> Sovite.Policy.Client.parse_address("spawn:/usr/bin/policyd-spf /etc/spf.conf")
{:ok, {:command, ["/usr/bin/policyd-spf", "/etc/spf.conf"]}}

request(conn, attrs, opts \\ [])

@spec request(t(), Sovite.Policy.attributes(), keyword()) ::
  {:ok, action :: String.t(), t()} | {:error, error()}

Sends a request and waits for the reply's action. See Sovite.Policy.encode_request/1 for attrs, and the module docs for errors and options. After an error the connection is closed.