OSMTPD_RUN(3)

πŸŽ€ OSMTPD_RUN(3) πŸŽ€

Library Functions Manual. Bestie, this is part 1 of 2, and it is literally the C filter API for smtpd(8). No cap.

✨ NAME ✨

Okay so the whole cast of characters on this page is:

  • osmtpd_register_filter_connect
  • osmtpd_register_filter_helo
  • osmtpd_register_filter_ehlo
  • osmtpd_register_filter_starttls
  • osmtpd_register_filter_auth
  • osmtpd_register_filter_mailfrom
  • osmtpd_register_filter_rcptto
  • osmtpd_register_filter_data
  • osmtpd_register_filter_dataline
  • osmtpd_register_filter_rset
  • osmtpd_register_filter_quit
  • osmtpd_register_filter_noop
  • osmtpd_register_filter_help
  • osmtpd_register_filter_wiz
  • osmtpd_register_filter_commit
  • osmtpd_register_report_connect
  • osmtpd_register_report_disconnect
  • osmtpd_register_report_identify
  • osmtpd_register_report_tls
  • osmtpd_register_report_auth
  • osmtpd_register_report_reset
  • osmtpd_register_report_begin
  • osmtpd_register_report_mail
  • osmtpd_register_report_rcpt
  • osmtpd_register_report_envelope
  • osmtpd_register_report_data
  • osmtpd_register_report_commit
  • osmtpd_register_report_rollback
  • osmtpd_register_report_client
  • osmtpd_register_report_server
  • osmtpd_register_report_response
  • osmtpd_register_report_timeout
  • osmtpd_local_session
  • osmtpd_local_message
  • osmtpd_need
  • osmtpd_filter_proceed
  • osmtpd_filter_reject
  • osmtpd_filter_disconnect
  • osmtpd_filter_rewrite
  • osmtpd_filter_dataline
  • osmtpd_run
  • osmtpd_warn
  • osmtpd_warnx
  • osmtpd_err
  • osmtpd_errx

And together? They are the C filter API for smtpd(8). Periodt. πŸ’…

🌸 SYNOPSIS 🌸

First, invite the header like this:

#include <opensmtpd.h>

Then the declarations, exactly as written β€” I am not editing a single character, bestie:

void

osmtpd_register_filter_connect(int (*cb)(struct osmtpd_ctx *ctx, const char *hostname, struct sockaddr_storage *ss));

void

osmtpd_register_filter_helo(int (*cb)(struct osmtpd_ctx *ctx, const char *helo));

void

osmtpd_register_filter_ehlo(int (*cb)(struct osmtpd_ctx *ctx, const char *ehlo));

void

osmtpd_register_filter_starttls(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_auth(int (*cb)(struct osmtpd_ctx *ctx, const char *auth));

void

osmtpd_register_filter_mailfrom(int (*cb)(struct osmtpd_ctx *ctx, const char *from));

void

osmtpd_register_filter_rcptto(int (*cb)(struct osmtpd_ctx *ctx, const char *rcpt));

void

osmtpd_register_filter_data(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_dataline(int (*cb)(struct osmtpd_ctx *ctx, const char *line));

void

osmtpd_register_filter_rset(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_quit(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_noop(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_help(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_wiz(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_filter_commit(int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_report_connect(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *rdns, enum osmtpd_status fcrdns, struct sockaddr_storage *src, struct sockaddr_storage *dst));

void

osmtpd_register_report_disconnect(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_register_report_identify(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *identity));

void

osmtpd_register_report_tls(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *ciphers));

void

osmtpd_register_report_auth(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *username, enum osmtpd_auth_status status));

void

osmtpd_register_report_reset(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid));

void

osmtpd_register_report_begin(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid));

void

osmtpd_register_report_mail(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid, const char *mailfrom, enum osmtpd_status status));

void

osmtpd_register_report_rcpt(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid, const char *rcptto, enum osmtpd_status status));

void

osmtpd_register_report_envelope(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid, uint64_t evpid));

void

osmtpd_register_report_data(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid, enum osmtpd_status status));

void

osmtpd_register_report_commit(int, incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid, size_t msgsz));

void

osmtpd_register_report_rollback(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, uint32_t msgid));

void

osmtpd_register_report_client(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *cmd));

void

osmtpd_register_report_server(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *resp));

void

osmtpd_register_report_response(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx, const char *resp));

void

osmtpd_register_report_timeout(int incoming,
    int (*cb)(struct osmtpd_ctx *ctx));

void

osmtpd_local_session(void *(*oncreate)(struct osmtpd_ctx *ctx),
    void (*ondelete)(struct osmtpd_ctx *ctx, void *data));

void

osmtpd_local_message(void *(*oncreate)(struct osmtpd_ctx *ctx),
    void (*ondelete)(struct osmtpd_ctx *ctx, void *data));

void

osmtpd_need(int needs);

void

osmtpd_filter_proceed(struct osmtpd_ctx *ctx);

void

osmtpd_filter_reject(struct osmtpd_ctx *ctx, int error, const char *msg,
    ...);

