qrelay ====== What is qrelay? --------------- qrelay is a QMQP server that receives emails on a socket and forwards them to another QMQP server. Configuration ------------- .. highlight:: lua The file :file:`/etc/cm4all/qrelay/config.lua` is a `Lua `_ script which is executed at startup. It contains at least one :samp:`qmqp_listen()` call, for example:: qmqp_listen('/run/cm4all/qrelay/qrelay.sock', function(m) return m:connect('192.168.1.99') end) The first parameter is the socket path to listen on. Passing the global variable :envvar:`systemd` (not the string literal :samp:`"systemd"`) will listen on the sockets passed by systemd:: qmqp_listen(systemd, function(m) ... To use this socket from within a container, move it to a dedicated directory and bind-mount this directory into the container. Mounting just the socket doesn't work because a daemon restart must create a new socket, but the bind mount cannot be refreshed. The second parameter is a callback function which shall decide what to do with an incoming email. This function receives a mail object which can be inspected. Multiple listeners can share the same handler by declaring the function explicitly:: function handler(m) return m:reject() end qmqp_listen('/foo', handler) qmqp_listen('/bar', handler) It is important that the function finishes quickly. It must never block, because this would block the whole daemon process. This means it must not do any network I/O, launch child processes, and should avoid anything but querying the email's parameters. ``SIGHUP`` ^^^^^^^^^^ On ``systemctl reload cm4all-qrelay`` (i.e. ``SIGHUP``), qrelay calls the Lua function ``reload`` if one was defined. It is up to the Lua script to define the exact meaning of this feature. Global Variables ^^^^^^^^^^^^^^^^ * ``max_size`` is the maximum size of an incoming email in bytes (used only during startup). There must be a reasonable upper limit to avoid consuming too many resources. The default value is ``16777216`` (``16 MiB``). * ``log_server`` is the address of the `Pond `__ server (or a multicast address) that will receive a log datagram for each email that was processed. Use the function `log_resolve()` to resolve host names. Inspecting Incoming Mail ^^^^^^^^^^^^^^^^^^^^^^^^ The following attributes can be queried: * :samp:`sender`: The sender envelope address. This attribute can also be written to. * :samp:`recipients`: A list of recipient envelope addresses. * :samp:`pid`: The client's process id. * :samp:`uid`: The client's user id. * :samp:`gid`: The client's group id. * :samp:`account`: Set this to an identifier of the user account. This will be used for logging. * :samp:`cgroup`: The control group of the client process with the following attributes: * ``path``: the cgroup path as noted in :file:`/proc/self/cgroup`, e.g. :file:`/user.slice/user-1000.slice/session-42.scope` * ``xattr``: A table containing extended attributes of the control group. * ``parent``: Information about the parent of this cgroup; it is another object of this type (or ``nil`` if there is no parent cgroup). Manipulating the Mail Object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The method `insert_header(NAME, VALUE)` inserts a new MIME header at the front of the email. Example:: m:insert_header('X-Cgroup', m.cgroup) Both name and value must conform to RFC 2822 2.2. No more than 16 additional headers can be inserted this way. Actions ^^^^^^^ The handler function shall return an object describing what to do with the email. The mail object contains several methods which create such action objects; they do not actually perform the action. The following actions are possible: * :samp:`connect("ADDRESS")`: Connect to this address and relay the email via QMQP. The address is either a string containing a (numeric) IP address, or an `address` object created by `qmqp_resolve()`. * :samp:`exec("PROGRAM", "ARG", ...)`: Execute the program and submit the email via QMQP on standard input. Read the QMQP response from standard output. The last parameter may be a table specifying options: - ``env``: a table with environment variables for the child process. - ``timeout``: cancel the submission after this number of seconds. * :samp:`exec_raw("PROGRAM", "ARG", ...)`: Execute the program and submit the raw email message (headers and body, but no envelope) on standard input. Translates the exit status to either "accepted (K)" (``EXIT_SUCCESS``), "temporary failure (Z)" or "permanent failure (D)" and read an error message from standard output/error. This can be used to launch programs which implement the ``/usr/sbin/sendmail`` interface instead of QMQP. Since the envelope is not submitted, the caller should translate the envelope to command-line arguments. The last parameter may be a table specifying options (the same as for ``exec()``). * :samp:`discard()`: Discard the email, pretending delivery was successful. * :samp:`reject()`: Reject the email with a permanent error. Addresses ^^^^^^^^^ It is recommended to create all `address` objects during startup, to avoid putting unnecessary pressure on the Lua garbage collector, and to reduce the overhead for invoking the system resolver (which blocks qrelay execution). The function `qmqp_resolve()` creates such an `address` object:: server1 = qmqp_resolve('192.168.0.2') server2 = qmqp_resolve('[::1]:4321') server3 = qmqp_resolve('server1.local:1234') server4 = qmqp_resolve('/run/server5.sock') server5 = qmqp_resolve('@server4') These examples do the following: - convert a numeric IPv4 address to an `address` object (port defaults to 628, the QMQP standard port) - convert a numeric IPv6 address with a non-standard port to an `address` object - invoke the system resolver to resolve a host name to an IP address (which blocks qrelay startup; not recommended) - convert a path string to a "local" socket address - convert a name to an abstract "local" socket address (prefix '@' is converted to a null byte, making the address "abstract") socket ^^^^^^ A simple low-level networking library. Example:: tcp = socket:connect('localhost:1234') udp = socket:connect('localhost:4321', {type='dgram'}) multicast = socket:connect('[ff02::dead:beef]:2345', {type='dgram'}) unix = socket:connect('/run/test.socket') abstract = socket:connect('@test', {type='seqpacket'}) abstract:send('hello world') The ``socket`` library has the following methods: - ``connect(ADDRESS, [OPTIONS])``: Create a new socket connected to the specified address. ``OPTIONS`` may be a table with the following keys: - ``type``: the socket type, one of ``stream`` (the default), ``dgram``, ``seqpacket``. Returns a new socket object on success or ``[nil,error]`` on error. Socket objects have the following methods: - ``close()``: Close the socket. - ``send(DATA, [START], [END])``: Send data (i.e. a string) to the peer. ``START`` and ``END`` are start and end position within the string with the same semantics as in ``string.sub()``. Returns the number of bytes sent on success or ``[nil,error]`` on error. control_client ^^^^^^^^^^^^^^ A client for the `beng-proxy control protocol `__. During startup, create a ``control_client`` object:: -- IPv4 (default port) c = control_client:new('224.0.0.42') -- IPv6 on default port c = control_client:new('ff02::dead:beef') -- IPv6 on non-default port (requires square brackets) c = control_client:new('[ff02::dead:beef]:1234') -- local socket c = control_client:new('/run/cm4all/workshop/control') -- abstract socket c = control_client:new('@bp-control') The ``new()`` constructor returns ``nil,error`` on error (and thus the call can be wrapped in ``assert()`` to raise a Lua error instead). The method ``build()`` creates an object which can be used to build a control datagram with one or more commands. After that datagram has been assembled, it can be sent with the ``send()`` method. Example:: c:send(c:build():fade_children('foo'):flush_http_cache('bar')) The ``send()`` method returns ``nil,error`` on error. The builder implements the following methods: - ``cancel_job(PARTITION_NAME, JOB_ID)`` - ``discard_session(ID)`` - ``disconnect_database(ACCOUNT)`` - ``fade_children(TAG)`` - ``flush_filter_cache(TAG)`` - ``flush_http_cache(TAG)`` - ``reject_client(ADDRESS)`` - ``reset_limiter(ACCOUNT)`` - ``tarpit_client(ADDRESS)`` - ``terminate_children(TAG)`` libsodium ^^^^^^^^^ There are some `libsodium `__ bindings. `Helpers `__:: bin = sodium.hex2bin("deadbeef") -- returns "\xde\xad\xbe\ef" hex = sodium.bin2hex("A\0\xff") -- returns "4100ff" `Generating random data `__:: key = sodium.randombytes(32) `Sealed boxes `__:: pk, sk = sodium.crypto_box_keypair() ciphertext = sodium.crypto_box_seal('hello world', pk) message = sodium.crypto_box_seal_open(ciphertext, pk, sk) `Public-key signatures `__:: pk, sk = sodium.crypto_sign_keypair() m = 'hello world' sig = sodium.crypto_sign_detached(m, sk) valid = sodium.crypto_sign_verify_detached(sig, m, pk) `Point*scalar multiplication `__:: pk = sodium.crypto_scalarmult_base(sk) json ^^^^ If built with `nlohmann json `__, the following functions are available in the ``json`` namespace:: - the function ``json.dump()`` converts the parameter to a JSON string:: print(json.dump{x=42, y="z"}) {"x":42,"y":"z"} - the function ``json.parse()`` parses a JSON string and returns it as a Lua value:: print(inspect(json.parse('{"x":42,"y":"z"}'))) { x = 42, y = "z" } JWT ^^^ The function `jwt.sign()` generates a JSON Web Token:: pk, sk = sodium.crypto_sign_keypair() header = {kid='foo'} payload = {iss="joe",exp=1300819380} token = jwt.sign(sk, header, payload) The JWT library requires building with ``nlohmann_json`` and ``libsodium``. PostgreSQL Client ^^^^^^^^^^^^^^^^^ The Lua script can query a PostgreSQL database. First, a connection should be established during initialization:: db = pg:new('dbname=foo', 'schemaname') In the handler function, queries can be executed like this (the API is similar to `LuaSQL `__):: local result = assert(db:execute('SELECT id, name FROM bar')) local row = result:fetch({}, "a") print(row.id, row.name) Query parameters are passed to ``db:execute()`` as an array after the SQL string:: local result = assert( db:execute('SELECT name FROM bar WHERE id=$1', {42})) The functions ``pg:encode_array()`` and ``pg:decode_array()`` support PostgreSQL arrays; the former encodes a Lua array to a PostgreSQL array string, and the latter decodes a PostgreSQL array string to a Lua array. To listen for `PostgreSQL notifications `__, invoke the ``listen`` method with a callback function:: db:listen('bar', function() print("Received a PostgreSQL NOTIFY") end) Examples ^^^^^^^^ TODO About Lua ^^^^^^^^^ `Programming in Lua `_ (a tutorial book), `Lua 5.3 Reference Manual `_. Note that in Lua, attributes are referenced with a dot (e.g. :samp:`m.sender`), but methods are referenced with a colon (e.g. :samp:`m:reject()`).