<?xml version="1.0"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
<article>
	<title>The Amavis::check_mail function</title>
	<para>The <function>Amavis::check_mail</function> function is the main MTA-independent mail checking function, which performs all the work of running content scanners and deciding message destinies.  It also handles quarantining, recipient/admin notification messages, and forwarding the message or sending a DSN in cases where that is amavisd-new's responsibility.</para>
	<para>For each message, before calling <function>check_mail</function> you must call <classname>Amavis::check_mail_begin_task</classname> which clears the values of several variables. <classname> Amavis::In::SMTP</classname> calls this upon receiving the MAIL FROM command.  Other packages call it immediately before calling <function>check_mail</function>.</para>
	<para>A reference to the <function>check_mail</function> function is passed as an argument to the MTA request function.  It takes three arguments, described below.</para>
	<section>
		<title>Arguments</title>
		<section>
			<title>$conn: Amavis::In::Connection object</title>
			<para>The $conn object holds information about how the message was received by amavisd-new.  This object is created by <function>Amavis::process_request</function> and passed to the MTA-specific request function, which may then set further properties before passing it on to <function>check_mail</function>.</para>
			<para>The <symbol>proto</symbol> property holds the type of socket by which it was received: either TCP or UNIX, and is set by <function>Amavis::process_request</function>.  The <symbol>client_ip</symbol>, <symbol>socket_ip</symbol> and <symbol>socket_port</symbol> properties make sense only if the message was received by TCP, and are set by <function>Amavis::process_request</function> to the TCP/IP addresses of the client and of amavisd-new itself.  The <symbol>smtp_proto</symbol> and <symbol>smtp_helo</symbol> properties are meaningful only if the message was received by SMTP, and are set by <function>Amavis::In::SMTP::process_smtp_request</function>.  (The smtp_proto property is also set by <classname>Amavis::In::QMQPqq</classname>, to "QMQPqq").</para>
			<para>These properties are used for generating the Received line which is inserted whenever amavisd-new is responsible for forwarding the message.  They are used for dynamic message forwarding, in which the message is forwarded back to the same host from which it was received (and perhaps also to a port equal to the port on which amavisd-new received it, plus one).  The <symbol>client_ip</symbol> and <symbol>smtp_helo</symbol> are included in the Received-From-MTA header of DSNs if they are specified.  They are also used for logging within <function>process_smtp_request</function>.</para>
		</section>
		<section>
			<title>$msginfo: Amavis::In::Message object</title>
			<para>The $msginfo object holds all the information about the message itself.  The MTA-specific request function creates the object and sets various properties which are used by <function>check_mail</function> in processing.  Further properties are set by <function>check_mail</function> itself, both for use within MTA-independent code and to instruct the MTA-specific code on how to handle the message.  Those properties which may be considered by MTA-specific code after <function>check_mail</function> returns are considered below. Here are the properties which MTA-specific code should set before calling <function>check_mail</function>.</para>
			<variablelist>
				<varlistentry>
					<term>
						<symbol>rx_time</symbol>
					</term>
					<listitem>
						<para>The time at which the message was received by amavisd-new</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>client_addr</symbol>
					</term>
					<listitem>
						<para>IP address of the client MTA</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>client_name</symbol>
					</term>
					<listitem>
						<para>DNS name of the client MTA</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>client_proto</symbol>
					</term>
					<listitem>
						<para>Protocol by which mail was received ("ESMTP") - only used if mail is forwarded to an MTA which supports XFORWARD</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>client_helo</symbol>
					</term>
					<listitem>
						<para>Client MTA HELO name - only used if mail is forwarded to an MTA which supports XFORWARD</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>queue_id</symbol>
					</term>
					<listitem>
						<para>MTA queue ID of the message</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>msg_size</symbol>
					</term>
					<listitem>
						<para>The message size - <classname>Amavis::In::SMTP</classname> sets it from the ESMTP SIZE option; however if it is not set, <function>check_mail</function> can calculate it from the message</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>auth_user</symbol>
					</term>
					<listitem>
						<para>ESMTP AUTH user (set by <classname>Amavis::In::SMTP</classname> when ESMTP AUTH is used) - used to authenticate to the next-hop MTA</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>auth_pass</symbol>
					</term>
					<listitem>
						<para>ESMTP AUTH password (set by <classname>Amavis::In::SMTP</classname> when ESMTP AUTH is used) - used to authenticate to the next-hop MTA</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>sender</symbol>
					</term>
					<listitem>
						<para>The envelope sender of the message</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>mail_text</symbol>
					</term>
					<listitem>
						<para>File handle of the message text</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>mail_text_fn</symbol>
					</term>
					<listitem>
						<para>Path to the mail text file</para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>mail_tempdir</symbol>
					</term>
					<listitem>
						<para>Temporary directory for use by <function>check_mail</function></para>
					</listitem>
				</varlistentry>
				<varlistentry>
					<term>
						<symbol>delivery_method</symbol>
					</term>
					<listitem>
						<para>The method by which the mail will be delivered, or "" if this is the MTA's responsibility (set from the <symbol>forward_method</symbol> policy bank variable)</para>
					</listitem>
				</varlistentry>
			</variablelist>
			<para> In addition the <symbol>per_recip_data</symbol> property must also be initialised. This property holds an array of <classname>Amavis::In::Message::PerRecip</classname> objects, one holding information on each recipient of the message.  The <function>per_recip_data</function> method can be used to access this array.  To initialise it, you should use the <function>recips</function> method which simply takes a reference to an array of recipient addresses as an argument.  It takes care of creating an <classname>Amavis::In::Message::PerRecip</classname> object for each recipient, and setting their <symbol>recip_addr</symbol> properties. </para>
		</section>
		<section>
			<title>$dsn_per_recip_capable</title>
			<para>The $dsn_per_recip_capable should be 1 if your MTA can handle different destinies for different recipients, and 0 if it cannot.</para>
			<para>It is passed on to the mail_dispatch and one_response_for_all functions.</para>
			<para>one_response_for_all uses it to declare that amavisd-new must send DSNs if some but not all recipients REJECT the message and the MTA is not DSN-per-recip-capable.</para>
			<para>mail_dispatch hands it on to the various specific delivery method functions.  mail_via_smtp_single uses it to abort sending the message if there is an temporary error in response to one of the RCPT TO commands.  None of the others use it.</para>
		</section>
	</section>
	<section>
		<title>Return value</title>
		<para>The <symbol>check_mail</symbol> function returns a list of three values. </para>
		<section>
			<title>SMTP response</title>
			<para>The SMTP response is a string matching <literal>/^([1-5]\d\d) ([245]\.\d{1,3}\.\d{1,3})(?: |\z)(.*)\z/</literal> i.e. an SMTP response conforming to RFC 2821.  This is the default SMTP response you should use if you cannot use per-recipient responses.</para>
			<para>It is determined from the per-recipient responses and the <parameter>$dsn_per_recip_capable</parameter> argument by <function>Amavis::rfc2821_2822_Tools::one_response_for_all</function>.  It is decided as follows:</para>
			<procedure>
				<step>
					<para>If any recipient has a temporary failure (4xx), then the first such failure is returned.</para>
				</step>
				<step>
					<para>If any recipient has an invalid response (i.e. one not beginning 2, 4 or 5), then "450 4.5.1 Bad SMTP response??? " is returned.</para>
				</step>
				<step>
					<para>If all recipients have a destiny of D_DISCARD (in which case the response will begin 2xx), then the first recip SMTP response is returned.</para>
				</step>
				<step>
					<para>If all recipients have either a destiny of D_DISCARD or a REJECT (i.e. a 5xx response but destiny not D_BOUNCE), then a 5xx response is returned. Errors returned while trying to dispatch the mail for some recipients are preferred to errors from content scanning; subject to that, the first error is returned.</para>
				</step>
				<step>
					<para>If at least one recipient has a 2xx response and a destiny of D_PASS, then the first such is returned.</para>
				</step>
				<step>
					<para>If we get here, it means that there are some BOUNCEs, perhaps mixed with DISCARDs and REJECTs.  "250 2.5.0 OK" is returned.</para>
				</step>
			</procedure>
			<para>In the last two cases, the number of DISCARDs, BOUNCEs and REJECTs is also counted.  If there is at least one of these, these count values are appended to the response.</para>
			<para>The SMTP response will be set to "451 4.5.1 Error in processing" if an exception is thrown in <function>check_mail</function> before any mails (forwards, notifications or DSNs) are sent.</para>
		</section>
		<section>
			<title>Exit code</title>
			<para>The exit code again is an overall summary of the action to be taken by the MTA.  It is very much milter-specific.  It can take the values EX_OK (deliver the message), EX_NOUSER, EX_UNAVAILABLE, EX_NOPERM (reject the message), EX_TEMPFAIL (temporary failure) or 99 (discard the message).  These constants are exported by <classname>Amavis::rfc2821_2822_Tools</classname>.</para>
			<para>The exit code is also determined by <function>one_response_for_all</function>.  This will return EX_TEMPFAIL if it returns a 4xx SMTP response (rules 1 and 2).  It returns EX_UNAVAILABLE if it returns a 5xx response (rule 4).  If there it returns a PASS response (rule 5), then it returns EX_OK.  And finally, for rules 3 and 6 the MTA should discard the mail and amavisd-new will handle DSNs.  In this case, it returns 99 if <symbol>$msginfo-&gt;delivery_method</symbol> is "" (i.e. delivery is handled by the MTA) and EX_OK otherwise.</para>
			<para>It will also be set to EX_TEMPFAIL if an exception occurs which causes <function>check_mail</function> to set the SMTP response to 451.</para>
		</section>
		<section>
			<title>Preserve evidence</title>
			<para>This is 1 if an error occurred which should cause the temporary directory to be preserved to be analysed by the admin, and 0 if the temporary directory should be reused or deleted.</para>
		</section>
	</section>
	<section>
		<title>Per-recipient handling</title>
		<para>After <function>check_mail</function> returns, the per_recip_data of <parameter>$msginfo</parameter> will be filled in with instructions for MTAs capable of handling per-recipient destinies. Note that the order of elements of this array is preserved, which may assist processing. Each element has the following useful properties:</para>
		<variablelist>
			<varlistentry>
				<term>
					<symbol>recip_done</symbol>
				</term>
				<listitem>
					<para>This is set to 1 if the message has been bounced by <function>check_mail</function> or marked for discard or rejection, and to 2 if the message has been forwarded (in these cases the MTA should be told to drop the recipient).   It is left undefined if the MTA should be left to forward the message for this recipient.</para>
				</listitem>
			</varlistentry>
			<varlistentry>
				<term>
					<symbol>recip_smtp_response</symbol>
				</term>
				<listitem>
					<para>Per-recipient SMTP response</para>
				</listitem>
			</varlistentry>
			<varlistentry>
				<term>
					<symbol>recip_addr</symbol>
				</term>
				<listitem>
					<para>The original recipient address - useful for comparing to <symbol>recip_final_addr</symbol></para>
				</listitem>
			</varlistentry>
			<varlistentry>
				<term>
					<symbol>recip_final_addr</symbol>
				</term>
				<listitem>
					<para>The final recipient address, after any necessary modifications have been applied</para>
				</listitem>
			</varlistentry>
		</variablelist>
		<para>You may also wish to consult the <symbol>$msginfo-&gt;dsn_sent</symbol> property.  This is set to 1 if <function> check_mail</function> has sent a DSN and 2 if it has pretended to send one.  In these cases, the mail should be discarded for any recipients with a 5xx response code to prevent the MTA sending another DSN.</para>
		<para>If your MTA can handle per-recipient SMTP responses, then you should simply use the <symbol>recip_smtp_response</symbol> properties in combination with <symbol>$msginfo-&gt;dsn_sent</symbol>.</para>
		<para>If it can handle per-recipient discards but not per-recipient responses, then you should simply use the global SMTP response if it is not 2xx. If it is 2xx, then you should instruct the MTA to  drop recipients who have <symbol>recip_done</symbol> non-zero, and (if possible) to change any non-done recipients for whom <symbol>recip_final_addr</symbol> is not equal to <symbol>recip_addr</symbol>.</para>
	</section>
	<section>
		<title>Header edits</title>
		<para>Header edits are determined by the <function>add_forwarding_header_edits_common</function> and <function>add_forwarding_header_edits_per_recip</function> functions.  If notify_method is non-empty,   so that forwarding the message is <function>check_mail</function>'s responsibility, then it can apply the correct per-recip header edits independently for each recipient.  If <parameter>notify_method</parameter> is empty so that forwarding is the MTA's responsibility, then only a single set of header edits can be applied.  In this case the edits for the first passed recipient will be used (the common headers alone are insufficient as spam and virus tagging is only done in per-recip headers).</para>
		<para>The header edits are recorded in an <classname>Amavis::Out::EditHeader</classname> object, which is found in the <symbol>$msginfo-&gt;header_edits</symbol> property.  This object does not provide methods to access its properties, so you will have to treat it as a hash reference and access keys directly.  The <symbol>prepend</symbol> and <symbol>append</symbol> keys each contain an array of headers to be added, before or after existing headers respectively.  Each element of these arrays is simply a string containing the entire header line.  Both arrays contain the set of additional headers in the order in which they should appear in the message.</para>
		<para>The <symbol>edit</symbol> key contains a hash of headers to be edited.  The hash keys are the names of the header fields.  If a value is undef, then that header should simply be deleted.  Otherwise the value will be a code reference, which takes two arguments: the header name and the original value.  It returns the new value for the header.  If you need to obtain the original header value to pass to a header edit function, this can be obtained from <literal>$msginfo-&gt;mime_entity-&gt;head-&gt;get(<replaceable>header_name</replaceable>, 0)</literal>.</para>
	</section>
	<section>
		<title>Policy banks</title>
		<para>Policy banks are used to provide policy switching between different sources of mail.  The most important thing you need to know about them is that configuration variables which are part of policy banks should be accessed using the <function>c</function> (to access a scalar), <function>cr</function> (to obtain a reference to the value) or <function>ca</function> (to access an array, returning a reference) functions to access configuration variables which are part of policy banks, instead of accessing the global variables directly.</para>
		<para>In addition, you should load the MYNETS policy bank if the client MTA has a local IP address and MYUSERS if the sender has a local address.  This is determined using the <symbol>mynetworks_maps</symbol> and <symbol>local_domains_maps</symbol> settings respectively.  The code looks like this:</para>
		<programlisting>my($cl_ip) = $msginfo-&gt;client_addr;  my($sender) = $msginfo-&gt;sender;
    if ($cl_ip ne '' &amp;&amp; defined $policy_bank{'MYNETS'}
        &amp;&amp; lookup_ip_acl($cl_ip,@{ca('mynetworks_maps')}) ) {
      Amavis::load_policy_bank('MYNETS'); $policy_changed = 1;
    }
    if ($sender ne '' &amp;&amp; defined $policy_bank{'MYUSERS'}
        &amp;&amp; lookup(0,$sender,@{ca('local_domains_maps')})) {
      Amavis::load_policy_bank('MYUSERS'); $policy_changed = 1;
    }</programlisting>
		<para>Before changing the policy bank, you need to save a copy of the<symbol> %current_policy_bank</symbol> hash.  After mail processing is completed, you need to restore this hash  to its original value.  (Is this strictly necessary?)</para>
		<para>You should also enable debugging if the sender matches <symbol>debug_sender_maps</symbol>, by doing <literal>debug_oneshot(1)  if lookup(0, $sender, @{ca('debug_sender_maps')});</literal> (after loading relevant policy banks).</para>
	</section>
	<section>
		<title>Temporary directory</title>
		<para>The <function>check_mail</function> function requires a temporary directory to unpack the parts of the mail into.  It is the responsibility of the MTA-specific code to maintain this directory - it is suggested  to create one directory per child process and reuse it for each message, as creating and removing directories is a slow operation.  You can store the path to the temporary directory in a property of your persistent object.  If the <symbol>preserve_evidence</symbol> return value of <function> check_mail</function> is 1, then you should leave (not delete) the existing temporary directory and create a new one.  This can be done by setting the persistent property to undef immediately,  ensuring that the directory will not be deleted if the process terminates, and leaving it until the next message to create the directory since one does not exist already.</para>
		<section>
			<title>Creating the temporary directory</title>
			<para>You must ensure that your temporary directory has a name which will differ both from other child processes running at the same time and from other directories created by your own process (or even an earlier child with the same PID).  A suggested way of achieving this is to include the time and the PID in the temporary directory name: the standard format is <filename>amavis-<replaceable>DATE</replaceable>-<replaceable>PID</replaceable></filename> where <replaceable>DATE</replaceable> is in ISO 8601 format. <function> Amavis::rfc2821_2822_Tools::iso8601_timestamp</function> can be used to create this.  The directory should be a subdirectory of the <symbol>$TEMPBASE</symbol> directory.</para>
			<para>After choosing the directory name, you must ensure that the directory exists and is a directory (it is best to check this before calling <function>check_mail</function> for every message). If it is not a directory, you should return a temporary failure.  It is a good idea to check that the device or inode number of the directory remain the same, and if not to report this in the log. If it does not exist, you should create the directory with mode 0750 and record its device and inode number.</para>
		</section>
		<section>
			<title>Creating email file</title>
			<para>Your function may need to create a file to hold the text of the email.  This should be a file called <filename>email.txt</filename> in the temporary directory.  Creating it is done after creating the temporary directory, and in a similar way.  If the device/inode numbers have changed this is not as serious a problem as for the directory, as the file may have been replaced by some other tool.  In this case you should delete and re-create the file.</para>
		</section>
		<section>
			<title>Cleaning the temporary directory</title>
			<para>The temporary directory should be cleaned after calling <function>check_mail</function>, unless evidence is to be preserved.  This can be done simply by calling <literal><function>Amavis::Util::strip_tempdir</function>(<replaceable>dir</replaceable>)</literal>. This deletes all files in the <filename>parts</filename> subdirectory (creating this is handled for you by <function>check_mail</function>) and ensures that the temporary directory holds nothing other than a <filename>parts</filename> subdirectory and file <filename>email.txt</filename>.</para>
			<para>If <symbol>$can_truncate</symbol> is 0, you will also need to close and delete the <filename>email.txt</filename> file.</para>
		</section>
		<section>
			<title>Deleting the temporary directory</title>
			<para>When the child process terminates, the current temporary directory should be deleted.  This can be done in your class's destructor.  If the directory exists, then simply use <literal><function>Amavis::Util::rmdir_recursively</function>(<replaceable>dir</replaceable>)</literal> to delete it.</para>
		</section>
	</section>
</article>
