programming:python:python-libmilter
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
programming:python:python-libmilter [2011/12/12 20:38] – jay | programming:python:python-libmilter [2012/02/20 18:39] – jay | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== python-libmilter ====== | ||
+ | |||
====== About ====== | ====== About ====== | ||
python-libmilter started as a project to maximize the portability of libmilter. | python-libmilter started as a project to maximize the portability of libmilter. | ||
I also wanted some more choices for getting things done than the default thread approach. | I also wanted some more choices for getting things done than the default thread approach. | ||
+ | |||
+ | Note that this documentation assumes a good working knowledge of the SMTP protocol as defined in RFC 2822. | ||
+ | |||
+ | ====== Bug Reports, Issues and Contributions ====== | ||
+ | Please submit any and all bug reports/ | ||
+ | |||
+ | If you wish to contribute code patches, please use the repository fork and pull request mechanisms of [[http:// | ||
+ | |||
+ | If you have any other questions, you can always email me directly at admin@splitstreams.com. | ||
====== The Library ====== | ====== The Library ====== | ||
+ | You will find that the library file is split up into sections defining the constants for client server options and flags. | ||
+ | |||
+ | ===== Constants ===== | ||
+ | |||
+ | ==== SMFIF_* ==== | ||
+ | These are flags that are set defining mutations that we wish to make in our app. This first set is compatible with version 2 of the milter protocol. | ||
+ | |||
+ | ^ SMFIF_ADDHDRS | States that we may add headers to the email | | ||
+ | ^ SMFIF_CHGBODY | States that we may replace the body | | ||
+ | ^ SMFIF_ADDRCPT | States that we may add recipients | | ||
+ | ^ SMFIF_DELRCPT | States that we may delete recipients | | ||
+ | ^ SMFIF_CHGHDRS | States that we may change/ | ||
+ | ^ SMFIF_QUARANTINE | States that we may quarantine the message (how this is done is up to the MTA. Postfix will put the message in its HOLD queue) | | ||
+ | |||
+ | This next set is available in later milter protocol versions. | ||
+ | |||
+ | ^ SMFIF_CHGFROM | States that we may replace the envelope sender | | ||
+ | ^ SMFIF_ADDRCPT_PAR | States that we may add recipients + args | | ||
+ | ^ SMFIF_SETSYMLIST | We may send macro names (CURRENTLY UNSUPPORTED) | | ||
+ | |||
+ | There are also some convenience flags. | ||
+ | |||
+ | ^ SMFIF_ALLOPTS_V2 | This sets all flags from the first section | | ||
+ | ^ SMFIF_ALLOPTS_V6 | This sets all flags from the second section | | ||
+ | ^ SMFIF_ALLOPTS | This sets all flags | | ||
+ | |||
+ | ==== SMFIP_* ==== | ||
+ | These are protocol options, and can be set in a couple of different ways. The first way is to simply set them when setting up your factory (TODO: link to the factory section). | ||
+ | |||
+ | Again, this first section is available to version 2 of the milter protocol. | ||
+ | |||
+ | ^ SMFIP_NOCONNECT | Tell the MTA we don't want connection info | | ||
+ | ^ SMFIP_NOHELO | Tell the MTA we don't want HELO info | | ||
+ | ^ SMFIP_NOMAIL | Tell the MTA we don't want MAIL info | | ||
+ | ^ SMFIP_NORCPT | Tell the MTA we don't want RCPT info | | ||
+ | ^ SMFIP_NOBODY | Tell the MTA we don't want the body | | ||
+ | ^ SMFIP_NOHDRS | Tell the MTA we don't want the headers | | ||
+ | ^ SMFIP_NOEOH | Tell the MTA we don't want the end of headers notification | | ||
+ | |||
+ | These other protocol options are defined for new versions of the milter protocol than version 2. | ||
+ | |||
+ | ^ SMFIP_NR_HDR | Tell the MTA we don't reply to the headers | | ||
+ | ^ SMFIP_NOUNKNOWN | Tell the MTA we don't want any unknown commands | | ||
+ | ^ SMFIP_NODATA | Tell the MTA we don't want the DATA command sent to us | | ||
+ | ^ SMFIP_RCPT_REJ | Tell the MTA we want the rejected recipients | | ||
+ | ^ SMFIP_NR_CONN | Tell the MTA we don't reply to the headers | | ||
+ | ^ SMFIP_NR_HELO | Tell the MTA we don't reply to HELO info | | ||
+ | ^ SMFIP_NR_MAIL | Tell the MTA we don't reply to MAIL info | | ||
+ | ^ SMFIP_NR_RCPT | Tell the MTA we don't reply to RCPT info | | ||
+ | ^ SMFIP_NR_DATA | Tell the MTA we don't reply to the DATA command | | ||
+ | ^ SMFIP_NR_UNKN | Tell the MTA we don't reply to UNKNOWNs | | ||
+ | ^ SMFIP_NR_EOH | Tell the MTA we don't reply to the end of headers | | ||
+ | ^ SMFIP_NR_BODY | Tell the MTA we don't reply to the body chunks | | ||
+ | ^ SMFIP_HDR_LEADSPC | Header value has leading space | | ||
+ | |||
+ | And just like the OPTS, there are some convenience variables for setting all protocol options. | ||
+ | |||
+ | ^ SMFIP_ALLPROTOS_V2 | Set all protocol options for version 2 of the protocol | | ||
+ | ^ SMFIP_ALLPROTOS_V6 | Set all the protocol options for everything post version 2 | | ||
+ | ^ SMFIP_ALLPROTOS | Sets all protocol options | | ||
+ | |||
+ | ==== Response Constants ==== | ||
+ | These are what you use to reply to the MTA with what you want it to do currently. | ||
+ | |||
+ | Example: | ||
+ | <code python> | ||
+ | import libmilter | ||
+ | ... | ||
+ | ... | ||
+ | def eob(self , cmdDict): | ||
+ | return libmilter.CONTINUE | ||
+ | ... | ||
+ | ... | ||
+ | </ | ||
+ | |||
+ | ^ Constant ^ Description ^ | ||
+ | | ACCEPT | Accept this message | | ||
+ | | CONTINUE | Do nothing. | ||
+ | | REJECT | Reject this message/ | ||
+ | | TEMPFAIL | Temporarily fail this message/ | ||
+ | | DISCARD | Discard the message | | ||
+ | | CONN_FAIL | Cause a connection failure | | ||
+ | | SHUTDOWN | 421: shutdown (internal to MTA) | | ||
+ | |||
+ | ==== All the Rest ==== | ||
+ | All the rest of the standard constants are defined as well. I've only documented the ones above as these are the only ones directly used. | ||
+ | |||
+ | ===== Overridable Callbacks ===== | ||
+ | Here is the list of the overridable callbacks. | ||
+ | |||
+ | Note that some of these callbacks include a '' | ||
+ | |||
+ | As an aside, all this information is also available with a '' | ||
+ | |||
+ | ==== connect(self , hostname , family , ip , port , cmdDict) ==== | ||
+ | This gets the connection info: | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | hostname | ||
+ | | str | family | The IP family (L=unix , 4=ipv4 , 6=ipv6 , U=unknown) | | ||
+ | | str | ip| The IP of the connecting client | | ||
+ | | int | port | The port number of the connecting client | | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== helo(self , heloname) ==== | ||
+ | This gets the HELO string sent by the client | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | heloname | What the client HELOed as | | ||
+ | |||
+ | ==== mailFrom(self , frAddr , cmdDict) ==== | ||
+ | This gets the MAIL FROM envelope address | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | frAddr | The envelope from address | | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== rcpt(self , recip , cmdDict) ==== | ||
+ | This gets the RCPT TO envelope address | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | recip | The envelope recipient address | | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== header(self , key , val , cmdDict) ==== | ||
+ | This gets one header from the email at a time. The " | ||
+ | |||
+ | ex.: key=" | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | key | The header name | | ||
+ | | str | val | The header value | | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== eoh(self , cmdDict) ==== | ||
+ | This tells you when all the headers have been received | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== data(self , cmdDict) ==== | ||
+ | This is called when the client sends DATA | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== body(self , chunk , cmdDict) ==== | ||
+ | This gets a chunk of the body of the email from the MTA. This will be called many times for a large email. | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | str | chunk | A chunk of the email' | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== eob(self , cmdDict) ==== | ||
+ | This signals that the MTA has sent the entire body of the email. This is the callback where you can use [[# | ||
+ | |||
+ | ^ Type ^ Name ^ Description ^ | ||
+ | | dict | cmdDict | The raw dictionary of items sent by the MTA | | ||
+ | |||
+ | ==== close(self) ==== | ||
+ | Here, you can close any open resources. | ||
+ | |||
+ | **NOTE:** this method is **always** called when everything is complete. | ||
+ | |||
+ | ==== abort(self) ==== | ||
+ | This is called when an ABORT is received from the MTA. | ||
+ | |||
+ | **NOTE:** Postfix will send an ABORT at the end of every message. | ||
+ | |||
+ | ===== Modification Methods ===== | ||
+ | These are all the methods that can **only be used in the eob()** callback in your milter to modify the message/ | ||
+ | |||
+ | ==== addRcpt(self , rcpt , esmtpAdd='' | ||
+ | This will tell the MTA to add a recipient to the email. | ||
+ | |||
+ | There are essentially 2 different ways you can use this. The most basic way is to just call it with a recipient address: | ||
+ | <code python> | ||
+ | self.addRcpt(' | ||
+ | </ | ||
+ | That will add an envelope recipient to the message. | ||
+ | |||
+ | The second way is to call this and set the '' | ||
+ | **IMPORTANT: | ||
+ | |||
+ | ==== delRcpt(self , rcpt) ==== | ||
+ | This will tell the MTA to delete a recipient from the email | ||
+ | |||
+ | **NOTE:** The recipient address must be EXACTLY the same as one of the addresses received in the rcpt() callback. | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== replBody(self , body) ==== | ||
+ | This will replace the body of the email with a new body. This is how you would make changes to the body of an email. | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== addHeader(self , key , val) ==== | ||
+ | This will add a header to the email in the form: "key: val" | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== chgHeader(self , key , val='' | ||
+ | This will change a header in the email. | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== quarantine(self , msg='' | ||
+ | This tells the MTA to quarantine the message (put it in the HOLD queue in Postfix). | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== setReply(self , rcode , xcode , msg) ==== | ||
+ | Sets the reply that the MTA will use for this message.\\ | ||
+ | The '' | ||
+ | The '' | ||
+ | The '' | ||
+ | |||
+ | Example: | ||
+ | <code python> | ||
+ | self.setReply(555 , ' | ||
+ | </ | ||
+ | |||
+ | ==== chgFrom(self , frAddr , esmtpAdd='' | ||
+ | This tells the MTA to change the envelope From address, with optional ESMTP extensions in '' | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ==== skip(self) ==== | ||
+ | This tells the MTA that we don't want any more of this type of callback. | ||
+ | |||
+ | Unlike the other modifiers, **THIS CAN ONLY BE CALLED FROM THE body() callback!!**. | ||
+ | |||
+ | You must have the '' | ||
+ | |||
+ | ===== Factories ===== | ||
+ | When you are building your own milter, you will have to choose a factory to use to run your milter. | ||
+ | |||
+ | ^ AsyncFactory | A single threaded, single process asynchronous factory (not recommended) | | ||
+ | ^ ThreadFactory | Spawns a thread per connection from the MTA. Works well with minimal processing in your milter. | | ||
+ | ^ ForkFactory | Spawns a process for each connection from the MTA. Works the best when you don't need to share global resources | | ||
+ | |||
+ | When creating a factory object, you do so by calling the factory class ('' | ||
+ | |||
+ | <code python> | ||
+ | async = AsyncFactory(socketStr , MyMilter , smfifOptions , tcpListenQueue , sockChmod) | ||
+ | </ | ||
+ | |||
+ | The arguments are as follows: | ||
+ | ^ Arg ^ Type ^ Description ^ | ||
+ | | socketStr | str | This is a string representing the listen socket. | ||
+ | | MyMilter | libmilter.MilterProtocol | This should be your subclass implementation of '' | ||
+ | | smfifOptions | int | This should be any [[# | ||
+ | | tcpListenQueue | int | The TCP listen queue for the socket. | ||
+ | | sockChmod | int | What to '' | ||
+ | |||
+ | ==== AsyncFactory ==== | ||
+ | This factory is somewhat modeled after a Twisted implementation, | ||
+ | |||
+ | The " | ||
+ | |||
+ | <code python> | ||
+ | import libmilter | ||
+ | class MyMilter(libmilter.MilterProtocol): | ||
+ | ... | ||
+ | ... | ||
+ | @libmilter.callInThread | ||
+ | def connect(self , hostname , family , ip , port , cmdDict): | ||
+ | # Run checks on the IP. Perhaps this includes RBLs, a local database check, etc. | ||
+ | if processIP(ip): | ||
+ | return libmilter.CONTINUE | ||
+ | else: | ||
+ | return libmilter.REJECT | ||
+ | </ | ||
+ | |||
+ | **NOTE:** No " | ||
+ | |||
+ | ==== ThreadFactory ==== | ||
+ | This is a factory, wherein each connection from the MTA ends up in it's own thread. | ||
+ | |||
+ | This works well, with one caveat, the Python GIL. If you are making a single external call which can take a long time and is not considered blocking (like IO), that call can make your entire daemon unresponsive. | ||
+ | |||
+ | **NOTE:** If you choose use this, your milter protocol class implementation **must** inherit the '' | ||
+ | |||
+ | Example: | ||
+ | <code python> | ||
+ | import libmilter | ||
+ | class MyMilter(libmilter.ThreadMixin , libmilter.MilterProtocol): | ||
+ | ... | ||
+ | </ | ||
+ | |||
+ | ==== ForkFactory ==== | ||
+ | This is a factory that will fork a child process for each incoming connection from the MTA. This will add some more overhead on your machine, but is recommended for the best concurrency. | ||
+ | |||
+ | **NOTE:** If you choose use this, your milter protocol class implementation **must** inherit the '' | ||
+ | |||
+ | Example: | ||
+ | <code python> | ||
+ | import libmilter | ||
+ | class MyMilter(libmilter.ForkMixin , libmilter.MilterProtocol): | ||
+ | ... | ||
+ | </ | ||
+ | ====== Example ====== | ||
+ | See the examples directory in the distribution for a complete test milter example using the [[# |
programming/python/python-libmilter.txt · Last modified: 2023/11/10 20:06 by jay