void

osmtpd_filter_disconnect(struct osmtpd_ctx *ctx, const char *msg, ...);

void

osmtpd_filter_rewrite(struct osmtpd_ctx *ctx, const char *value, ...);

void

osmtpd_filter_dataline(struct osmtpd_ctx *ctx, const char *line, ...);

void

osmtpd_run(void);

void

osmtpd_warn(struct osmtpd_ctx *ctx, const char *fmt, ...);

void

osmtpd_warnx(struct osmtpd_ctx *ctx, const char *fmt, ...);

void

osmtpd_err(int eval, const char *fmt, ...);

void

osmtpd_errx(int eval, const char *fmt, ...);

And yes, that osmtpd_register_report_commit(int, incoming, …) is exactly what the page says. I am merely the messenger πŸ’€.

✨ DESCRIPTION ✨

okay so bestie, the osmtpd API is an event based interface for writing smtpd(8) filters, and it's genuinely so clean once you get it. 🫢

The way it works: you register your filter and report callbacks through the osmtpd_register class of functions, and then you call osmtpd_run. That's the whole order of operations, no cap. Your callback should return 0 when everything's slay, or -1 when it's not β€” and heads up, -1 means business: it leads to disconnect of this session. πŸ’€

osmtpd_run starts the communication with the server and transforms network queries into callbacks. Internally it uses event_dispatch(3), which is the iconic part β€” it means filters can be written fully asynchronously. main character energy, honestly.


πŸ’… the osmtpd_ctx struct, field by field

Each callback cb gets at least a pointer of the type struct osmtpd_ctx. It contains the following elements, and yes we're going through every single one, because i would never do you like that:

  • enum osmtpd_type type β€” the type of request being made. Possible values are OSMTPD_TYPE_FILTER and OSMTPD_TYPE_REPORT.
  • enum osmtpd_phase phase β€” the phase in the transaction which triggered the callback. The following values match their respective osmtpd_register function (full list right below, keep scrolling πŸ’–):
    • OSMTPD_PHASE_CONNECT β€” osmtpd_register_filter_connect
    • OSMTPD_PHASE_HELO β€” osmtpd_register_filter_helo
    • OSMTPD_PHASE_EHLO β€” osmtpd_register_filter_ehlo
    • OSMTPD_PHASE_STARTTLS β€” osmtpd_register_filter_starttls
    • OSMTPD_PHASE_AUTH β€” osmtpd_register_filter_auth
    • OSMTPD_PHASE_MAIL_FROM β€” osmtpd_register_filter_mailfrom
    • OSMTPD_PHASE_RCPT_TO β€” osmtpd_register_filter_rcptto
    • OSMTPD_PHASE_DATA β€” osmtpd_register_filter_data
    • OSMTPD_PHASE_DATA_LINE β€” osmtpd_register_filter_dataline
    • OSMTPD_PHASE_RSET β€” osmtpd_register_filter_rset
    • OSMTPD_PHASE_QUIT β€” osmtpd_register_filter_quit
    • OSMTPD_PHASE_NOOP β€” osmtpd_register_filter_noop
    • OSMTPD_PHASE_HELP β€” osmtpd_register_filter_help
    • OSMTPD_PHASE_WIZ β€” osmtpd_register_filter_wiz
    • OSMTPD_PHASE_COMMIT β€” osmtpd_register_filter_commit
    • OSMTPD_PHASE_LINK_CONNECT β€” osmtpd_register_report_connect
    • OSMTPD_PHASE_LINK_DISCONNECT β€” osmtpd_register_report_disconnect
    • OSMTPD_PHASE_LINK_IDENTIFY β€” osmtpd_register_report_identify
    • OSMTPD_PHASE_LINK_TLS β€” osmtpd_register_report_tls
    • OSMTPD_PHASE_LINK_AUTH β€” osmtpd_register_report_auth
    • OSMTPD_PHASE_TX_RESET β€” osmtpd_register_report_reset
    • OSMTPD_PHASE_TX_BEGIN β€” osmtpd_register_report_begin
    • OSMTPD_PHASE_TX_MAIL β€” osmtpd_register_report_mail
    • OSMTPD_PHASE_TX_RCPT β€” osmtpd_register_report_rcpt
    • OSMTPD_PHASE_TX_ENVELOPE β€” osmtpd_register_report_envelope
    • OSMTPD_PHASE_TX_DATA β€” osmtpd_register_report_data
    • OSMTPD_PHASE_TX_COMMIT β€” osmtpd_register_report_commit
    • OSMTPD_PHASE_TX_ROLLBACK β€” osmtpd_register_report_rollback
    • OSMTPD_PHASE_PROTOCOL_CLIENT β€” osmtpd_register_report_client
    • OSMTPD_PHASE_PROTOCOL_SERVER β€” osmtpd_register_report_server
    • OSMTPD_PHASE_FILTER_RESPONSE β€” osmtpd_register_report_response
    • OSMTPD_PHASE_TIMEOUT. β€” osmtpd_register_report_timeout
  • int version_major β€” the major version number of the protocol. most filters don't need this information, so don't stress it. πŸ™ˆ
  • int version_minor β€” the minor version number of the protocol. again: most filters don't need this information.
  • struct timespec tm β€” the time the event was triggered inside smtpd(8).
  • int incoming β€” set to 1 if the event was based on an incoming connection, 0 if it's an outgoing connection. the osmtpd_register_filter class of functions is always based on incoming connections. osmtpd_register_report can be both incoming and outgoing. πŸ’–
  • uint64_t reqid β€” the request ID of the connection the event was issued on. this value can be useful for logging. filters in need of filter specific data can use osmtpd_local_session and local_session.
  • uint64_t token β€” the filter specific token. most filters don't need this information.
  • struct sockaddr_storage src β€” the source address and port of the connection. this needs to be cast to the appropriate sockaddr type based on the ss_family attribute. it can have the following families: AF_INET, AF_INET6 and AF_UNIX. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_SRC. if not available the entire attribute is zeroed out.
  • struct sockaddr_storage dst β€” the destination address and port of the connection. this needs to be cast to the appropriate sockaddr type based on the ss_family attribute. it can have the following families: AF_INET, AF_INET6 and AF_UNIX. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_DST. if not available the entire attribute is zeroed out.
  • char *rdns β€” the reverse DNS hostname of the connection. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_RDNS. if not available the attribute is set to NULL.
  • enum osmtpd_status fcrdns β€” whether the reverse DNS hostname is forward confirmed. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_FCRDNS. if not available the attribute is set to OSMTPD_STATUS_TEMPFAIL.
  • char *identity β€” the identity of the remote host as presented by the HELO or EHLO SMTP command. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_IDENTITY. if not available the attribute is set to NULL.
  • char *ciphers β€” the ciphers used during (start)tls. to use this attribute, initialize osmtpd_need with OSMTPD_NEED_CIPHERS. if not available the attribute is set to NULL.
  • uint32_t msgid β€” the message ID of the current message being handled in the SMTP transaction. this value can be useful for logging. osmtpd_need needs to be initialized with OSMTPD_NEED_MSGID. if not available the attribute is set to 0. filters in need of filter specific data can use osmtpd_local_message and local_message.
  • char *username β€” the username with which the session was successfully authenticated. osmtpd_need needs to be initialized with β€” okay bestie, ngl the original page literally glitches mid-sentence here and drops the constant name 😭 β€” and if not available or authentication failed, it's set to NULL.
  • char *mailfrom β€” the envelope MAIL FROM address in the SMTP transaction. osmtpd_need needs to be initialized with OSMTPD_NEED_MAILFROM. if not available the attribute is set to NULL.
  • char **rcptto β€” the envelope RCPT TO address in the SMTP transaction. osmtpd_need needs to be initialized with OSMTPD_NEED_RCPTTO. this attribute is a NULL-terminated array of address strings. if not available the first element in the array is set to NULL.
  • uint64_t evpid β€” the envelope ID we're currently working on. osmtpd_need needs to be initialized with OSMTPD_NEED_EVPID. if not available the attribute is set to 0.
  • void *local_session β€” any filter specific data that needs to be stored during the session. this is initialized on ctx creation by calling the oncreate argument from osmtpd_local_session.
  • void *local_message β€” any filter specific data that needs to be stored during the message transaction. this is initialized on ctx creation by calling the oncreate argument from osmtpd_local_message.

