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_connectosmtpd_register_filter_heloosmtpd_register_filter_ehloosmtpd_register_filter_starttlsosmtpd_register_filter_authosmtpd_register_filter_mailfromosmtpd_register_filter_rcpttoosmtpd_register_filter_dataosmtpd_register_filter_datalineosmtpd_register_filter_rsetosmtpd_register_filter_quitosmtpd_register_filter_nooposmtpd_register_filter_helposmtpd_register_filter_wizosmtpd_register_filter_commitosmtpd_register_report_connectosmtpd_register_report_disconnectosmtpd_register_report_identifyosmtpd_register_report_tlsosmtpd_register_report_authosmtpd_register_report_resetosmtpd_register_report_beginosmtpd_register_report_mailosmtpd_register_report_rcptosmtpd_register_report_envelopeosmtpd_register_report_dataosmtpd_register_report_commitosmtpd_register_report_rollbackosmtpd_register_report_clientosmtpd_register_report_serverosmtpd_register_report_responseosmtpd_register_report_timeoutosmtpd_local_sessionosmtpd_local_messageosmtpd_needosmtpd_filter_proceedosmtpd_filter_rejectosmtpd_filter_disconnectosmtpd_filter_rewriteosmtpd_filter_datalineosmtpd_runosmtpd_warnosmtpd_warnxosmtpd_errosmtpd_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 areOSMTPD_TYPE_FILTERandOSMTPD_TYPE_REPORT.enum osmtpd_phase phaseβ the phase in the transaction which triggered the callback. The following values match their respectiveosmtpd_registerfunction (full list right below, keep scrolling π):OSMTPD_PHASE_CONNECTβosmtpd_register_filter_connectOSMTPD_PHASE_HELOβosmtpd_register_filter_heloOSMTPD_PHASE_EHLOβosmtpd_register_filter_ehloOSMTPD_PHASE_STARTTLSβosmtpd_register_filter_starttlsOSMTPD_PHASE_AUTHβosmtpd_register_filter_authOSMTPD_PHASE_MAIL_FROMβosmtpd_register_filter_mailfromOSMTPD_PHASE_RCPT_TOβosmtpd_register_filter_rcpttoOSMTPD_PHASE_DATAβosmtpd_register_filter_dataOSMTPD_PHASE_DATA_LINEβosmtpd_register_filter_datalineOSMTPD_PHASE_RSETβosmtpd_register_filter_rsetOSMTPD_PHASE_QUITβosmtpd_register_filter_quitOSMTPD_PHASE_NOOPβosmtpd_register_filter_noopOSMTPD_PHASE_HELPβosmtpd_register_filter_helpOSMTPD_PHASE_WIZβosmtpd_register_filter_wizOSMTPD_PHASE_COMMITβosmtpd_register_filter_commitOSMTPD_PHASE_LINK_CONNECTβosmtpd_register_report_connectOSMTPD_PHASE_LINK_DISCONNECTβosmtpd_register_report_disconnectOSMTPD_PHASE_LINK_IDENTIFYβosmtpd_register_report_identifyOSMTPD_PHASE_LINK_TLSβosmtpd_register_report_tlsOSMTPD_PHASE_LINK_AUTHβosmtpd_register_report_authOSMTPD_PHASE_TX_RESETβosmtpd_register_report_resetOSMTPD_PHASE_TX_BEGINβosmtpd_register_report_beginOSMTPD_PHASE_TX_MAILβosmtpd_register_report_mailOSMTPD_PHASE_TX_RCPTβosmtpd_register_report_rcptOSMTPD_PHASE_TX_ENVELOPEβosmtpd_register_report_envelopeOSMTPD_PHASE_TX_DATAβosmtpd_register_report_dataOSMTPD_PHASE_TX_COMMITβosmtpd_register_report_commitOSMTPD_PHASE_TX_ROLLBACKβosmtpd_register_report_rollbackOSMTPD_PHASE_PROTOCOL_CLIENTβosmtpd_register_report_clientOSMTPD_PHASE_PROTOCOL_SERVERβosmtpd_register_report_serverOSMTPD_PHASE_FILTER_RESPONSEβosmtpd_register_report_responseOSMTPD_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 insidesmtpd(8).int incomingβ set to1if the event was based on an incoming connection,0if it's an outgoing connection. theosmtpd_register_filterclass of functions is always based on incoming connections.osmtpd_register_reportcan 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 useosmtpd_local_sessionandlocal_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 appropriatesockaddrtype based on thess_familyattribute. it can have the following families:AF_INET,AF_INET6andAF_UNIX. to use this attribute, initializeosmtpd_needwithOSMTPD_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 appropriatesockaddrtype based on thess_familyattribute. it can have the following families:AF_INET,AF_INET6andAF_UNIX. to use this attribute, initializeosmtpd_needwithOSMTPD_NEED_DST. if not available the entire attribute is zeroed out.char *rdnsβ the reverse DNS hostname of the connection. to use this attribute, initializeosmtpd_needwithOSMTPD_NEED_RDNS. if not available the attribute is set toNULL.enum osmtpd_status fcrdnsβ whether the reverse DNS hostname is forward confirmed. to use this attribute, initializeosmtpd_needwithOSMTPD_NEED_FCRDNS. if not available the attribute is set toOSMTPD_STATUS_TEMPFAIL.char *identityβ the identity of the remote host as presented by theHELOorEHLOSMTP command. to use this attribute, initializeosmtpd_needwithOSMTPD_NEED_IDENTITY. if not available the attribute is set toNULL.char *ciphersβ the ciphers used during (start)tls. to use this attribute, initializeosmtpd_needwithOSMTPD_NEED_CIPHERS. if not available the attribute is set toNULL.uint32_t msgidβ the message ID of the current message being handled in the SMTP transaction. this value can be useful for logging.osmtpd_needneeds to be initialized withOSMTPD_NEED_MSGID. if not available the attribute is set to0. filters in need of filter specific data can useosmtpd_local_messageandlocal_message.char *usernameβ the username with which the session was successfully authenticated.osmtpd_needneeds 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 toNULL.char *mailfromβ the envelopeMAIL FROMaddress in the SMTP transaction.osmtpd_needneeds to be initialized withOSMTPD_NEED_MAILFROM. if not available the attribute is set toNULL.char **rcpttoβ the envelopeRCPT TOaddress in the SMTP transaction.osmtpd_needneeds to be initialized withOSMTPD_NEED_RCPTTO. this attribute is aNULL-terminated array of address strings. if not available the first element in the array is set toNULL.uint64_t evpidβ the envelope ID we're currently working on.osmtpd_needneeds to be initialized withOSMTPD_NEED_EVPID. if not available the attribute is set to0.void *local_sessionβ any filter specific data that needs to be stored during the session. this is initialized on ctx creation by calling theoncreateargument fromosmtpd_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 theoncreateargument fromosmtpd_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 andosmtpd_register_filterfunctions' callbacks without argument can't useosmtpd_filter_rewrite.osmtpd_register_filter_dataline's callback can only useosmtpd_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
- alpine 3.24: libopensmtpd-doc 1.0-r0 Β· ISC
- Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms