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/13 18:06] – jay | programming:python:python-libmilter [2012/02/20 18:42] – jay | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== |
+ | |||
+ | ===== 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. | ||
Line 6: | Line 8: | ||
Note that this documentation assumes a good working knowledge of the SMTP protocol as defined in RFC 2822. | Note that this documentation assumes a good working knowledge of the SMTP protocol as defined in RFC 2822. | ||
- | ====== The Library | + | ===== 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 ===== | ||
You will find that the library file is split up into sections defining the constants for client server options and flags. | You will find that the library file is split up into sections defining the constants for client server options and flags. | ||
- | ===== SMFIF_* | + | ==== 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. | 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. | ||
Line 31: | Line 42: | ||
^ SMFIF_ALLOPTS | This sets all flags | | ^ SMFIF_ALLOPTS | This sets all flags | | ||
- | ===== SMFIP_* | + | === 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). | 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). | ||
Line 66: | Line 77: | ||
^ SMFIP_ALLPROTOS | Sets all protocol options | | ^ 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