FBB::Milter(3bobcat)
β¨ FBB::Milter β¨
the manual page for FBB::Milter β section 3bobcat, a.k.a. the Milter interface. Bestie, buckle up, this one's a whole journey and it's lowkey iconic. π
π NAME π
FBB::Milter β Interface to the sendmail mail filter facilities π¦
πΈ SYNOPSIS πΈ
#include <bobcat/milter>
Linking option: -lmilter -lbobcat
π DESCRIPTION π
Okay so Milter defines an abstract base class that interfaces to the sendmail mail filter (milter) facilities. It gives you a C++ interface, built on the assumption that a single mail filter program does not implement multiple mail filters. The classic sendmail C-based Milter API uses a (SMFICTX) pointer that represents a mail connection, plus a pointer to connection-specific private data, and that whole situation makes the Milter constructor do a lot of administrative busywork. In a C environment that's fine, whatever, but those administrative tasks distract from the actual main character energy: the Milter's mail filtering functionality. The FBB::Milter class hides all that admin stuff from you, the programmer, so you can focus on filtering mail like a legend. β¨
So the main benefits of Milter are, fr fr:
- Basic administration is performed by the
Milterclass π«Ά - The class' interface is more C++ like than the raw C interface offered by the milter API π
- Administration, allocation and communicating of connection specific data is no longer required π
- It is not normally necessary to use connection-specific data, like a pointer identifying the connection, anymore when implementing the Milter π
Milteruses current-day design patterns enforcing principles of reusable software, thus simplifying the construction of the actual Milter. To activate a milter from thesendmail.mcconfiguration file, use, e.g.,INPUT_MAIL_FILTER(name',S=socket'), where name is the milter's name, and socket is the name of the socket. See also thesetConnectionmember below. π
π§ NAMESPACE π§
FBB
All constructors, members, operators and manipulators, mentioned in this man-page, are defined in the namespace FBB.
π« INHERITS FROM π«
-
π ENUMERATIONS π
The class defines four (public) enumerations, bestie. One enumeration is used to indicate the callback-functions that need to be called, the second one renames the flags that can be passed to sendmail to indicate which actions the milter is allowed to perform. The third one defines status values that may be used to inform sendmail how to further process a message. The fourth one defines return values. The enumerations are:
πΈ enum CallBack
This enumeration holds the following values:
CONNECTβ Indicates that the milter defines (overrides) the connection-functionality. This is the first callback function that can be called by sendmail;HELOβ Indicates that the milter defines (overrides) the helo-functionality. This indicates that thehelofunction should be called by sendmail, providing the milter with information about the connecting client;SENDERβ Indicates that the milter defines (overrides) the sender-functionality. This indicates that thesenderfunction should be called by sendmail, providing the milter with the sender's envelope (initialFrom ...line) information;RECIPIENTβ Indicates that the milter defines (overrides) the recipient-functionality. This indicates that therecipientfunction should be called by sendmail, providing the milter with the recipient's envelope (To: ...header address) information;HEADERβ Indicates that the milter defines (overrides) the header-functionality. This indicates that theheaderfunction should be called by sendmail for each mail header that is used in the current mail message;EOHβ Indicates that the milter defines (overrides) the end-of-header-functionality. This indicates that theeohfunction should be called by sendmail once all header lines have been processed;BODYβ Indicates that the milter defines (overrides) the body-functionality. This indicates that thebodyfunction should be called by sendmail, offering the mail-body to the milter;EOMβ Indicates that the milter defines (overrides) the end-of-message-functionality. This indicates that theeomfunction should be called by sendmail, once all elements of the e-mail message have been processed;ABORTβ Indicates that the milter defines (overrides) the abort-functionality. Theabortfunction may be called by sendmail beforeeomis called. It should reclaim all resources used by the message, but not delete any memory allocated by the milter, as this isclose's job;CLOSEβ Indicates that the milter defines (overrides) the close-functionality. Theclosefunction should delete all (connection specific) memory allocated by the milter. It may be calledout-of-order, i.e. even beforeconnectis called and developers should anticipate this possibility when crafting theirclosecode. In particular, it is incorrect to assume the private context pointer will be something other than 0 in this callback;UNKNOWNβ Currently not used. Reserved for versions exceeding version 2 of the sendmail milter API;DATAβ Currently not used. Reserved for versions exceeding version 3 of the sendmail milter API;ALL_CALLBACKSβ Shortcut to indicate all callback facilities. TheCallBackvalues are bit-flags. Thebit_oroperator may be used to combine them, and thebit_notoperator may be used to remove a flag fromALL_CALLBACKS(e.g.,ALL_CALLBACKS && ~ABORT).
π
enum Flags
This enumeration holds the following values:
NO_FLAGSβ No flags are defined;ADD_HEADERSβ This flag indicates that the milter is allowed to add headers to the current e-mail message;ADD_RECIPIENTSβ This flag indicates that the milter is allowed to add recipients to the current e-mail message;CHANGE_BODYβ This flag indicates that the milter is allowed to modify the message's body content;CHANGE_HEADERSβ This flag indicates that the milter is allowed to change headers of the current e-mail message;DELETE_RECIPIENTSβ This flag indicates that the milter is allowed to remove recipients from the current e-mail message;QUARANTINEβ This flag indicates that the milter is allowed to request sendmail to quarantine the current e-mail message;ALL_FLAGSβ Shortcut to indicate all callback facilities. TheFlagsvalues are bit-flags. Thebit_oroperator may be used to combine them, and thebit_notoperator may be used to remove a flag fromALL_FLAGS(e.g.,ALL_FLAGS && ~QUARANTINE).
π Status
This enumeration simplifies the extended SMFIS_ values used by the C API. These values may be used to return sfsistat values:
ACCEPTβ This value is equal toSMFIS_ACCEPT, indicating that Sendmail should accept the message. For a connection-oriented callback (see below at PROTECTED VIRTUAL MEMBER FUNCTIONS), accept this connection without further filter processing, callclose(see below). For other callbacks: accept this message without further filtering;CONTINUEβ This value is equal toSMFIS_CONTINUE, indicating that Sendmail should continue processing. This is the default for all callback functions which are not overridden by the class derived fromMilter;DISCARDβ This value is equal toSMFIS_DISCARD, indicating that Sendmail should discard the mail message. It should not be returned by a connection-oriented callback. For other callbacks: the message is accepted, but silently discarded;REJECTβ This value is equal toSMFIS_REJECT, indicating that Sendmail should reject the mail message. For a connection-oriented callback, reject this connection; callclose. For a message-oriented callback (except foreomorabort, see below), reject this message. For a recipient-oriented callback, reject the current recipient (but continue processing the current message);TEMPFAILβ This value is equal toSMFIS_TEMPFAIL, indicating that Sendmail should return atemporary unavailablemessage to the sender of the mail message. For a message-oriented callback (exceptsender, see below), fail for this message. For a connection-oriented callback, fail for this connection and callclose. For a recipient-oriented callback, only fail for the current recipient and continue message processing.
π¦ Return
This enumeration simplifies the extended MI_ values used by the C API. Most return values used by the Milter class, however, are bool values. The Return values are:
FAILUREβ This value is equal toMI_FAILURE, indicating that a C-api function failed to perform its task;SUCCESSβ This value is equal toMI_SUCCESS, indicating that a C-api function succeeded in performing its task.
π CONSTRUCTOR/DESTRUCTOR π
Milter()β The default constructor is available for derived classes. It performs no actions;virtual ~Milter()β The (public) virtual destructor performs no actions: derived classes can override the destructor to suit their own needs.
Overloaded assignment operators are not available. π«
β¨ PUBLIC STATIC MEMBER FUNCTIONS β¨
okay bestie these ones are literally the whole soul of the Milter base class β you call them to initialize, start and stop your milter. no biggie. just the entire lifecycle of your daemon. π
void initialize(std::string const &name, Milter &milter, callback_set callbacks = CONNECT, flag_set flags = NO_FLAGS)β this is the setup era, the glow-up ritual. it initializes the Milter's administration. first arg = the name of the mailfilter. second arg = a reference to aMilterobject β and becauseMilteris an abstract base class, the actual object you pass is always an object of a class derived fromMilter. (yes bestie that's the whole point, you subclass it.) third arg = which callbacks to call for this milter, and by default the connect and close callbacks are called. heads up, starting bobcat version 6.04.00: the close callback is always called once a connection ends β you can specify it when you pass the callbacks argument, but if you don't, the close callback still gets called. no escape. π last arg = flags, which specify the Milter's capabilities.std::string const &name()β returns the milter's name. that's it. that's the function. iconic minimalism. πbool start()β this one callssmfi_mainand takes control of the milter's event loop. returnstrueif the event loop is successfully terminated.void stop()β shuts down the milter's event loop, but after finishing all threads (polite queen behavior π«Ά). once you've called this,startcan be called again to continue the milter. it's giving pause-and-resume energy.
π PROTECTED MEMBER FUNCTIONS π
these next ones are non-virtual, and they can be called by members of classes derived from Milter. so basically: your subclass's little private toolbox. π§
bool addHeader(std::string const &hdrName, std::string const &hdrValue)β only callable fromeom, and theADD_HEADERSflag must have been specified or it will fail.hdrNameandhdrValuemust both be non-empty strings. each line of the header must be under 2048 characters and should be under 998 characters. need longer headers? make them multi-line, bestie. to make a multi-line header, insert a line feed (\n) followed by at least one whitespace character such as a space or a tab (\t). the line feed should not be preceded by a carriage return. and fr fr, it is the filter writer's responsibility to make sure no standards are violated β that's on you, not onMilter. πbool addRecipient(std::string const &rcptName)β only callable fromeom, and theADD_RECIPIENTSflag must have been specified or it will fail.bool changeHeader(std::string const &hdrName, size_t headerNr, std::string const &hdrValue)β only callable fromeom, and theCHANGE_HEADERSflag must have been specified or it will fail. seeaddHeaderfor the header-requirements (yes, all of them, same rules).headerNris a 1-based header index value. aheaderNrvalue of 1 will modify the first occurrence of a header namedhdrValue. ifheaderNris greater than the number of timeshdrNameappears, a brand newhdrName-header will be added. and ifhdrValueis empty? the header gets deleted. gone. no trace. πbool deleteRecipient(std::string const &rcptName)β only callable fromeom, and theDELETE_RECIPIENTSflag must have been specified or it will fail. this one removes the named recipient from the current message's envelope.SMFICTX *id() constβ theMilterobject can call this to get a pointer identifying its sendmail-connection. ngl, normally you shouldn't need to call this one. it's the "iykyk, but you probably don't need it" member. πbool insertHeader(size_t hdrIdx, std::string const &hdrName, std::string const &hdrValue)β only callable fromeom, and theADD_HEADERSflag must have been specified or it will fail. seeaddHeaderfor the header-requirements. theheaderNrparameter is a header index value β aheaderNrvalue of 0 will insert this header as the first of thehdrNameheaders. and ifheaderNris greater than the number of timeshdrNameappears, a newhdrName-header will be added. β¨bool openSocket(bool removeIfTrue = true)β call this beforestart. it attempts to create the socket specified bysetConnection(scroll down bestie, it's right there), which lets the calling application make sure the socket can actually be created β and possibly change its protection (access rights) before the milter starts its work. if you don't call it, it will be called implicitly whenrunis started. returnstrueif the socket could be created. π§bool quarantine(std::string const &reason)β only callable fromeom. causes the MTA to quarantine the message using the given reason. it's giving time out, you're in the corner now. π«bool replaceBody(std::string const &body)β only callable fromeom, and theCHANGE_BODYflag must have been specified or it will fail. it can be called multiple times, in which case the various body contents are concatenated into the final message. newlines should be coded asCRLF.bool setBacklog(size_t backlog = 5)β call this beforestart. sets the incoming socket backlog used bylisten(2). ifsetBacklogisn't called, the operating system default is used. returnsfalseif the backlog couldn't be set as requested. and it is the programmer's responsibility not to call this function with a0argument β do not do it, bestie, that's on you. π€bool setConnection(std::string const &name)β call this beforestart. sets the socket through which the filter communicates with sendmail. the socket can be specified using one of these variants:
{unix|local}:/path/to/file - A named pipe
net:port@{hostname|ip-address} - An IPV4 socket
inet6:port@{hostname|ip-address} - An IPV6 socket
if possible, filters should not run as root when communicating over unix/local domain sockets. π¨
unix/local sockets should have their permissions set to 0600 (read/write permission only for the socket's owner) or 0660 (read/write permission for the socket's owner and group), which is useful if the sendmail RunAsUser option is used. the permissions for a unix/local domain socket are determined as usual by umask, which should be set to 007 or 077.
okay this part is important: possible failure of this function cannot be determined from its return value. instead, run will fail. so don't be out here trusting that return value, bestie. π
setReply(std::string const &rcode, std::string const &xcode = "", std::string const &msg = "")β sets the default SMTP error reply code. it can be called from any callback member, exceptconnect.rcodeshould be a three-digit (RFC 821/2821) SMTP reply code, and it must be a valid4XXor5XXreply code.xcode, when specified, must be an extended (RFC 1893/2034) reply code.msgmay be an additional textual message. heads up: theMilterclass has no member comparable to the libmilter API functionsmfi_setmlreply. theMilterclass. (that's how the original ends the sentence, and honestly same, it just. stops there. π)void setTimeout(size_t seconds = 7210)β call this beforestart. sets the number of seconds libmilter will wait for an MTA connection before timing out a socket. ifsetTimeoutisn't called, a default timeout of7210seconds is used. and again: it is the programmer's responsibility not to call this function with an argument equal to0. β°char const *symval(std::string const &name) constβ returns the value of a specific sendmail macro.nameshould be set to the name of a sendmail macro. single letter macros can optionally be enclosed in braces ({and}), but longer macro names must be enclosed in braces, just as in asendmail.cffile.0is returned if the macro isn't defined. by default, these macros are valid in the given contexts:- for
connect:daemon_name,if_name,if_addr,j,_ - for
helo:tls_version,cipher,cipher_bits,cert_subject,cert_issuer - for
sender:i,auth_type,auth_authen,auth_ssf,auth_author,mail_mailer,mail_host,mail_addr - for
recipient:rcpt_mailer,rcpt_host,rcpt_addr
- for
all macros stay in effect from the point they are received until the end of the connection for the first two sets, until the end of the message for the third set (sender), and just for each recipient for the final set (recipient).
the following macros may be specified in the sendmail.mc configuration file:
define(`confMILTER_MACROS_CONNECTβ, `m1β, ...),
define(`confMILTER_MACROS_HELOβ, ...),
define(`confMILTER_MACROS_ENVFROMβ, ...),
define(`confMILTER_MACROS_ENVRCPTβ, ...),
where `m1β, ... represents a comma separated list of returnable macros. single letter macros can optionally be enclosed in braces ({ and }), longer macro names must be enclosed in braces. πΈ
bool wait()β only callable fromeom. tells the MTA that the filter is still working on a message, which causes the MTA to re-start its timeouts. hold on hold on i'm not done yet energy. β
π PRIVATE VIRTUAL MEMBER FUNCTIONS β¨
okay bestie, this is the part where it gets juicy πΈ β everything left in class Milter is only useful for Milter implementations living in classes derived from Milter. These members below can be overridden by derived classes. And heads up, icon: clone must be overridden. No skipping, no negotiating. π
Except for clone, every single member in this section is a callback function. Which means: the MTA calls them to process parts of the mail message. And they come in three flavors, and yes we're distinguishing them like a true main character π
- Recipient-oriented callbacks (
recipient, see below) can affect the processing of a single message to a single recipient. - Connection-oriented callbacks (
connect,heloandclose) affect the processing of the entire connection β and during that one connection, multiple messages may be delivered to multiple sets of recipients. multitasking queen π - The remaining callbacks are message-oriented, affecting the processing of a single message to all its recipients.
virtual sfsistat abort()β this message-oriented member may be called at any time during message processing, i.e. between some message-oriented routine andeom.abortreclaims any resources allocated on a per-message basis β and no, that's not the connection specific data, that's the derived class' destructor's whole job, stay in your lane.abortmust be tolerant of being called between any two message-oriented callbacks. It's only called if the message gets aborted outside the filter's control and the filter hasn't completed its message-oriented processing. For example: if a filter has already returnedACCEPT,REJECT, orDISCARDfrom a message-oriented routine,abortwon't be called, even if the message is later aborted outside its control. your fave is ghosted πvirtual sfsistat body(unsigned char *text, size_t length)β this message-oriented member is called zero or more times betweeneohandeom.textpoints to a sequence of bytes. It is not necessarily 0-terminated. Also, the sequence may contain 0-characters. And since message bodies can be very large, definingbodycan significantly impact filter performance β this is giving "heavy is the head that wears the crown" π. End-of-lines are represented as received from SMTP (normallyCR/LF). Later filters will see body changes made by earlier ones, and message bodies may be sent in multiple chunks, with one call tobodyper chunk. fr fr, chunk by chunk πvirtual Milter *clone() const = 0β this pure virtual function must be implemented by derived classes, and it returns a newly allocated copy of the derived object passed to theinitializestatic member. It's used by the standardvirtual constructordesign pattern. Destruction of the allocated object is the responsibility ofclone's caller β so like, you allocated it, you're the parent now, do your duty π«Άvirtual sfsistat close()β this connection-oriented member is always called once at the end of each connection. It may be called "out-of-order", i.e. before evenconnectis called. Here's the lore: after a connection is established by the MTA to the filter, if the MTA decides this connection's traffic will be discarded (e.g. via anaccess_dbresult), no data will be passed to the filter from the MTA until the client closes down. At that time,closeis called. So yes, it can be the only callback ever used for a given connection, and developers should anticipate this possibility when crafting theirclosecode.closeis called on close even if the previous mail transaction was aborted. The default implementation of the membercloseis
sfsistat Milter::close()
{
smfi_setpriv(d_ctx, 0); // delete this d_ctx data
return CONTINUE;
}
By calling smfi_setpriv with second argument 0, any data associated with the current connection is deleted by the milter. Implementations overriding close should at least call smfi_setpriv, passing the value returned by the member id (cf. section PROTECTED MEMBER FUNCTIONS) as first argument and 0 as second argument. Other data managed by the milter and specifically associated with a connection should be accessed via the connection's id β e.g. using a std::unordered_map using an SMFICTX * as key. it's giving keyed lookup realness π
virtual sfsistat connect(char *hostname, _SOCK_ADDR *hostaddr)β this connection-oriented member may be called once, at the start of each SMTP connection. The parameterhostnameis the host name of the message sender, as determined by a reverse lookup on the host address. If the reverse lookup fails,hostnamewill contain the message sender's IP address enclosed in square brackets (e.g.[a.b.c.d]). The parameterhostaddris the host address, as determined by agetpeername(2)call on the SMTP socket. It's0if the type isn't supported in the current version, or if the SMTP connection is made viastdin. sometimes the answer is just0bestie πvirtual sfsistat data()β Not yet supported. Will be available with libmilter versions beyond 3. yea she's not here yet, manifesting β¨virtual sfsistat eoh()β this message-oriented member is called once after all headers have been processed. end of headers, end of an era πvirtual sfsistat eom()β this message-oriented member is called once after all calls tobodyfor a given message have been completed. Note that only in this function can modifications to the message headers, body, and envelope be made (see theadd-,change- anddelete- members listed below). one function to rule them all πvirtual sfsistat header(char *headerf, char *headerv)β this message-oriented member is called zero or more times betweenrecipientandeoh, once for each e-mail header. Theheaderfparameter contains the text of the header, theheadervparameter contains its value. E.g., if an e-mail message contains the following headers:
From: sender <f@example.com>
Subject:no
then header will be called twice, with the following values for, respectively, headerf and headerv:
First header: "From", " sender <f@example.com>"
Second header: "Subject", "no"
Further details about header information is given in RFC 882. yes that space before sender is real, don't @ me π
virtual sfsistat helo(char *helohost)β this connection-oriented member is called whenever the client sends aHELO/EHLOcommand. It may therefore be called between zero and three times. Thehelohostparameter should be the domain name of the sending host β but is, in practice, anything the sending host wants to send. so basically it's the wild west out here π€virtual sfsistat recipient(char **argv)β this recipient-oriented member is called once per recipient (provided by theTo:orCc:headers), hence one or more times per message, immediately aftersender. The parameterargvis a 0-terminated array of pointers to SMTP command arguments;argv[0]is guaranteed to be the recipient address. Later arguments are the ESMTP arguments. Here's the response tea β:TEMPFAILmay be returned, to indicate that sendmail should return a temporary failure for this particular recipient; further recipients may still be sent,abortis not called.REJECTwill reject this particular recipient; further recipients may still be sent,abortis not called.DISCARDwill accept (but discard) the message,abortwill be called.ACCEPTwill accept recipient,abortwill not be called.
More details on ESMTP responses are described in RFC 1869. iykyk π¬
virtual sfsistat sender(char **argv)β this message-oriented member is called once at the beginning of each message, beforerecipient.argv[0]is guaranteed to be the sender's envelope address (provided in the e-mail's initialFrom ...line). Later arguments are the ESMTP arguments. And the response menu π½οΈ:TEMPFAILmay be returned, indicating that sendmail should return a temporary failure for this particular message,abortis not called.REJECTwill reject this message,abortis not called.DISCARDwill accept (but discard) the message,abortwill be called.ACCEPTwill accept recipient,abortwill not be called.
More details on ESMTP responses are described in RFC 1869.
virtual sfsistat unknown(char *ptr)β Not yet supported. Will be available with libmilter versions beyond 2. mystery member, she's mysterious π
πΈ EXAMPLE πΈ
To do
literally just vibes in there. no cap, that's the whole thing. π
π FILES π
bobcat/milterβ defines the class interface
β¨ SEE ALSO β¨
bobcat(7),getpeername(2),listen(2)http://www.milter.org(e.g.,http://www.milter.org/developers/api)http://sendmail.org/m4/readme.htmlhttp://rfc.net/rfc821.htmlhttp://rfc.net/rfc822.htmlhttp://rfc.net/rfc1869.htmlhttp://rfc.net/rfc1893.htmlhttp://rfc.net/rfc2034.htmlhttp://rfc.net/rfc2821.html/usr/include/libmilter/mfapi.h
go read the RFCs bestie, they're long but they're iconic ππ¦
π BUGS π
-lmiltermust be specified before-lbobcat.
order matters, always. put her first or she throws a fit π
π«Ά BOBCAT PROJECT FILES π«Ά
https://fbb-git.gitlab.io/bobcat/: gitlab project page;- Debian Bobcat project files:
libbobcat6: debian package containing the shared library, changelog and copyright note;libbobcat-dev: debian package containing the static library, headers, manual pages, and developer info;
π BOBCAT π
Bobcat is an acronym of `Brokken's Own Base Classes And Templates'. cute name, ate π
π COPYRIGHT π
This is free software, distributed under the terms of the GNU General Public License (GPL).
free as in freedom, slay ποΈ
β AUTHOR β
Frank B. Brokken (f.b.brokken@rug.nl).
and that's a wrap bestie π₯° this page lives in libbobcat-dev_6.15.01, dated 2005-2026, brought to you by FBB β FBB::Milter(3bobcat). go forth and filter those mails, you absolute legend π¦πβ¨
π¦ where this page comes from
- ubuntu stonking: libbobcat-dev 6.15.01-1 Β· GPL-3.0-or-later WITH OpenSSL exception
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms