opendkim-lua(3)

โœจ opendkim-lua โ€” the opendkim-lua(3) era โœจ

okay so bestie, welcome to the opendkim-lua(3) library functions manual, aka the group chat where you learn how to program the OpenDKIM filter with Lua scripts ๐Ÿ’… this is part one, so hydrate before the deep lore hits ๐Ÿ“


๐Ÿฆ‹ NAME ๐Ÿฆ‹

opendkim-lua โ€” programming the OpenDKIM filter using Lua scripts, no cap


๐Ÿ’– DESCRIPTION ๐Ÿ’–

okay so picture this: the OpenDKIM filter has hooks that run your scripts so you can make policy decisions. outbound? that's about which signatures to add. inbound? that's verification and acceptance of messages. the hooks take the form of multiple Lua scripts, and if you define them, they get run at the important points while a message is being processed ๐ŸŽ€

for the full dish on the Lua language itself, go consult the Lua programming references (there's a starting point linked below ๐Ÿ‘€). this man page is only about using Lua in the context of OpenDKIM โ€” specifically the functions and global variables OpenDKIM hands you for your own homegrown scripts, on top of whatever Lua gives you by default โœจ

heads up bestie: unless it says otherwise, all the functions described below return a single result. on success they hand you the requested data, and on error they return the Lua constant nil. that's the vibe check ๐Ÿ˜ญ

๐ŸŒˆ the four scripting hooks ๐ŸŒˆ

there are four scripting hooks, and they are, in order:

  • setup โ€” this one runs after all the headers for the message have been received, but before any DKIM operations have started. so at this point you can peek at the available header fields and decide whether the message should be signed or verified (or both ๐Ÿซถ) and, if you're signing, which key(s) to use for adding signatures and which signature features you want. main character decision energy.
  • screen โ€” this one runs after the DKIM verification context has been established. the whole point here is giving you a chance to compare the message header fields against the available DKIM signatures and figure out which ones (if any) should be ignored during verification. like, for example: maybe you decide only signatures added by domains that exactly match the one in the From: domain are acceptable, and the rest should just be ignored. iconic filtering behaviour, honestly.
  • statistics โ€” this one runs after all the DKIM verification and signing work is done, but before any final message handling happens. its whole purpose is letting you look at the message or its signatures and make arbitrary extra statistical observations that the statistics module should record. (๐Ÿ“Œ note: this feature is experimental โ€” so, y'know, treat it accordingly ๐Ÿ™ˆ)
  • final โ€” this one runs after all the DKIM verification and signing work has been completed. now you get to look at the results of all the signature evaluations and decide whether the message should be accepted, rejected, discarded, quarantined, etc. the whole โœจfate of the messageโœจ is in your hands. and if the message gets accepted, any signatures you requested earlier get added to the message before it's released ๐Ÿ’–

๐Ÿ‘‘ GLOBAL VARIABLES ๐Ÿ‘‘

the following global variable(s) are provided for all user scripts, periodt:

  • ctx โ€” this is a generic context pointer referring to the context in which the filtering operation is being performed. it represents a single message in progress, plus the connection that accepted it. literally the main character of your script's world ๐ŸŒธ

โœจ SETUP SCRIPT FUNCTIONS โœจ

Okay bestie, these are the functions Lua gets handed so it can actually do stuff with a message as it rolls through the setup script. Each one's a little tool in your kit ๐Ÿ’…


๐ŸŽ€ odkim.check_popauth(ctx)

Returns 1 if the SMTP client represented by ctx is coming from an IP address found in the POPAUTH database (if it's enabled and configured), and 0 otherwise. If the POPAUTH database isn't enabled or isn't configured at all, you get the Lua constant nil โ€” so like, "no answer" rather than "no". ๐Ÿ™ˆ

๐Ÿฆ‹ odkim.db_check(db, string)

Returns 1 if db refers to a valid database handle (peep odkim.get_dbhandle() below, iykyk) and string is found in that database, and 0 otherwise. If something goes wrong, the Lua constant nil is returned. ๐Ÿ˜ญ

๐ŸŒธ odkim.db_close(db)

Closes the specified data set. Returns 1. The current implementation will conduct data set garbage collection when the script terminates, so this isn't strictly necessary โ€” but it is recommended. Be a tidy icon, close your handles. โœจ

๐ŸŒˆ odkim.db_open(name[, icase])

Opens the data set specified by name. If icase is provided and is "true", then queries into the database will be case-insensitive. See the opendkim(8) man page for the whole deal on specifying a data set. On success it returns a handle you can pass to odkim.db_check(); on failure it raises an exception. No cap, it just throws. ๐Ÿ’€

โญ odkim.export(ctx, name, value[, name2, value2[, ...]])

Exports variables with their corresponding values so they'll be available to later scripts. Pass the pair, bestie โ€” name then value, as many times as you want. ๐Ÿซถ

๐Ÿ’œ odkim.get_clienthost(ctx)

Returns the name of the host on the other end of the SMTP connection sending the current message. Usually that's a hostname, but it might be an IP address in square brackets if the SMTP client's IP address doesn't have a name associated with it. It's giving "we tried to look it up, this is what we got". ๐Ÿ™ˆ

๐Ÿงš odkim.get_clientip(ctx)

Returns the IP address of the client on the other end of the SMTP connection sending the current message, as a string. Both IPv4 and IPv6 addresses are supported โ€” equal opportunity networking, we love to see it. ๐ŸŒˆ

๐Ÿ‘‘ odkim.get_dbhandle(ctx, db)

Returns a handle for the requested database that you can use in later queries. The value of db should be one of:

  • DB_MTAS โ€” database of MTA names whose mail should be signed
  • DB_MACROS โ€” database of MTA macro checks to be done when determining signing
  • DB_DOMAINS โ€” database of domains to be signed
  • DB_SIGNINGTABLE โ€” database of signing table entries
  • DB_THIRDPARTY โ€” database of third party signatures to be trusted
  • DB_DONTSIGNTO โ€” database of recipients whose mail should not be signed

If the requested database isn't set in the current configuration file, you get a Lua nil. Sad but true. ๐Ÿ˜ญ

๐Ÿ“ odkim.get_envfrom(ctx)

Retrieves the SMTP envelope sender address for the message represented by ctx. The envelope one, bestie, not the header one โ€” know the difference. ๐Ÿ’…

๐ŸŒธ odkim.get_fromdomain(ctx)

Retrieves the domain name of the sender of the message represented by ctx. Just the domain. Main character energy. ๐Ÿ‘‘

๐ŸŽ€ odkim.get_header(ctx, name, n)

Retrieves the string contained in instance n of the header field called name from the message represented by ctx, or the Lua constant nil if there was no such header field. Header field numbering starts at 0, so use 0 for the first instance, 1 for the second, etc. For example:

fromaddr = odkim.get_header(ctx, "From", 0)

That'll return the value of the first (and hopefully only) From header field. Negative values of n count backwards from the end of the set of header fields, so:

rcvd = odkim.get_header(ctx, "Received", -2)

will retrieve the second-last Received: header field on the message. Negative indexing in a man page?? We love a plot twist. ๐Ÿ”ฅ

๐Ÿ’ซ odkim.get_mtasymbol(ctx, name)

Retrieves the value of the symbol called name from the MTA connection represented by ctx, or the Lua constant nil if the requested symbol wasn't available at the time of the request. Timing is everything, ngl. ๐Ÿ™ˆ

๐Ÿฆ‹ odkim.get_rcpt(ctx, n)

Returns the nth envelope recipient for the message represented by ctx. Recipient numbering starts at 0, so for the first recipient, use 0 for n. If n references an out-of-range value, the Lua constant nil is returned. Zero-indexed, bestie, don't embarrass yourself. ๐Ÿ’…

๐ŸŒŸ odkim.get_rcptarray(ctx)

Returns the envelope recipients for the message represented by ctx in a single Lua array. All of them. At once. Iconic. โœจ

๐Ÿ’œ odkim.internal_ip(ctx)

Returns 1 if the SMTP client is coming from an internal IP address, and 0 otherwise. Are they one of us or not. ๐Ÿค”

๐ŸŽ€ odkim.log(ctx, log)

Logs the string log if the current configuration requested logging. (Checking the current configuration is why the ctx parameter is required โ€” it's not just there for vibes, okay.) ๐Ÿ“

๐ŸŒธ odkim.rcpt_count(ctx)

Returns the count of envelope recipients on the message. A number. Simple. Slay. ๐Ÿ”ข

๐Ÿ”ฅ odkim.replace_header(ctx, name, n, newval)

Retrieves the value of instance n of header field name in the message referenced by ctx and replaces it with the string in newval. See odkim.get_header() above for more info on possible parameter values for n.

Now listen carefully, because this one's important โš ๏ธ โ€” this only changes the content of the header field used when generating or verifying the signature; the actual delivered message is not modified. You can use this to anticipate how an intermediate mail transfer agent might alter the message, thus correcting an avoidable signature invalidation. You're basically reading the future. ๐Ÿ”ฎ

๐Ÿฆ‹ odkim.resign(ctx)

Arranges that the arriving message will be verified and then re-signed in a single operation. Returns 1 on success or the Lua constant nil on failure. One gesture, two jobs โ€” multitasking queen. ๐Ÿ‘‘

๐Ÿงš odkim.set_result(ctx, result)

Arranges to have the MTA return a specific result code in response to the message represented by ctx. The value of result must be one of:

  • SMFIS_TEMPFAIL โ€” temporary failure/rejection
  • SMFIS_ACCEPT โ€” accept without further processing
  • SMFIS_DISCARD โ€” accept but discard the message
  • SMFIS_REJECT โ€” permanent failure/rejection

Returns 1 on success or the Lua constant nil on failure.

โš ๏ธ Heads up bestie: returning any of these codes indicates a final message disposition. The MTA gets told immediately to take the specified action, and no further filter processing will occur. So likeโ€ฆ choose wisely. There is no undo. ๐Ÿ’€

โญ odkim.sign(ctx[, keyname[, signer[, signlen]]])

Requests that the filter sign the message represented by ctx using the specified keyname. The key name gets translated into an actual domain, selector and private key via a query to the KeyTable (see the opendkim.conf(5) page for the deets).

The keyname may be omitted if the KeyTable isn't defined, meaning the single signing domain, selector and key should be used to sign. Returns 1 on success and 0 on failure.

If a signer is specified, the string there gets included in the generated signature's "i=" tag. If a signlen is specified, the signature will cover that many bytes of the message body. And plot twist โ€” the order of these last two parameters is interchangeable. Go off, flexible API. ๐Ÿ’…

๐ŸŒˆ odkim.signfor(ctx, address[, multi])

Applies whatever signatures would be applied by default if the candidate message had the specified address in the message's From: field. The multi parameter, if "true" (default is "false"), allows the application of multiple signatures.

Returns the number of signatures applied โ€” which may be zero. Zero is a valid answer, don't take it personally. ๐Ÿ™ˆ

๐Ÿ“ odkim.spam(ctx)

Tags the message as spam, for use in developing reputation about domains that signed the message. Returns nothing.

Note: Feature is experimental. ๐Ÿงช

๐Ÿ’… odkim.use_ltag(ctx)

Requests that all signatures added to the message represented by ctx include "l=" (body length) tags. Always returns the Lua constant nil. Weird return, iconic behaviour. โœจ

๐ŸŽ€ odkim.verify(ctx)

Requests that the message represented by ctx be subjected to DKIM signature verification. Returns the Lua constant nil on success, or an error string on failure. So check that return value, bestie, fr fr. ๐Ÿ‘€

๐Ÿ”ฅ odkim.xtag(ctx, tag, value)

Requests that all signatures added to the message represented by ctx include the named extension tag and value. Returns the number of signatures successfully modified, or -1 on error.

An error can occur if the named tag is one already explicitly supported by the DKIM library, or if there's a syntax error in the tag or value. So don't try to outsmart the library โ€” it already knows that tag. ๐Ÿ’€

๐Ÿ’… SCREEN SCRIPT FUNCTIONS ๐Ÿ’…

okay so the screen script has all these functions available to it from above โ€” you already know them, bestie: odkim.db_check, odkim.db_close, odkim.db_open, odkim.export, odkim.get_dbhandle, odkim.get_envfrom, odkim.get_fromdomain, odkim.get_header, odkim.get_mtasymbol, odkim.get_rcpt, odkim.get_rcptarray, odkim.internal_ip, odkim.log, odkim.rcpt_count, and odkim.spam. slay โœจ

The screen script also gets this extra little bag of tricks:

  • odkim.get_sigarray(ctx) โ€” returns the complete set of signature handles found in the message represented by ctx, as a Lua array, or the Lua constant nil in case of an error. ๐ŸŽ€
  • odkim.get_sigcount(ctx) โ€” returns the number of signatures found in the message represented by ctx, or the Lua constant nil in case of an error. ๐Ÿ”ฅ
  • odkim.get_sighandle(ctx, n) โ€” returns a handle representing an internal copy of the nth signature found on the message represented by ctx. n must be a number greater than or equal to zero (representing the first signature) and less than the number of signatures on the message, which can be determined using odkim.get_sigcount above. The requested handle is returned on success, or the Lua constant nil is returned on failure. ๐Ÿ‘‘
  • odkim.parse_field(string) โ€” parses the contents of a header field, provided as string, into user and domain parts, discarding whitespace and comment components. Returns two strings, the user part and the domain part, or the Lua constant nil in case of a parsing error. ๐Ÿฆ‹
  • odkim.sig_getdomain(sig) โ€” returns the name of the domain in the signature handle specified by sig, previously returned by a call to odkim.get_sighandle(). This is taken from the signature's "d=" tag. ๐Ÿ’–
  • odkim.sig_getidentity(sig) โ€” returns the identity of the agent adding the signature handle specified by sig, previously returned by a call to odkim.get_sighandle(). This is taken from the signature's "i=" tag. This may be a default value and not one that was explicitly part of the signature. If the identity could not be determined, the Lua constant nil is returned. ๐ŸŒธ
  • odkim.sig_ignore(sig) โ€” instructs the verification code to ignore completely the signature specified by sig, previously returned by a call to odkim.get_sighandle(). Any pending verification of the message will act as if that signature was not present on the message. Always returns the Lua constant nil. ๐Ÿ’€

๐Ÿ“Š STATISTICS SCRIPT FUNCTIONS ๐Ÿ“Š

the statistics script has these functions available to it, whose descriptions can be found above: odkim.export, odkim.get_envfrom, odkim.get_header, odkim.get_mtasymbol, odkim.get_rcpt, odkim.get_rcptarray, odkim.get_sigarray, odkim.get_sigcount, odkim.get_sighandle, odkim.internal_ip, odkim.log, odkim.parse_field, odkim.rcpt_count, odkim.sig_getdomain, odkim.sig_getidentity, and odkim.spam. iykyk ๐Ÿ’…

The following functions are also available, defined in the next section: odkim.rbl_check, odkim.rcpt_count, odkim.sig_bhresult, odkim.sig_bodylength, odkim.sig_canonlength, and odkim.sig_result. ๐Ÿ‘€

The following additional function is provided for this script:

  • odkim.stats(ctx, name, value) โ€” records the additional statistic called name with its associated value for the message represented by ctx. ๐Ÿ“ˆโœจ

๐Ÿ‘‘ FINAL SCRIPT FUNCTIONS ๐Ÿ‘‘

the final script has these functions available to it, whose descriptions can be found above: odkim.get_clienthost, odkim.get_clientip, odkim.get_envfrom, odkim.get_fromdomain, odkim.get_header, odkim.get_mtasymbol, odkim.get_rcpt, odkim.get_rcptarray, odkim.get_sigarray, odkim.get_sigcount, odkim.get_sighandle, odkim.internal_ip, odkim.log, odkim.parse_field, odkim.rcpt_count, odkim.set_result, odkim.sig_getdomain, odkim.sig_getidentity, odkim.spam, and odkim.xtags. the whole squad is here ๐Ÿซถ

The following additional functions are provided for this script:

  • odkim.add_header(ctx, name, value) โ€” adds a new header field called name with the specified value to the message represented by ctx. Returns 1 on success, or the Lua constant nil on failure. ๐ŸŽ€
  • odkim.add_rcpt(ctx, addr) โ€” adds addr as an envelope recipient to the message represented by ctx. Returns 1 on success, or the Lua constant nil on failure. ๐Ÿ’Œ
  • odkim.del_header(ctx, name, n) โ€” deletes the nth instance (starting from 0) of the header field called name from the message represented by ctx. Returns 1 on success, or the Lua constant nil on failure. โœ‚๏ธ
  • odkim.del_rcpt(ctx, addr) โ€” deletes addr from the list of envelope recipients on the message represented by ctx, and adds a new X-Original-Recipient: header field containing the deleted address. Returns 1 on success, or the Lua constant nil on failure. ๐Ÿ—‘๏ธ
  • odkim.quarantine(ctx, reason) โ€” asks the MTA to quarantine the message represented by ctx using reason as a text string indicating the reason for the request. Returns 1 on success or the Lua constant nil on failure. ๐Ÿšง
  • odkim.rbl_check(ctx, query, qroot[, timeout]) โ€” makes an RBL query. The root of the RBL is assumed to be at qroot and the subject of the query is query, so the query performed will be query.qroot. The context handle ctx must also be provided as it contains a handle to the established DNS service. The optional timeout parameter is the timeout to use, in seconds. Returns nil on error, no values if the requested record was not present in the RBL, or the four octets of the RBL entry if it was. The octets are returned in big-endian order. (Note: Feature is experimental.) ๐Ÿงช
  • odkim.set_reply(ctx, rcode, xcode, message) โ€” instructs the MTA to return the specified SMTP reply to the client sending the message represented by ctx. rcode must be a three-digit SMTP reply code starting with 4 or 5 (for temporary or permanent failures, respectively); xcode must be the empty string or a valid extended reply code (see RFC2034) matching rcode; and message must be the text portion of the SMTP reply to be sent. Returns 1 on success or the Lua constant nil on failure. ๐Ÿ“จ
  • odkim.sig_bhresult(sig) โ€” returns the result code corresponding to the body hash evaluation of the signature handle specified by sig, previously returned by a call to odkim.get_sighandle(). Valid values are the DKIM_SIGBH_* constants defined in the libopendkim header file dkim.h. ๐Ÿ”
  • odkim.sig_bodylength(sig) โ€” returns the total length of the message signed by sig, previously returned by a call to odkim.get_sighandle(), or the Lua constant nil if this value could not be determined. ๐Ÿ“
  • odkim.sig_canonlength(sig) โ€” returns the canonicalized length of the message signed by sig, previously returned by a call to odkim.get_sighandle(), or the Lua constant nil if this value could not be determined. Note that this may be less than the value returned by odkim.get_bodylength() if the signature only covered part of the message. ๐Ÿงฎ
  • odkim.sig_result(sig) โ€” returns the result code corresponding to the signature handle specified by sig, previously returned by a call to odkim.get_sighandle(). Valid values are the constants with DKIM_SIGERROR_ prefixes as defined in the libopendkim header file dkim.h. โญ

๐ŸŒˆ VERSION ๐ŸŒˆ

This man page covers version 2.11.0 of OpenDKIM. periodt ๐Ÿ’–

Copyright (c) 2009-2015, The Trusted Domain Project. All rights reserved. ๐Ÿซถ

๐Ÿ”— SEE ALSO ๐Ÿ”—

opendkim(8), opendkim.conf(5)

Lua โ€” http://www.lua.org ๐Ÿฆ‹


mwah ๐Ÿ’‹ that's the whole page, bestie โ€” footer source/version vibes: The Trusted Domain Project, page opendkim-lua(3), version 2.11.0. no cap, that's a wrap ๐ŸŽ€โœจ

๐Ÿ“ฆ where this page comes from