🚨 the one rule you cannot skip

The osmtpd_register_filter class of functions must call one of osmtpd_filter_proceed, osmtpd_filter_rewrite, osmtpd_filter_reject and osmtpd_filter_disconnect. must. fr fr.

You can do it either in the callback function itself, or at a later moment through another callback β€” your call, bestie. but note that the session stalls until the osmtpd_filter has been called. like literally just sits there waiting. 😭 so don't leave it hanging.

Exceptions to the above reply options are:

  • osmtpd_register_filter_connect's and osmtpd_register_filter functions' callbacks without argument can't use osmtpd_filter_rewrite.
  • osmtpd_register_filter_dataline's callback can only use osmtpd_filter_dataline.

🌸 the warn/err glow-up

osmtpd_warn, osmtpd_warnx, osmtpd_err and osmtpd_errx can be used as a standin for warn(3), warnx(3), err(3) and errx(3) without printing the program name to stderr. that's the whole vibe. additionally, osmtpd_warn and osmtpd_warnx prints the request ID on the logline when ctx is not NULL. obsessed. ✨


πŸ”₯ SEE ALSO πŸ”₯

event_init(3), smtpd.conf(5)


πŸ‘‘ HISTORY πŸ‘‘

The osmtpd_run API first appeared in OpenBSD 6.6. vintage but still iconic.


πŸ¦‹ AUTHORS πŸ¦‹

Martijn van Duren <martijn@openbsd.org>


and that's a wrap on this page, bestie πŸ’œ β€” this beauty lives at Linux 6.12.54-0-lts and it's stamped April 28, 2026. go write some filters, you're gonna ate. πŸŽ€

πŸ“¦ where this page comes from