[mireka] r192 committed - Mireka can act as a null client: all outgoing mails are sent via anoth...

25 views
Skip to first unread message

mir...@googlecode.com

unread,
May 9, 2013, 6:41:57 PM5/9/13
to mir...@googlegroups.com
Revision: 192
Author: hont...@flyordie.com
Date: Thu May 9 15:39:28 2013
Log: Mireka can act as a null client: all outgoing mails are sent via
another MTA
http://code.google.com/p/mireka/source/detail?r=192

Added:
/trunk/setup/doc/basic-configuration/null-client.html
/trunk/src/mireka/smtp/SendException.java
/trunk/src/mireka/smtp/client
/trunk/src/mireka/smtp/client/BackendServer.java
/trunk/src/mireka/smtp/client/ClientFactory.java
/trunk/src/mireka/smtp/client/MtaAddress.java
/trunk/src/mireka/smtp/client/SmtpClient.java
/trunk/src/mireka/transmission/immediate/DirectImmediateSender.java
/trunk/src/mireka/transmission/immediate/NullClientImmediateSender.java

/trunk/test/mireka/transmission/immediate/direct/DirectImmediateSenderTest.java
Deleted:
/trunk/src/mireka/filter/proxy/BackendServer.java
/trunk/src/mireka/namespace
/trunk/src/mireka/smtp/ClientFactory.java
/trunk/src/mireka/transmission/immediate/ImmediateSenderFactory.java
/trunk/src/mireka/transmission/immediate/RemoteMta.java
/trunk/src/mireka/transmission/immediate/SendException.java
/trunk/src/mireka/transmission/immediate/direct
/trunk/src/mireka/transmission/immediate/dns/AddressLookupFactory.java
/trunk/src/mireka/transmission/immediate/dns/MxLookupFactory.java

/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitterFactory.java
/trunk/test/mireka/transmission/immediate/direct/ImmediateSenderTest.java
Modified:
/trunk/lib/build/jmockit-coverage.jar
/trunk/lib/build/jmockit.jar
/trunk/lib/runtime/subethasmtp-UNVERSIONED.jar
/trunk/setup/conf/mireka.js
/trunk/setup/conf/submission/queues.js
/trunk/setup/doc/basic-configuration/proxy.html
/trunk/setup/doc/index.html
/trunk/setup/lib/configuration.js
/trunk/src/mireka/filter/proxy/BackendClient.java
/trunk/src/mireka/filter/proxy/ClientWithProxyErrorHandling.java
/trunk/src/mireka/filter/proxy/RelayDestination.java
/trunk/src/mireka/transmission/Mail.java
/trunk/src/mireka/transmission/TransmitterDestination.java
/trunk/src/mireka/transmission/dsn/RecipientProblemReport.java
/trunk/src/mireka/transmission/immediate/ImmediateSender.java
/trunk/src/mireka/transmission/immediate/PostponeException.java
/trunk/src/mireka/transmission/immediate/RecipientRejection.java

/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
/trunk/src/mireka/transmission/immediate/dns/AddressLookup.java
/trunk/src/mireka/transmission/immediate/dns/MxLookup.java
/trunk/src/mireka/transmission/immediate/dns/MxLookupException.java
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitter.java
/trunk/src/mireka/transmission/immediate/package-info.java
/trunk/src/mireka/transmission/queuing/OutboundMtaMailProcessor.java
/trunk/src/mireka/transmission/queuing/QueuingTransmitter.java
/trunk/src/mireka/transmission/queuing/RetryPolicy.java
/trunk/test/mireka/ExampleAddress.java
/trunk/test/mireka/filter/proxy/RelayFunctionalTest.java
/trunk/test/mireka/filter/proxy/RelayMailTransactionTest.java
/trunk/test/mireka/server/ClientServerRelayTest.java
/trunk/test/mireka/transmission/dsn/DsnMailCreatorTest.java
/trunk/test/mireka/transmission/immediate/MailToHostTransmitterTest.java
/trunk/test/mireka/transmission/immediate/dns/AddressLookupTest.java
/trunk/test/mireka/transmission/immediate/dns/DnsJavaLookupTest.java
/trunk/test/mireka/transmission/immediate/dns/MxLookupTest.java
/trunk/test/mireka/transmission/queuing/OutboundMtaMailProcessorTest.java
/trunk/test/mireka/transmission/queuing/QueuingTransmitterTest.java
/trunk/test/mireka/transmission/queuing/RetryPolicyTest.java

=======================================
--- /dev/null
+++ /trunk/setup/doc/basic-configuration/null-client.html Thu May 9
15:39:28 2013
@@ -0,0 +1,58 @@
+<html>
+ <head>
+ <title>Null client: sending all mails via another server</title>
+ <link rel="stylesheet" type="text/css" href="../doc.css" />
+ </head>
+
+ <body>
+ <h1>Null client: sending all mails via another server</h1>
+
+<p>Mireka can be configured to be a null client, which means that it sends
all
+outgoing mail through another server. The other server is the so called
+<i>smart host</i>. This is useful for example if the local network
connects to the
+internet through a dynamic IP address.
+<p>
+In contrast to proxy mode, Mireka, acting as a null client, waits until it
+completely receives the incoming mail, stores it, and forwards them later,
+usually within a few milliseconds. Mireka tries all configured smart
+hosts, and if none of them is accessible, than it tries them again later,
up to
+the configured duration.
+<p><i>NOTE:
+If it turns out that the mail cannot be forwarded for some reason, then
Mireka
+sends a Delivery Status Notification mail back to the original sender.
However,
+if the original sender is not a local user, this bounce mail must also go
through
+the smart host. If the smart host is inaccessible for an extended time,
Mireka
+will give up, and delete the DSN mail too. Depending on the importance of
the
+mails this can be a dangerous situation because the mail is accepted but
+neither the mail is neither delivered, nor the user is notified. To
prevent this,
+configure appropriate retry intervals and regularly monitor the mail error
logs.
+</i><p>
+
+<p>First edit <code>mireka.js</code> and configure the value of the
<code>backendServer</code>
+variable. For example if the smarthost is <code>myisp.example.com</code>,
+listens on port 587, and requires authentication, then your configuration
should
+look like this:
+<p class="code">...
+backendServer = setup(BackendServer, {
+ host: "<span class="relevant">myisp.example.com</span>",
+ <span class="relevant">port: 587,</span>
+ <span class="relevant">user: "mycompany",</span>
+ <span class="relevant">password: "CHANGEIT",</span>
+ clientFactory: clientFactory
+});
+...</p>
+
+<p>Note: you can define more than one smart hosts.
+
+<p>Next, edit <code>submission/queues.js</code> and uncomment the null
client
+sender definition.
+Your configuraton should look like this:
+<p class="code">...
+immediateSender = setup(<span
class="relevant">NullClientImmediateSender</span>, {
+ mailToHostTransmitter: mailToHostTransmitter,
+ <span class="relevant">smartHosts: [ backendServer ]</span>,
+});
+...</p>
+
+ </body>
+</html>
=======================================
--- /dev/null
+++ /trunk/src/mireka/smtp/SendException.java Thu May 9 15:39:28 2013
@@ -0,0 +1,59 @@
+package mireka.smtp;
+
+import java.util.Date;
+
+
+/**
+ * Signals an error occurred while attempting to transmit a mail to a
remote
+ * domain. Typically the remote system cannot be found in the DNS or it
rejects
+ * the mail.
+ */
+public class SendException extends Exception {
+ private static final long serialVersionUID = 379604390803596371L;
+
+ private final EnhancedStatus errorStatus;
+ public final Date failureDate = new Date();
+ /**
+ * It must be set by the function which logs this exception by calling
+ * {@link #initLogId}.
+ */
+ private String logId;
+
+ public SendException(String message, EnhancedStatus status) {
+ super(message);
+ this.errorStatus = status;
+ }
+
+ public SendException(Throwable e, EnhancedStatus status) {
+ super(e);
+ this.errorStatus = status;
+ }
+
+ public SendException(String message, Throwable e, EnhancedStatus
status) {
+ super(message, e);
+ this.errorStatus = status;
+ }
+
+ /**
+ * @see <a href="http://tools.ietf.org/html/rfc3464#section-2.3.4">RFC
3464
+ * An Extensible Message Format for Delivery Status Notifications
-
+ * Status field</a>
+ */
+ public EnhancedStatus errorStatus() {
+ return errorStatus;
+ }
+
+ /**
+ * It must be called if this exception gets logged. In theory Log-ID
should
+ * be set in the constructor if the cause was already logged. This
never
+ * happens, first of all because the logging framework doesn't know
such
+ * term as Log-ID.
+ */
+ public void initLogId(String logId) {
+ this.logId = logId;
+ }
+
+ public String getLogId() {
+ return logId;
+ }
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/smtp/client/BackendServer.java Thu May 9 15:39:28
2013
@@ -0,0 +1,199 @@
+package mireka.smtp.client;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Pattern;
+
+import mireka.smtp.EnhancedStatus;
+import mireka.smtp.SendException;
+
+import org.subethamail.smtp.client.PlainAuthenticator;
+
+public class BackendServer {
+ private static final String IPV6_PREFIX = "[IPv6:";
+ private static final Pattern dottedQuad = Pattern
+ .compile("\\d{1,3}(\\.\\d{1,3}){3}");
+
+ private ClientFactory clientFactory;
+
+ /**
+ * The host name in the format as it appears in the configuration. This
+ * format is theoretically ambiguous, so it is only appropriate for
+ * informational purposes.
+ */
+ private String host;
+ /**
+ * The host name formatted in the same way as the remote part of a
mailbox.
+ * For example:
+ * <ul>
+ * <li>mail.example.com
+ * <li>[192.0.2.0]
+ * <li>[IPv6:::1]
+ * </ul>
+ */
+ private String smtpFormattedHost;
+
+ /**
+ * The IP address which was specified in the host field. It the host
field
+ * contains a domain name, then this field is null.
+ */
+ private InetAddress fixedAddress;
+ private int port = 25;
+ private String user;
+ private String password;
+
+ /**
+ *
+ * @throws SendException
+ * if the IP address of the backend server could not be
+ * determined based on its domain name.
+ */
+ public SmtpClient createClient() throws SendException {
+ SmtpClient client = clientFactory.create();
+ if (user != null) {
+ PlainAuthenticator authenticator =
+ new PlainAuthenticator(client, user, password);
+ client.setAuthenticator(authenticator);
+ }
+ InetAddress address;
+ try {
+ address =
+ fixedAddress != null ? fixedAddress : InetAddress
+ .getByName(host);
+ } catch (UnknownHostException e) {
+ // without detailed information, assume it is a temporary
failure
+ throw new SendException("Resolving the backend " +
this.toString()
+ + " domain failed.", e, new
EnhancedStatus(450, "4.4.0",
+ "Domain name resolution failed"));
+ }
+ client.setMtaAddress(new MtaAddress(smtpFormattedHost, address,
port));
+ return client;
+ }
+
+ @Override
+ public String toString() {
+ return "BackendServer [" + host + ":" + port + "]";
+ }
+
+ /**
+ * @category GETSET
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Sets the domain name or IP address of the backend server. The name
may
+ * contain a domain name or IPv4 or IPv6 literals in various forms. It
+ * guesses the actual type of the name.
+ * <p>
+ * Examples for legal values:
+ * <ul>
+ * <li>mail.example.com
+ * <li>[192.0.2.0]
+ * <li>192.0.2.0
+ * <li>[IPv6:::1]
+ * <li>[::1]
+ * <li>::1
+ * </ul>
+ */
+ public void setHost(String host) {
+ this.host = host;
+ if (host == null)
+ throw new NullPointerException();
+ if (host.isEmpty())
+ throw new IllegalArgumentException();
+ if (host.charAt(0) == '[') {
+ // literal
+ if (host.charAt(host.length() - 1) != ']')
+ throw new IllegalArgumentException();
+ if (host.length() > IPV6_PREFIX.length()
+ && IPV6_PREFIX.equalsIgnoreCase(host.substring(0,
+ IPV6_PREFIX.length())))
+ setHostByAddress(host.substring(IPV6_PREFIX.length(),
+ host.length()));
+ setHostByAddress(host.substring(1, host.length()));
+ } else {
+ if (dottedQuad.matcher(host).matches())
+ setHostByAddress(host);
+ if (host.contains(":"))
+ setHostByAddress(host);
+ setHostByDomain(host);
+ }
+ }
+
+ private void setHostByDomain(String domain) {
+ smtpFormattedHost = domain;
+ fixedAddress = null;
+ }
+
+ private void setHostByAddress(String address) {
+ try {
+ InetAddress inetAddress = InetAddress.getByName(address);
+ if (inetAddress instanceof Inet4Address) {
+ smtpFormattedHost = "[" + address + "]";
+ } else if (inetAddress instanceof Inet6Address) {
+ smtpFormattedHost = "[IPv6:" + address + "]";
+ } else {
+ throw new RuntimeException();
+ }
+ fixedAddress = inetAddress;
+ } catch (UnknownHostException e) {
+ // impossible, the argument is an IP address, not a domain
name.
+ throw new RuntimeException("Assertion failed");
+ }
+ }
+
+ public ClientFactory getClientFactory() {
+ return clientFactory;
+ }
+
+ public void setClientFactory(ClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public String getUser() {
+ return user;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/smtp/client/ClientFactory.java Thu May 9 15:39:28
2013
@@ -0,0 +1,51 @@
+package mireka.smtp.client;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+/**
+ * ClientFactory creates {@link SmtpClient} instances based on the
configured
+ * parameters.
+ */
+public class ClientFactory {
+ private String helo;
+ private String bind;
+
+ public SmtpClient create() {
+ SmtpClient client = new SmtpClient();
+ client.setHeloHost(helo);
+ SocketAddress bindpoint =
+ bind == null ? null : new InetSocketAddress(bind, 0);
+ client.setBindpoint(bindpoint);
+ return client;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public String getHelo() {
+ return helo;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setHelo(String helo) {
+ this.helo = helo;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public String getBind() {
+ return bind;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setBind(String bind) {
+ this.bind = bind;
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/smtp/client/MtaAddress.java Thu May 9 15:39:28 2013
@@ -0,0 +1,143 @@
+package mireka.smtp.client;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.xbill.DNS.Name;
+
+/**
+ * MtaAddress contains all informations which are necessary to contact
that MTA
+ * and identify it in DSN reports, logs.
+ */
+@Immutable
+public class MtaAddress {
+ /**
+ * The host name as an RFC-5321 domain name or address-literal. It
comes
+ * from the content of a DNS MX record or an email address with a
literal
+ * address part, or a smart host or back-end server name. For example:
+ * <ul>
+ * <li>mail.example.com
+ * <li>[192.0.2.0]
+ * <li>[IPv6:::1]
+ * </ul>
+ */
+ public final String dnsName;
+
+ /**
+ * IP address of MTA. It is possible that a domain name in an MX
records
+ * have multiple A records, therefore a single domain name may refer to
+ * multiple MTA servers, and only this address differentiates between
them.
+ */
+ public final InetAddress address;
+
+ public final int port;
+
+ /**
+ * @param smtpFormattedHost
+ * The host name as an RFC-5321 domain name or
address-literal.
+ * It comes from the content of a DNS MX record or an email
+ * address with a literal address part, or a smart host or
+ * back-end server name. For example:
+ * <ul>
+ * <li>mail.example.com
+ * <li>[192.0.2.0]
+ * <li>[IPv6:::1]
+ * </ul>
+ * @param address
+ * IP address of the MTA
+ * @param port
+ * port of the remote MTA
+ */
+ public MtaAddress(String smtpFormattedHost, InetAddress address, int
port) {
+ this.dnsName = smtpFormattedHost;
+ this.address = address;
+ this.port = port;
+ }
+
+ /**
+ * Constructs a new MtaAddress with the default port, which is 25.
This is
+ * the equivalent of calling {@link #MtaAddress(String, InetAddress,
int)}
+ * with port 25.
+ */
+ public MtaAddress(String dnsName, InetAddress address) {
+ this(dnsName, address, 25);
+ }
+
+ /**
+ * Constructs a new MtaAddress with the default port, which is 25.
This is
+ * the equivalent of calling {@link #MtaAddress(String, InetAddress,
int)}
+ * with port 25, and with a dnsName converted from the supplied
DnsJava Name
+ * object by removing the last dot. The trailing dot marks an absolute
+ * domain name in DNS - but that syntax is invalid in SMTP.
+ */
+ public MtaAddress(Name dnsName, InetAddress address) {
+ this(name2string(dnsName), address);
+ }
+
+ private static String name2string(Name name) {
+ String s = name.toString();
+ if (s.charAt(s.length() - 1) == '.')
+ s = s.substring(0, s.length() - 1);
+ return s;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((address == null) ? 0 :
address.hashCode());
+ result = prime * result + ((dnsName == null) ? 0 :
dnsName.hashCode());
+ result = prime * result + port;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MtaAddress other = (MtaAddress) obj;
+ if (address == null) {
+ if (other.address != null)
+ return false;
+ } else if (!address.equals(other.address))
+ return false;
+ if (dnsName == null) {
+ if (other.dnsName != null)
+ return false;
+ } else if (!dnsName.equals(other.dnsName))
+ return false;
+ if (port != other.port)
+ return false;
+ return true;
+ }
+
+ /**
+ * Prints the address in a compact form for informational purposes.
+ */
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder(64);
+ buffer.append(dnsName);
+
+ if (address != null) {
+ buffer.append(" (");
+ if (address instanceof Inet4Address)
+ buffer.append(address.getHostAddress());
+ else if (address instanceof Inet6Address)
+
buffer.append('[').append(address.getHostAddress()).append(']');
+ else
+ throw new RuntimeException("Assertion failed");
+ buffer.append(':').append(port).append(')');
+ }
+
+ return buffer.toString();
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/smtp/client/SmtpClient.java Thu May 9 15:39:28 2013
@@ -0,0 +1,28 @@
+package mireka.smtp.client;
+
+import java.io.IOException;
+
+import org.subethamail.smtp.client.SMTPException;
+import org.subethamail.smtp.client.SmartClient;
+
+/**
+ * SmtpClient extends SmartClient so that it requires no additional
information
+ * to connect to a client.
+ */
+public class SmtpClient extends SmartClient {
+ private MtaAddress mtaAddress;
+
+ public void connect() throws SMTPException, IOException {
+ super.setHostPort(mtaAddress.toString());
+ super.connect(mtaAddress.address.getHostAddress(),
mtaAddress.port);
+ }
+
+ public MtaAddress getMtaAddress() {
+ return mtaAddress;
+ }
+
+ public void setMtaAddress(MtaAddress mtaAddress) {
+ this.mtaAddress = mtaAddress;
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/transmission/immediate/DirectImmediateSender.java Thu
May 9 15:39:28 2013
@@ -0,0 +1,245 @@
+package mireka.transmission.immediate;
+
+import java.net.InetAddress;
+
+import mireka.address.AddressLiteral;
+import mireka.address.Domain;
+import mireka.address.DomainPart;
+import mireka.address.Recipient;
+import mireka.address.RemotePart;
+import mireka.address.RemotePartContainingRecipient;
+import mireka.smtp.SendException;
+import mireka.smtp.client.ClientFactory;
+import mireka.smtp.client.MtaAddress;
+import mireka.smtp.client.SmtpClient;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.dns.AddressLookup;
+import mireka.transmission.immediate.dns.MxLookup;
+import mireka.transmission.immediate.host.MailToHostTransmitter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xbill.DNS.Name;
+
+/**
+ * DirectImmediateSender synchronously sends a mail directly to an SMTP
server
+ * of a single remote domain, which may include attempting delivery to
more than
+ * one MX hosts of the domain until a working one is found.
+ * <p>
+ * The remote domain is specified by the remote part of the recipient
addresses,
+ * which must be the same for all recipients in case of this
implementation.
+ * <p>
+ * The receiving SMTP servers are usually specified by the MX records of
the
+ * remote domain, except if the remote part is a literal address, or the
domain
+ * has an implicit MX record only.
+ * <p>
+ * If it cannot transmit the mail to any of the MX hosts of the domain,
then it
+ * throws an exception, it does not retry later.
+ * <p>
+ * TODO: if a recipient is rejected because of a transient failure, then it
+ * should be retried on another host.
+ */
+public class DirectImmediateSender implements ImmediateSender {
+ private final Logger logger = LoggerFactory
+ .getLogger(DirectImmediateSender.class);
+ private MxLookup mxLookup;
+ private AddressLookup addressLookup;
+ private ClientFactory clientFactory;
+ private MailToHostTransmitter mailToHostTransmitter;
+
+ public DirectImmediateSender() {
+ mxLookup = new MxLookup();
+ addressLookup = new AddressLookup();
+ }
+
+ @Override
+ public boolean singleDomainOnly() {
+ return true;
+ }
+
+ /**
+ * Transmits mail to a single domain.
+ *
+ * @throws IllegalArgumentException
+ * if the domains of the recipients are not the same, or
if the
+ * recipient is the special global postmaster address,
which has
+ * no absolute domain.
+ * @throws PostponeException
+ * if transmission to all of the hosts must be postponed,
+ * because all of them are assumed to be busy at this
moment.
+ */
+ @Override
+ public void send(Mail mail) throws SendException,
+ RecipientsWereRejectedException, IllegalArgumentException,
+ PostponeException {
+ RemotePart remotePart = commonRecipientRemotePart(mail);
+ if (remotePart instanceof AddressLiteral) {
+ AddressLiteral addressLiteral = (AddressLiteral) remotePart;
+ sendToAddressLiteral(mail, addressLiteral);
+ } else if (remotePart instanceof DomainPart) {
+ Domain domain = ((DomainPart) remotePart).domain;
+ sendToDomain(mail, domain);
+ } else {
+ throw new RuntimeException();
+ }
+ }
+
+ private RemotePart commonRecipientRemotePart(Mail mail)
+ throws IllegalArgumentException {
+ RemotePart result = null;
+ for (Recipient recipient : mail.recipients) {
+ if (!(recipient instanceof RemotePartContainingRecipient))
+ throw new IllegalArgumentException(
+ "Cannot send mail to non-remote address: " +
recipient);
+ RemotePart remotePart =
+ ((RemotePartContainingRecipient)
recipient).getMailbox()
+ .getRemotePart();
+ if (result == null) {
+ result = remotePart;
+ } else {
+ if (!result.equals(remotePart))
+ throw new IllegalArgumentException(
+ "Recipients are expected to belong to the same
domain. "
+ + " Recipient list contains both " +
result
+ + " and " + remotePart);
+ }
+ }
+ if (result == null)
+ throw new IllegalArgumentException("recipient list is empty");
+ return result;
+ }
+
+ private void sendToAddressLiteral(Mail mail, AddressLiteral target)
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ MtaAddress mtaAddress =
+ new MtaAddress(target.smtpText(), target.inetAddress());
+
+ SmtpClient client = clientFactory.create();
+ client.setMtaAddress(mtaAddress);
+
+ mailToHostTransmitter.transmit(mail, client);
+ }
+
+ /**
+ * Queries MX hosts of the domain and tries to transmit to the hosts
until
+ * it is successful or no more hosts remain.
+ *
+ * @throws PostponeException
+ * if transmission to all of the hosts must be postponed,
+ * because all of them are assumed to be busy at this
moment.
+ */
+ private void sendToDomain(Mail mail, Domain domain) throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ Name[] mxNames = mxLookup.queryMxTargets(domain);
+
+ // a PostponeException does not prevent successful delivery using
+ // another host, but it must be saved so if there are no more
hosts then
+ // this exception instance will be rethrown.
+ PostponeException lastPostponeException = null;
+ // if there is a host which failed, but which should be retried
later,
+ // then a following unrecoverable DNS exception on another MX host
may
+ // not prevent delivery, so this temporary exception will be
returned
+ SendException lastRetryableException = null;
+ // an unrecoverable DNS exception may not prevent delivery (to
another
+ // MX host of the domain), so the function will continue, but it
must be
+ // saved, because maybe there is no more host.
+ SendException lastUnrecoverableDnsException = null;
+ for (Name name : mxNames) {
+ InetAddress[] addresses;
+ try {
+ addresses = addressLookup.queryAddresses(name);
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry())
+ lastRetryableException = e;
+ else
+ lastUnrecoverableDnsException = e;
+ logger.debug("Looking up address of MX host " + name
+ + " failed, continuing with the next MX host "
+ + "if one is available: ", e.getMessage());
+ continue;
+ }
+
+ try {
+ for (InetAddress hostAddress : addresses) {
+ MtaAddress mtaAddress = new MtaAddress(name,
hostAddress);
+ SmtpClient client = clientFactory.create();
+ client.setMtaAddress(mtaAddress);
+ mailToHostTransmitter.transmit(mail, client);
+ return;
+ }
+ } catch (PostponeException e) {
+ lastPostponeException = e;
+ logger.debug("Sending to SMTP host " + name
+ + " must be postponed, continuing with the next "
+ + "MX host if one is available: " +
e.getMessage());
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry()) {
+ // lastSendException = e;
+ lastRetryableException = e;
+ logger.debug("Sending to SMTP host " + name
+ + " failed, continuing with the next "
+ + "MX host if one is available: ",
e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // at this point it is known that the transmission was not
successful
+
+ if (lastRetryableException != null)
+ throw lastRetryableException;
+ if (lastPostponeException != null) {
+ // there is at least one host successfully found in DNS but
have not
+ // tried
+ throw lastPostponeException;
+ }
+ if (lastUnrecoverableDnsException == null)
+ throw new RuntimeException(); // impossible, but prevents
warning
+ // an unrecoverable DNS exception
+ throw lastUnrecoverableDnsException;
+ }
+
+ /** @category GETSET **/
+ public MxLookup getMxLookup() {
+ return mxLookup;
+ }
+
+ /** @category GETSET **/
+ public void setMxLookup(MxLookup mxLookup) {
+ this.mxLookup = mxLookup;
+ }
+
+ /** @category GETSET **/
+ public AddressLookup getAddressLookup() {
+ return addressLookup;
+ }
+
+ /** @category GETSET **/
+ public void setAddressLookup(
+ AddressLookup addressLookup) {
+ this.addressLookup = addressLookup;
+ }
+
+ /** @category GETSET **/
+ public ClientFactory getClientFactory() {
+ return clientFactory;
+ }
+
+ /** @category GETSET **/
+ public void setClientFactory(ClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ /** @category GETSET **/
+ public MailToHostTransmitter getMailToHostTransmitter() {
+ return mailToHostTransmitter;
+ }
+
+ /** @category GETSET **/
+ public void setMailToHostTransmitter(
+ MailToHostTransmitter mailToHostTransmitter) {
+ this.mailToHostTransmitter = mailToHostTransmitter;
+ }
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/transmission/immediate/NullClientImmediateSender.java
Thu May 9 15:39:28 2013
@@ -0,0 +1,136 @@
+package mireka.transmission.immediate;
+
+import java.util.List;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import mireka.smtp.SendException;
+import mireka.smtp.client.BackendServer;
+import mireka.smtp.client.SmtpClient;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.host.MailToHostTransmitter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * NullClientImmediateSender synchronously sends a mail to a smart host,
trying
+ * all listed smart hosts until a working one is found. The smart host
will in
+ * turn transmit the mail to remote domains. This is useful for example if
a
+ * network is behind a dynamic IP address, considering that dynamic IP
addresses
+ * are frequently rejected by SMTP servers.
+ * <p>
+ * If a smart host name resolves to more than one IP addresses, than only
the
+ * first one is used.
+ */
+@NotThreadSafe
+public class NullClientImmediateSender implements ImmediateSender {
+ private final Logger logger = LoggerFactory
+ .getLogger(NullClientImmediateSender.class);
+ private MailToHostTransmitter mailToHostTransmitter;
+ private List<BackendServer> smartHosts;
+
+ @Override
+ public boolean singleDomainOnly() {
+ return false;
+ }
+
+ /**
+ * Transmits mail to a smart host.
+ *
+ * @throws PostponeException
+ * if transmission to all of the hosts must be postponed,
+ * because all of them are assumed to be busy at this
moment.
+ */
+ @Override
+ public void send(Mail mail) throws SendException,
+ RecipientsWereRejectedException, IllegalArgumentException,
+ PostponeException {
+
+ // a PostponeException does not prevent successful delivery using
+ // another host, but it must be saved so if there are no more
hosts then
+ // this exception instance will be rethrown.
+ PostponeException lastPostponeException = null;
+ // if there is a host which failed, but which should be retried
later,
+ // then a following unrecoverable DNS exception on another host may
+ // not prevent delivery, so this temporary exception will be
returned
+ SendException lastRetryableException = null;
+ // an unrecoverable DNS exception may not prevent delivery (to
another
+ // host), so the function will continue, but it must be
+ // saved, because maybe there is no more host.
+ SendException lastUnrecoverableDnsException = null;
+ for (BackendServer smartHost : smartHosts) {
+ SmtpClient client;
+ try {
+ client = smartHost.createClient();
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry())
+ lastRetryableException = e;
+ else
+ lastUnrecoverableDnsException = e;
+ logger.debug(
+ "Looking up address of MTA " + smartHost.toString()
+ + " failed, continuing with the next MTA "
+ + "if one is available: ", e.getMessage());
+ continue;
+ }
+
+ try {
+ mailToHostTransmitter.transmit(mail, client);
+ return;
+ } catch (PostponeException e) {
+ lastPostponeException = e;
+ logger.debug("Sending to SMTP host " +
client.getMtaAddress()
+ + " must be postponed, continuing with the next "
+ + "smart host if one is available: " +
e.getMessage());
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry()) {
+ // lastSendException = e;
+ lastRetryableException = e;
+ logger.debug(
+ "Sending to SMTP host " +
client.getMtaAddress()
+ + " failed, continuing with the next "
+ + "smart host if one is available: ",
+ e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // at this point it is known that the transmission was not
successful
+
+ if (lastRetryableException != null)
+ throw lastRetryableException;
+ if (lastPostponeException != null) {
+ // there is at least one host successfully found in DNS but
have not
+ // tried
+ throw lastPostponeException;
+ }
+ if (lastUnrecoverableDnsException == null)
+ throw new RuntimeException(); // impossible, but prevents
warning
+ // an unrecoverable DNS exception
+ throw lastUnrecoverableDnsException;
+ }
+
+ /** @category GETSET **/
+ public MailToHostTransmitter getMailToHostTransmitter() {
+ return mailToHostTransmitter;
+ }
+
+ /** @category GETSET **/
+ public void setMailToHostTransmitter(
+ MailToHostTransmitter mailToHostTransmitter) {
+ this.mailToHostTransmitter = mailToHostTransmitter;
+ }
+
+ /** @category GETSET **/
+ public List<BackendServer> getSmartHosts() {
+ return smartHosts;
+ }
+
+ /** @category GETSET **/
+ public void setSmartHosts(List<BackendServer> smartHosts) {
+ this.smartHosts = smartHosts;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/test/mireka/transmission/immediate/direct/DirectImmediateSenderTest.java
Thu May 9 15:39:28 2013
@@ -0,0 +1,372 @@
+package mireka.transmission.immediate.direct;
+
+import static mireka.ExampleAddress.*;
+import static org.junit.Assert.*;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+import mireka.ExampleMail;
+import mireka.address.Domain;
+import mireka.address.Recipient;
+import mireka.smtp.EnhancedStatus;
+import mireka.smtp.SendException;
+import mireka.smtp.client.ClientFactory;
+import mireka.smtp.client.MtaAddress;
+import mireka.smtp.client.SmtpClient;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.DirectImmediateSender;
+import mireka.transmission.immediate.PostponeException;
+import mireka.transmission.immediate.RecipientsWereRejectedException;
+import mireka.transmission.immediate.dns.AddressLookup;
+import mireka.transmission.immediate.dns.MxLookup;
+import mireka.transmission.immediate.host.MailToHostTransmitter;
+import mockit.Expectations;
+import mockit.Injectable;
+import mockit.Mocked;
+import mockit.NonStrictExpectations;
+import mockit.Tested;
+import mockit.Verifications;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xbill.DNS.Name;
+
+public class DirectImmediateSenderTest {
+
+ @Tested
+ private DirectImmediateSender sender;
+
+ /** Automatically created by constructor, cannot be @Injected **/
+ @Mocked
+ private MxLookup mxLookup;
+
+ /** Automatically created by constructor, cannot be @Injected **/
+ @Mocked
+ private AddressLookup addressLookup;
+
+ @Injectable
+ private ClientFactory clientFactory;
+
+ @Mocked
+ private SmtpClient client;
+
+ @Injectable
+ private MailToHostTransmitter mailToHostTransmitter;
+
+
+ private final Mail mail = ExampleMail.simple();
+ private Mail adaAddressLiteralMail;
+ private Mail janeJoeMail;
+
+ private final SendException permanentSendException = new SendException(
+ "Example permanent failure",
+ EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
+ private final SendException transientSendException = new SendException(
+ "Example transient failure",
+ EnhancedStatus.TRANSIENT_LOCAL_ERROR_IN_PROCESSING);
+ private static final PostponeException POSTPONE_EXCEPTION =
+ new PostponeException(
+ 10,
+
EnhancedStatus.TRANSIENT_SYSTEM_NOT_ACCEPTING_NETWORK_MESSAGES,
+ "Test exception");
+
+ @Before
+ public void initialize() {
+ adaAddressLiteralMail = ExampleMail.simple();
+ adaAddressLiteralMail.recipients =
+ Arrays.asList(ADA_ADDRESS_LITERAL_AS_RECIPIENT);
+ janeJoeMail = ExampleMail.simple();
+ janeJoeMail.recipients =
+ Arrays.asList(JANE_AS_RECIPIENT, JOHN_AS_RECIPIENT);
+
+ new NonStrictExpectations() {
+ {
+ clientFactory.create();
+ result = client;
+ }
+ };
+ }
+
+ @Test
+ public void testSendToAddressLiteralVerifyNoDns() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+
+ sender.send(adaAddressLiteralMail);
+
+ new Verifications() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ times = 0;
+
+ addressLookup.queryAddresses((Name)any);
+ times = 0;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+
+ client.setMtaAddress(new MtaAddress(ADDRESS_LITERAL, IP));
+ }
+ };
+ }
+
+ @Test
+ public void testSendToDomain() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = new InetAddress[] { IP_ADDRESS_ONLY };
+
+ client.setMtaAddress(new MtaAddress("host1.example.com",
IP));
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendToDifferentDomain() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ mail.recipients =
+ Arrays.asList(JANE_AS_RECIPIENT, NANCY_NET_AS_RECIPIENT);
+ sender.send(mail);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendToGlobalPostmaster() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ mail.recipients =
+ Arrays.asList((Recipient) GLOBAL_POSTMASTER_AS_RECIPIENT);
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstMxCannotBeResolved() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = permanentSendException;
+ result = new InetAddress[] { IP2 };
+
+ client.setMtaAddress(new MtaAddress("host2.example.com",
IP2));
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstHostHasTransientProblem() throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = transientSendException;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = null;
+ }
+ };
+
+ sender.send(mail);
+
+ }
+
+ private void twoMxDnsExpectation() throws SendException {
+ new NonStrictExpectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+ times = 1;
+
+ addressLookup.queryAddresses((Name)any);
+ result = new InetAddress[] { IP1 };
+ result = new InetAddress[] { IP2 };
+ times = 2;
+ }
+ };
+ }
+
+ @Test(expected = SendException.class)
+ public void testSendFirstHostHasPermanentProblem() throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = new InetAddress[] { IP1 };
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = permanentSendException;
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstHostHasTransientSecondHasPermanentProblem()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = transientSendException;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = permanentSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("Exception expected");
+ } catch (SendException e) {
+ assertFalse(e.errorStatus().shouldRetry());
+ }
+
+ }
+
+ @Test
+ public void testSendFirstHostPostponed() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = null;
+ }
+ };
+
+ sender.send(mail);
+
+ }
+
+ @Test
+ public void testSendFirstHostPostponedSecondHasTransientProblem()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = transientSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("Exception expected");
+ } catch (SendException e) {
+ assertTrue(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test(expected = PostponeException.class)
+ public void testSendBothPostponed() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = POSTPONE_EXCEPTION;
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendSingleHostPermanentlyCannotBeResolved()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = permanentSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("An exception must have been thrown");
+ } catch (SendException e) {
+ assertFalse(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test
+ public void testSendSingleHostTemporarilyCannotBeResolved()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = transientSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("An exception must have been thrown");
+ } catch (SendException e) {
+ assertTrue(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test(expected = PostponeException.class)
+ public void testSendSingleHostPostponeException() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets((Domain)any);
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses((Name)any);
+ result = new InetAddress[] { IP1 };
+
+ mailToHostTransmitter.transmit((Mail) any, null);
+ result = POSTPONE_EXCEPTION;
+ }
+ };
+
+ sender.send(mail);
+ }
+}
=======================================
--- /trunk/src/mireka/filter/proxy/BackendServer.java Mon May 28 11:27:34
2012
+++ /dev/null
@@ -1,76 +0,0 @@
-package mireka.filter.proxy;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import mireka.smtp.ClientFactory;
-
-import org.subethamail.smtp.client.SMTPException;
-import org.subethamail.smtp.client.SmartClient;
-
-public class BackendServer {
- private ClientFactory clientFactory;
- private String host;
- private int port = 25;
-
- /**
- *
- * @throws UnknownHostException
- * if the IP address of the backend server could not be
- * determined based on its domain name.
- */
- public SmartClient connect() throws UnknownHostException,
SMTPException,
- IOException {
- InetAddress inetAddress = InetAddress.getByName(host);
- return clientFactory.create(inetAddress, port);
- }
-
- @Override
- public String toString() {
- return "BackendServer [" + host + ":" + port + "]";
- }
-
- /**
- * @category GETSET
- */
- public ClientFactory getClientFactory() {
- return clientFactory;
- }
-
- /**
- * @category GETSET
- */
- public void setClientFactory(ClientFactory clientFactory) {
- this.clientFactory = clientFactory;
- }
-
- /**
- * @category GETSET
- */
- public String getHost() {
- return host;
- }
-
- /**
- * @category GETSET
- */
- public void setHost(String host) {
- this.host = host;
- }
-
- /**
- * @category GETSET
- */
- public int getPort() {
- return port;
- }
-
- /**
- * @category GETSET
- */
- public void setPort(int port) {
- this.port = port;
- }
-
-}
=======================================
--- /trunk/src/mireka/namespace Sat Mar 12 16:55:03 2011
+++ /dev/null
@@ -1,24 +0,0 @@
-mireka
-mireka.destination
-mireka.filter
-mireka.filter.misc
-mireka.filter.dnsbl
-mireka.filter.local
-mireka.filter.local.table
-mireka.filter.proxy
-mireka.filter.spf
-mireka.filterchain
-mireka.forward
-mireka.list
-mireka.login
-mireka.pop
-mireka.pop.store
-mireka.smtp
-mireka.smtp.server
-mireka.submission
-mireka.transmission
-mireka.transmission.dsn
-mireka.transmission.queue
-mireka.transmission.queuing
-mireka.transmission.immediate
-
=======================================
--- /trunk/src/mireka/smtp/ClientFactory.java Mon May 14 09:03:22 2012
+++ /dev/null
@@ -1,62 +0,0 @@
-package mireka.smtp;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.UnknownHostException;
-
-import org.subethamail.smtp.client.SMTPException;
-import org.subethamail.smtp.client.SmartClient;
-
-/**
- * ClientFactory creates {@link SmartClient} instances based on the
configured
- * parameters.
- */
-public class ClientFactory {
- private String helo;
- private String bind;
-
- public SmartClient create(InetAddress inetAddress)
- throws UnknownHostException, IOException, SMTPException {
- return create(inetAddress, 25);
- }
-
- public SmartClient create(InetAddress inetAddress, int port)
- throws UnknownHostException, SMTPException, IOException {
- SocketAddress bindpoint =
- bind == null ? null : new InetSocketAddress(bind, 0);
-
- return new SmartClient(inetAddress.getHostAddress(), port,
bindpoint,
- helo);
- }
-
- /**
- * @category GETSET
- */
- public String getHelo() {
- return helo;
- }
-
- /**
- * @category GETSET
- */
- public void setHelo(String helo) {
- this.helo = helo;
- }
-
- /**
- * @category GETSET
- */
- public String getBind() {
- return bind;
- }
-
- /**
- * @category GETSET
- */
- public void setBind(String bind) {
- this.bind = bind;
- }
-
-}
=======================================
--- /trunk/src/mireka/transmission/immediate/ImmediateSenderFactory.java
Sun May 5 07:37:27 2013
+++ /dev/null
@@ -1,17 +0,0 @@
-package mireka.transmission.immediate;
-
-/**
- * Factory for {@link ImmediateSender}.
- */
-public interface ImmediateSenderFactory {
- /**
- * Returns a new instance of {@link ImmediateSender}.
- */
- ImmediateSender create();
-
- /**
- * Returns true if the created {@link ImmediateSender} requires that
all
- * recipients of the mail to be sent have the same remote-part.
- */
- boolean singleDomainOnly();
-}
=======================================
--- /trunk/src/mireka/transmission/immediate/RemoteMta.java Sun May 5
07:37:27 2013
+++ /dev/null
@@ -1,37 +0,0 @@
-package mireka.transmission.immediate;
-
-import javax.annotation.Nullable;
-
-/**
- * Collects identification information about a remote MTA as they become
- * available, these informations are eventually used in delivery status
- * notifications
- */
-public class RemoteMta {
- /**
- * Either a name in a DNS MX record or a literal address in square
bracket.
- */
- public final String dnsName;
- /**
- * resolved address of {@link #dnsName} or null if {@link #dnsName} is
not
- * yet resolved.
- */
- @Nullable
- public final String address;
-
- public RemoteMta(String dnsName) {
- this.dnsName = dnsName;
- this.address = null;
- }
-
- public RemoteMta(String dnsName, String address) {
- this.dnsName = dnsName;
- this.address = address;
- }
-
- @Override
- public String toString() {
- return dnsName + (address == null ? "" : " [" + address + "]");
- }
-
-}
=======================================
--- /trunk/src/mireka/transmission/immediate/SendException.java Sun Mar 6
07:40:22 2011
+++ /dev/null
@@ -1,94 +0,0 @@
-package mireka.transmission.immediate;
-
-import java.util.Date;
-
-import javax.annotation.Nullable;
-
-import mireka.smtp.EnhancedStatus;
-
-/**
- * Signals an error occurred while attempting to transmit a mail to a
remote
- * domain. Typically the remote system cannot be found in the DNS or it
rejects
- * the mail.
- */
-public class SendException extends Exception {
- private static final long serialVersionUID = 379604390803596371L;
-
- private final EnhancedStatus errorStatus;
- @Nullable
- private final RemoteMta remoteMta;
- public final Date failureDate = new Date();
- /**
- * It must be set by the function which logs this exception by calling
- * {@link #initLogId}.
- */
- private String logId;
-
- public SendException(String message, EnhancedStatus status) {
- super(message);
- this.errorStatus = status;
- this.remoteMta = null;
- }
-
- /**
- * Constructs a new exception where the message is coming from the
- * {@link EnhancedStatus} but it is complemented with a comment
(following a
- * colon).
- */
- public SendException(String message, EnhancedStatus status,
- RemoteMta remoteMta) {
- super(message);
- this.errorStatus = status;
- this.remoteMta = remoteMta;
- }
-
- public SendException(Throwable e, EnhancedStatus status) {
- super(e);
- this.errorStatus = status;
- this.remoteMta = null;
- }
-
- public SendException(Throwable e, EnhancedStatus status, RemoteMta
remoteMta) {
- super(e);
- this.errorStatus = status;
- this.remoteMta = remoteMta;
- }
-
- public SendException(String message, Throwable e, EnhancedStatus
status,
- RemoteMta remoteMta) {
- super(message, e);
- this.errorStatus = status;
- this.remoteMta = remoteMta;
- }
-
- /**
- * @see <a href="http://tools.ietf.org/html/rfc3464#section-2.3.4">RFC
3464
- * An Extensible Message Format for Delivery Status Notifications
-
- * Status field</a>
- */
- public EnhancedStatus errorStatus() {
- return errorStatus;
- }
-
- /**
- * Returns null if the remote MTA is not yet determined. This is the
case
- * when the exception occurs before a successful DNS MX lookup.
- */
- public RemoteMta remoteMta() {
- return remoteMta;
- }
-
- /**
- * It must be called if this exception gets logged. In theory Log-ID
should
- * be set in the constructor if the cause was already logged. This
never
- * happens, first of all because the logging framework doesn't know
such
- * term as Log-ID.
- */
- public void initLogId(String logId) {
- this.logId = logId;
- }
-
- public String getLogId() {
- return logId;
- }
-}
=======================================
--- /trunk/src/mireka/transmission/immediate/dns/AddressLookupFactory.java
Thu Apr 29 05:49:30 2010
+++ /dev/null
@@ -1,9 +0,0 @@
-package mireka.transmission.immediate.dns;
-
-import org.xbill.DNS.Name;
-
-public class AddressLookupFactory {
- public AddressLookup create(Name name) {
- return new AddressLookup(name);
- }
-}
=======================================
--- /trunk/src/mireka/transmission/immediate/dns/MxLookupFactory.java Thu
Apr 29 05:49:30 2010
+++ /dev/null
@@ -1,9 +0,0 @@
-package mireka.transmission.immediate.dns;
-
-import mireka.address.Domain;
-
-public class MxLookupFactory {
- public MxLookup create(Domain domain) {
- return new MxLookup(domain);
- }
-}
=======================================
---
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitterFactory.java
Sun May 5 07:37:27 2013
+++ /dev/null
@@ -1,49 +0,0 @@
-package mireka.transmission.immediate.host;
-
-import mireka.smtp.ClientFactory;
-import mireka.transmission.immediate.RemoteMta;
-import mireka.transmission.queuing.LogIdFactory;
-
-/**
- * Factory for creating {@link MailToHostTransmitter} instances.
- */
-public class MailToHostTransmitterFactory {
- private ClientFactory clientFactory;
- private OutgoingConnectionsRegistry outgoingConnectionRegistry;
- private LogIdFactory logIdFactory;
-
- public MailToHostTransmitter create(RemoteMta remoteMta) {
- return new MailToHostTransmitter(clientFactory,
- outgoingConnectionRegistry, logIdFactory, remoteMta);
- }
-
- /**
- * @category GETSET
- */
- public void setClientFactory(ClientFactory clientFactory) {
- this.clientFactory = clientFactory;
- }
-
- /**
- * @category GETSET
- */
- public void setOutgoingConnectionRegistry(
- OutgoingConnectionsRegistry outgoingConnectionRegistry) {
- this.outgoingConnectionRegistry = outgoingConnectionRegistry;
- }
-
- /**
- * @category GETSET
- */
- public OutgoingConnectionsRegistry getOutgoingConnectionRegistry() {
- return outgoingConnectionRegistry;
- }
-
- /**
- * @category GETSET
- */
- public void setLogIdFactory(LogIdFactory logIdFactory) {
- this.logIdFactory = logIdFactory;
- }
-
-}
=======================================
---
/trunk/test/mireka/transmission/immediate/direct/ImmediateSenderTest.java
Sun May 5 07:37:27 2013
+++ /dev/null
@@ -1,383 +0,0 @@
-package mireka.transmission.immediate.direct;
-
-import static mireka.ExampleAddress.ADA_ADDRESS_LITERAL_AS_RECIPIENT;
-import static mireka.ExampleAddress.GLOBAL_POSTMASTER_AS_RECIPIENT;
-import static mireka.ExampleAddress.HOST1_EXAMPLE_COM_NAME;
-import static mireka.ExampleAddress.HOST2_EXAMPLE_COM_NAME;
-import static mireka.ExampleAddress.IP;
-import static mireka.ExampleAddress.IP1;
-import static mireka.ExampleAddress.IP2;
-import static mireka.ExampleAddress.IP_ADDRESS_ONLY;
-import static mireka.ExampleAddress.JANE_AS_RECIPIENT;
-import static mireka.ExampleAddress.JOHN_AS_RECIPIENT;
-import static mireka.ExampleAddress.NANCY_NET_AS_RECIPIENT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.net.InetAddress;
-import java.util.Arrays;
-
-import mireka.ExampleMail;
-import mireka.address.Domain;
-import mireka.address.Recipient;
-import mireka.smtp.EnhancedStatus;
-import mireka.transmission.Mail;
-import mireka.transmission.immediate.PostponeException;
-import mireka.transmission.immediate.RecipientsWereRejectedException;
-import mireka.transmission.immediate.RemoteMta;
-import mireka.transmission.immediate.SendException;
-import mireka.transmission.immediate.dns.AddressLookup;
-import mireka.transmission.immediate.dns.AddressLookupFactory;
-import mireka.transmission.immediate.dns.MxLookup;
-import mireka.transmission.immediate.dns.MxLookupFactory;
-import mireka.transmission.immediate.host.MailToHostTransmitter;
-import mireka.transmission.immediate.host.MailToHostTransmitterFactory;
-import mockit.Expectations;
-import mockit.Mocked;
-import mockit.NonStrictExpectations;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.xbill.DNS.Name;
-
-public class ImmediateSenderTest {
-
- @Mocked
- private MxLookupFactory mxLookupFactory;
-
- @Mocked
- private MxLookup mxLookup;
-
- @Mocked
- private AddressLookupFactory addressLookupFactory;
-
- @Mocked
- private AddressLookup addressLookup;
-
- @Mocked
- private MailToHostTransmitterFactory mailToHostTransmitterFactory;
-
- @Mocked
- private MailToHostTransmitter mailToHostTransmitter;
-
- private Mail mail = ExampleMail.simple();
- private Mail adaAddressLiteralMail;
- private Mail janeJoeMail;
-
- private DirectImmediateSender sender;
-
- private SendException permanentSendException = new SendException(
- "Example permanent failure",
- EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
- private SendException transientSendException = new SendException(
- "Example transient failure",
- EnhancedStatus.TRANSIENT_LOCAL_ERROR_IN_PROCESSING);
- private static final PostponeException POSTPONE_EXCEPTION =
- new PostponeException(
- 10,
-
EnhancedStatus.TRANSIENT_SYSTEM_NOT_ACCEPTING_NETWORK_MESSAGES,
- "Test exception");
-
- @Before
- public void initialize() {
- adaAddressLiteralMail = ExampleMail.simple();
- adaAddressLiteralMail.recipients =
- Arrays.asList(ADA_ADDRESS_LITERAL_AS_RECIPIENT);
- janeJoeMail = ExampleMail.simple();
- janeJoeMail.recipients =
- Arrays.asList(JANE_AS_RECIPIENT, JOHN_AS_RECIPIENT);
- sender =
- new DirectImmediateSender(mxLookupFactory,
addressLookupFactory,
- mailToHostTransmitterFactory);
-
- new NonStrictExpectations() {
- {
- mailToHostTransmitterFactory.create((RemoteMta) any);
- result = mailToHostTransmitter;
-
- mxLookupFactory.create((Domain) any);
- result = mxLookup;
-
- addressLookupFactory.create((Name) any);
- result = addressLookup;
- }
- };
- }
-
- @Test
- public void testSendToAddressLiteralVerifyNoDns() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- times = 0;
-
- addressLookup.queryAddresses();
- times = 0;
-
- mailToHostTransmitter.transmit((Mail) any, IP);
- }
- };
-
- sender.send(adaAddressLiteralMail);
- }
-
- @Test
- public void testSendToDomain() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP_ADDRESS_ONLY };
-
- mailToHostTransmitter.transmit((Mail) any,
IP_ADDRESS_ONLY);
- }
- };
-
- sender.send(mail);
-
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testSendToDifferentDomain() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- mail.recipients =
- Arrays.asList(JANE_AS_RECIPIENT, NANCY_NET_AS_RECIPIENT);
- sender.send(mail);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testSendToGlobalPostmaster() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- mail.recipients =
- Arrays.asList((Recipient) GLOBAL_POSTMASTER_AS_RECIPIENT);
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstMxCannotBeResolved() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = permanentSendException;
- result = new InetAddress[] { IP2 };
-
- mailToHostTransmitter.transmit((Mail) any, IP2);
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstHostHasTransientProblem() throws
SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
-
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = null;
- }
- };
-
- sender.send(mail);
-
- }
-
- private void twoMxDnsExpectation() throws SendException {
- new NonStrictExpectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
- times = 1;
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
- result = new InetAddress[] { IP2 };
- times = 2;
- }
- };
- }
-
- @Test(expected = SendException.class)
- public void testSendFirstHostHasPermanentProblem() throws
SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = permanentSendException;
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstHostHasTransientSecondHasPermanentProblem()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = permanentSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("Exception expected");
- } catch (SendException e) {
- assertFalse(e.errorStatus().shouldRetry());
- }
-
- }
-
- @Test
- public void testSendFirstHostPostponed() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = null;
- }
- };
-
- sender.send(mail);
-
- }
-
- @Test
- public void testSendFirstHostPostponedSecondHasTransientProblem()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("Exception expected");
- } catch (SendException e) {
- assertTrue(e.errorStatus().shouldRetry());
- }
- }
-
- @Test(expected = PostponeException.class)
- public void testSendBothPostponed() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendSingleHostPermanentlyCannotBeResolved()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = permanentSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("An exception must have been thrown");
- } catch (SendException e) {
- assertFalse(e.errorStatus().shouldRetry());
- }
- }
-
- @Test
- public void testSendSingleHostTemporarilyCannotBeResolved()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = transientSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("An exception must have been thrown");
- } catch (SendException e) {
- assertTrue(e.errorStatus().shouldRetry());
- }
- }
-
- @Test(expected = PostponeException.class)
- public void testSendSingleHostPostponeException() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
-
- mailToHostTransmitter.transmit((Mail) any, IP1);
- result = POSTPONE_EXCEPTION;
- }
- };
-
- sender.send(mail);
- }
-}
=======================================
--- /trunk/lib/build/jmockit-coverage.jar Sun May 20 09:17:33 2012
+++ /trunk/lib/build/jmockit-coverage.jar Thu May 9 15:39:28 2013
Binary file, no diff available.
=======================================
--- /trunk/lib/build/jmockit.jar Sun May 20 09:17:33 2012
+++ /trunk/lib/build/jmockit.jar Thu May 9 15:39:28 2013
Binary file, no diff available.
=======================================
--- /trunk/lib/runtime/subethasmtp-UNVERSIONED.jar Sat Mar 16 05:13:47 2013
+++ /trunk/lib/runtime/subethasmtp-UNVERSIONED.jar Thu May 9 15:39:28 2013
Binary file, no diff available.
=======================================
--- /trunk/setup/conf/mireka.js Sun Mar 24 12:07:38 2013
+++ /trunk/setup/conf/mireka.js Thu May 9 15:39:28 2013
@@ -35,12 +35,15 @@

/*
Default backend server. It specifies an SMTP server, to where
- mails are relayed for delivery or submission. It can be
- referred where SMTP proxy functionality is configured.
+ mails are relayed for delivery or submission. It is used in
+ both proxy and null client mode.
*/
backendServer = setup(BackendServer, {
host: "backend.example.com",
- clientFactory: clientFactory
+ //port: 587,
+ //user: "office-server",
+ //password: "CHANGEIT",
+ clientFactory: clientFactory,
});

/*
=======================================
--- /trunk/setup/conf/submission/queues.js Sun May 5 07:37:27 2013
+++ /trunk/setup/conf/submission/queues.js Thu May 9 15:39:28 2013
@@ -49,16 +49,24 @@
*/
});

-mailToHostTransmitterFactory = setup(MailToHostTransmitterFactory, {
- clientFactory: clientFactory,
+mailToHostTransmitter = setup(MailToHostTransmitter, {
outgoingConnectionRegistry: outgoingConnectionRegisty,
logIdFactory: logIdFactory,
});

-immediateSenderFactory = setup(DirectImmediateSenderFactory, {
- mailToHostTransmitterFactory: mailToHostTransmitterFactory
+immediateSender = setup(DirectImmediateSender, {
+ clientFactory: clientFactory,
+ mailToHostTransmitter: mailToHostTransmitter
});

+/* uncomment to send all outgoing mails through a smart host */
+/*
+immediateSender = setup(NullClientImmediateSender, {
+ mailToHostTransmitter: mailToHostTransmitter,
+ smartHosts: [ backendServer ],
+});
+*/
+
dsnMailCreator = setup(DsnMailCreator, {
reportingMtaName: helo,
fromAddress: mailerdaemon
@@ -76,7 +84,7 @@

setup(primaryTransmitter, {
queue: submittedMailQueue,
- immediateSenderFactory: immediateSenderFactory,
+ immediateSender: immediateSender,
retryPolicy: retryPolicy,
logIdFactory: logIdFactory,
summary: setup(TransmitterSummary, {
@@ -86,7 +94,7 @@

setup(dsnTransmitter, {
queue: dsnMailQueue,
- immediateSenderFactory: immediateSenderFactory,
+ immediateSender: immediateSender,
retryPolicy: retryPolicy,
logIdFactory: logIdFactory,
summary: setup(TransmitterSummary, {
@@ -96,7 +104,7 @@

setup(retryTransmitter, {
queue: retryMailQueue,
- immediateSenderFactory: immediateSenderFactory,
+ immediateSender: immediateSender,
retryPolicy: retryPolicy,
logIdFactory: logIdFactory,
summary: setup(TransmitterSummary, {
=======================================
--- /trunk/setup/doc/basic-configuration/proxy.html Sun Mar 24 12:47:19 2013
+++ /trunk/setup/doc/basic-configuration/proxy.html Thu May 9 15:39:28 2013
@@ -8,7 +8,7 @@
<h1>Using as proxy</h1>

<p>Mireka can relay mails sent to one or more or even any addresses to
another SMTP server.
-<p>Mireka relays the SMTP statements received in a mail
+<p>When acting as a proxy, Mireka relays the SMTP statements received in a
mail
session to a backend server step-by-step in real-time. In this way both
this server and the backend server is able to reject a mail within the
SMTP session, before accepting the responsibility of delivering the
=======================================
--- /trunk/setup/doc/index.html Sun Mar 24 12:47:19 2013
+++ /trunk/setup/doc/index.html Thu May 9 15:39:28 2013
@@ -34,6 +34,7 @@
<li><a href="basic-configuration/forward.html">Specifying forwarding</a>
<li><a href="basic-configuration/mailing-list.html">Defining a simple
mailing list</a>
<li><a href="basic-configuration/proxy.html">Using as proxy</a>
+ <li><a href="basic-configuration/null-client.html">Null client: sending
all mails via another server</a>
<li><a href="basic-configuration/authenticate-by-ip.html">Authenticate by
IP address</a>
<li><a href="basic-configuration/sendmail.html">Sendmail command on
Linux</a>
</ul>
=======================================
--- /trunk/setup/lib/configuration.js Sun May 5 07:37:27 2013
+++ /trunk/setup/lib/configuration.js Thu May 9 15:39:28 2013
@@ -28,15 +28,16 @@
importPackage(Packages.mireka.pop);
importPackage(Packages.mireka.pop.store);
importPackage(Packages.mireka.smtp);
+importPackage(Packages.mireka.smtp.client);
importPackage(Packages.mireka.smtp.server);
importPackage(Packages.mireka.startup);
importPackage(Packages.mireka.submission);
importPackage(Packages.mireka.transmission);
importPackage(Packages.mireka.transmission.dsn);
+importPackage(Packages.mireka.transmission.immediate);
+importPackage(Packages.mireka.transmission.immediate.host);
importPackage(Packages.mireka.transmission.queue);
importPackage(Packages.mireka.transmission.queuing);
-importPackage(Packages.mireka.transmission.immediate.direct);
-importPackage(Packages.mireka.transmission.immediate.host);

/*
* Make the include function global. The include function reads and
executes
=======================================
--- /trunk/src/mireka/filter/proxy/BackendClient.java Mon Nov 1 19:58:52
2010
+++ /trunk/src/mireka/filter/proxy/BackendClient.java Thu May 9 15:39:28
2013
@@ -4,6 +4,7 @@
import java.io.InputStream;

import mireka.address.Recipient;
+import mireka.smtp.client.BackendServer;

import org.subethamail.smtp.RejectException;
import org.subethamail.smtp.TooMuchDataException;
=======================================
--- /trunk/src/mireka/filter/proxy/ClientWithProxyErrorHandling.java Mon
May 28 11:27:34 2012
+++ /trunk/src/mireka/filter/proxy/ClientWithProxyErrorHandling.java Thu
May 9 15:39:28 2013
@@ -4,6 +4,9 @@
import java.io.InputStream;

import mireka.address.Recipient;
+import mireka.smtp.SendException;
+import mireka.smtp.client.BackendServer;
+import mireka.smtp.client.SmtpClient;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,8 +35,14 @@

private SmartClient connect() throws BackendRejectException,
RejectException {
+ SmtpClient client;
try {
- return backend.connect();
+ client = backend.createClient();
+ } catch (SendException e) {
+ throw new RejectException(e.errorStatus().getSmtpReplyCode(),
e.errorStatus().getMessage());
+ }
+ try {
+ client.connect();
} catch (SMTPException e) {
throw new BackendRejectException(e,
" - Backend rejected connection");
@@ -42,6 +51,7 @@
"Error while communicating with " +
backend.toString(), e);
throw new RejectException(451, "Local error in processing.");
}
+ return client;
}

public void from(String from) throws BackendRejectException,
=======================================
--- /trunk/src/mireka/filter/proxy/RelayDestination.java Wed Jul 20
09:22:26 2011
+++ /trunk/src/mireka/filter/proxy/RelayDestination.java Thu May 9
15:39:28 2013
@@ -8,6 +8,7 @@
import mireka.destination.SessionDestination;
import mireka.filter.RecipientContext;
import mireka.smtp.RejectExceptionExt;
+import mireka.smtp.client.BackendServer;
import mireka.transmission.Mail;

import org.slf4j.Logger;
=======================================
--- /trunk/src/mireka/transmission/Mail.java Wed Jul 20 09:22:26 2011
+++ /trunk/src/mireka/transmission/Mail.java Thu May 9 15:39:28 2013
@@ -82,6 +82,10 @@
return result;
}

+ /**
+ * Returns a short descriptive information about the mail, useful for
+ * logging.
+ */
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
=======================================
--- /trunk/src/mireka/transmission/TransmitterDestination.java Mon Jun 4
15:43:21 2012
+++ /trunk/src/mireka/transmission/TransmitterDestination.java Thu May 9
15:39:28 2013
@@ -13,7 +13,9 @@
* The TransmitterDestination marks a recipient for which the mail must be
* transmitted asynchronously to a remote MTA as specified by the remote
part of
* the address. Usually the remote part is a domain name, and the MTA must
be
- * find by looking up the DNS MX record of that domain.
+ * find by looking up the DNS MX record of that domain. The transmitter
can also
+ * be configured to be a null client, which relays all mail through
another MTA,
+ * the so called smart client.
*/
public class TransmitterDestination implements MailDestination {
private final Logger logger = LoggerFactory
=======================================
--- /trunk/src/mireka/transmission/dsn/RecipientProblemReport.java Mon May
23 04:02:35 2011
+++ /trunk/src/mireka/transmission/dsn/RecipientProblemReport.java Thu May
9 15:39:28 2013
@@ -7,7 +7,7 @@
import mireka.address.Recipient;
import mireka.smtp.EnhancedStatus;
import mireka.smtp.MailSystemStatus;
-import mireka.transmission.immediate.RemoteMta;
+import mireka.smtp.client.MtaAddress;

/**
* RecipientProblemReport contains information necessary to produce the
@@ -30,7 +30,7 @@
@Nullable
public MailSystemStatus remoteMtaDiagnosticStatus;
@Nullable
- public RemoteMta remoteMta;
+ public MtaAddress remoteMta;
public Date failureDate;
public String logId;

=======================================
--- /trunk/src/mireka/transmission/immediate/ImmediateSender.java Sun May
5 07:37:27 2013
+++ /trunk/src/mireka/transmission/immediate/ImmediateSender.java Thu May
9 15:39:28 2013
@@ -1,13 +1,23 @@
package mireka.transmission.immediate;

+import mireka.smtp.SendException;
import mireka.transmission.Mail;

/**
* An ImmediateSender makes a single, synchronous attempt to deliver mail
to
* a remote system.
+ *
+ * If it cannot transmit the mail to any of the MX hosts of the domain,
then it
+ * throws an exception, it does not retry later.
*/
public interface ImmediateSender {
/**
+ * Returns true if the created {@link ImmediateSender} requires that
all
+ * recipients of the mail to be sent have the same remote-part.
+ */
+ boolean singleDomainOnly();
+
+ /**
* Synchronously transmits mail to a single domain.
*
* @throws IllegalArgumentException
=======================================
--- /trunk/src/mireka/transmission/immediate/PostponeException.java Thu Mar
17 05:06:50 2011
+++ /trunk/src/mireka/transmission/immediate/PostponeException.java Thu
May 9 15:39:28 2013
@@ -1,6 +1,7 @@
package mireka.transmission.immediate;

import mireka.smtp.EnhancedStatus;
+import mireka.smtp.client.MtaAddress;

/**
* Indicates that the specific host, must not be connected at this time,
because
@@ -13,7 +14,7 @@
private static final long serialVersionUID = 9171565056859085240L;
private final int recommendedDelay;
private final EnhancedStatus enhancedStatus;
- private RemoteMta remoteMta;
+ private MtaAddress remoteMta;

/**
* Construct a new exception without a remote MTA, which must be set
later.
@@ -44,15 +45,14 @@
/**
* Sets the remote MTA which will be returned by {@link
#getRemoteMta()}
*/
- public void setRemoteMta(RemoteMta remoteMta) {
+ public void setRemoteMta(MtaAddress remoteMta) {
this.remoteMta = remoteMta;
}

/**
- * Returns the remote MTA for which the connection must be postponed,
or one
- * of the MTA hosts if there are more.
+ * Returns the remote MTA for which the connection must be postponed.
*/
- public RemoteMta getRemoteMta() {
+ public MtaAddress getRemoteMta() {
return remoteMta;
}

=======================================
--- /trunk/src/mireka/transmission/immediate/RecipientRejection.java Sun
May 5 07:37:27 2013
+++ /trunk/src/mireka/transmission/immediate/RecipientRejection.java Thu
May 9 15:39:28 2013
@@ -1,6 +1,7 @@
package mireka.transmission.immediate;

import mireka.address.Recipient;
+import mireka.smtp.SendException;

/**
* RecipientRejection contains the rejected recipient and the rejection
=======================================
---
/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
Sun May 5 07:37:27 2013
+++
/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
Thu May 9 15:39:28 2013
@@ -2,19 +2,24 @@

import mireka.smtp.EnhancedStatus;
import mireka.smtp.MailSystemStatus;
+import mireka.smtp.SendException;
+import mireka.smtp.client.MtaAddress;

import org.subethamail.smtp.client.SMTPException;

/**
- * Thrown to indicate that the remote MTA returned an error message.
+ * Thrown to indicate that the remote MTA returned an error message.
*/
public class RemoteMtaErrorResponseException extends SendException {
private static final long serialVersionUID = -2886452940130526142L;
+
+ private final MtaAddress remoteMta;
+
private final MailSystemStatus remoteMtaStatus;

- public RemoteMtaErrorResponseException(SMTPException e, RemoteMta
remoteMta) {
- super(e,
enhancedStatusFromRemoteResponse(smtpStatusFromResponse(e)),
- remoteMta);
+ public RemoteMtaErrorResponseException(SMTPException e, MtaAddress
remoteMta) {
+ super(e,
enhancedStatusFromRemoteResponse(smtpStatusFromResponse(e)));
+ this.remoteMta = remoteMta;
this.remoteMtaStatus = smtpStatusFromResponse(e);
}

@@ -33,6 +38,10 @@
throw new RuntimeException("Unexpected: " +
smtpStatus.getClass());
}
}
+
+ public MtaAddress remoteMta() {
+ return remoteMta;
+ }

/**
* SMTP response sent by the remote MTA
=======================================
--- /trunk/src/mireka/transmission/immediate/dns/AddressLookup.java Sun
Mar 6 07:40:22 2011
+++ /trunk/src/mireka/transmission/immediate/dns/AddressLookup.java Thu
May 9 15:39:28 2013
@@ -2,9 +2,10 @@

import java.net.InetAddress;

+import javax.annotation.concurrent.ThreadSafe;
+
import mireka.smtp.EnhancedStatus;
-import mireka.transmission.immediate.RemoteMta;
-import mireka.transmission.immediate.SendException;
+import mireka.smtp.SendException;

import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
@@ -12,22 +13,24 @@
import org.xbill.DNS.Name;
import org.xbill.DNS.Record;

+/**
+ * The AddressLookup class queries the IP address of an MTA or domain by
+ * querying the A and AAAA records assigned to the domain name of the MTA.
+ * <p>
+ * This implementation uses Dnsjava, therefore it can provide much more
+ * precise error messages than the InetAddress. It also respects DNS TTL
+ * values.
+ */
+@ThreadSafe
public class AddressLookup {
- private final Name name;
- private final RemoteMta remoteMta;

- public AddressLookup(Name name) {
- this.name = name;
- this.remoteMta = new RemoteMta(name.toString());
- }
-
- public InetAddress[] queryAddresses() throws SendException {
- Record[] records = queryAddressRecords();
+ public InetAddress[] queryAddresses(Name name) throws SendException {
+ Record[] records = queryAddressRecords(name);
InetAddress[] addresses =
convertAddressRecordsToAddresses(records);
return addresses;
}

- private Record[] queryAddressRecords() throws SendException {
+ private Record[] queryAddressRecords(Name name) throws SendException {
Lookup lookup = new Lookup(name);
Record[] records = lookup.run();
switch (lookup.getResult()) {
@@ -36,28 +39,27 @@
case Lookup.TYPE_NOT_FOUND:
throw new SendException("Host " + name + " has no address
record ("
+ lookup.getErrorString() + ")",
- EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE, remoteMta);
+ EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
case Lookup.HOST_NOT_FOUND:
throw new SendException("Host " + name + " is not found ("
+ lookup.getErrorString() + ")",
- EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE, remoteMta);
+ EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
case Lookup.TRY_AGAIN:
throw new SendException(
"DNS network failure while looking up address of " +
name
+ ": " + lookup.getErrorString(),
- EnhancedStatus.TRANSIENT_DIRECTORY_SERVER_FAILURE,
- remoteMta);
+ EnhancedStatus.TRANSIENT_DIRECTORY_SERVER_FAILURE);
case Lookup.UNRECOVERABLE:
throw new SendException(
"Unrecoverable DNS error while looking up address of "
+ name + ": " + lookup.getErrorString(),
- EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE, remoteMta);
+ EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
default:
throw new SendException(
"Unknown DNS status while looking up address of " +
name
+ ": " + lookup.getResult() + ". "
+ lookup.getErrorString(),
- EnhancedStatus.PERMANENT_INTERNAL_ERROR, remoteMta);
+ EnhancedStatus.PERMANENT_INTERNAL_ERROR);
}
}

=======================================
--- /trunk/src/mireka/transmission/immediate/dns/MxLookup.java Mon May 14
09:03:22 2012
+++ /trunk/src/mireka/transmission/immediate/dns/MxLookup.java Thu May 9
15:39:28 2013
@@ -5,11 +5,11 @@
import java.util.Collections;

import javax.annotation.concurrent.Immutable;
-import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;

import mireka.address.Domain;
import mireka.smtp.EnhancedStatus;
-import mireka.transmission.immediate.SendException;
+import mireka.smtp.SendException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,19 +19,14 @@
import org.xbill.DNS.Record;

/**
- * This class wraps DNS query functionality in order to make unit testing
- * easier.
+ * The MxLookup class determines the MTA servers responsible from a domain
by
+ * querying the MX records from the DNS system.
*/
-@NotThreadSafe
+@ThreadSafe
public class MxLookup {
private static MXRecordPriorityComparator mxRecordPriorityComparator =
new MXRecordPriorityComparator();
private final Logger logger = LoggerFactory.getLogger(MxLookup.class);
- private final Domain domain;
-
- public MxLookup(Domain domain) {
- this.domain = domain;
- }

/**
* Returns an ordered host name list based on the MX records of the
domain.
@@ -42,12 +37,12 @@
* @see <a href="http://tools.ietf.org/html/rfc5321#section-5.1">RFC
5321
* Simple Mail Transfer Protocol - Locating the Target Host</a>
*/
- public Name[] queryMxTargets() throws MxLookupException {
- MXRecord[] records = queryMxRecords();
+ public Name[] queryMxTargets(Domain domain) throws MxLookupException {
+ MXRecord[] records = queryMxRecords(domain);
if (records.length == 0) {
logger.debug("Domain {} has no MX records, using an implicit "
+ "MX record targetting the host", domain);
- return implicitMxTargetForDomain();
+ return implicitMxTargetForDomain(domain);
} else {
//
ArrayList<MXRecord> list =
@@ -62,7 +57,7 @@
}

/**
- * looks up MX records in the DNS system
+ * Looks up MX records in the DNS system
*
* @param domain
* @return an empty array if no MX record was found
@@ -71,8 +66,8 @@
* the domain is not registered or the DNS servers are not
* accessible or any other DNS related problem
*/
- private MXRecord[] queryMxRecords() throws MxLookupException {
- org.xbill.DNS.Lookup lookup;
+ private MXRecord[] queryMxRecords(Domain domain) throws
MxLookupException {
+ Lookup lookup;

try {
lookup = new Lookup(domain.smtpText(), org.xbill.DNS.Type.MX);
@@ -113,7 +108,7 @@
return records;
}

- private Name[] implicitMxTargetForDomain() {
+ private Name[] implicitMxTargetForDomain(Domain domain) {
Name[] singletonNames = new Name[] { domain.toName() };
return singletonNames;
}
@@ -131,6 +126,7 @@
@Immutable
private static class MXRecordPriorityComparator implements
java.util.Comparator<MXRecord> {
+ @Override
public int compare(MXRecord o1, MXRecord o2) {
int p1 = o1.getPriority();
int p2 = o2.getPriority();
=======================================
--- /trunk/src/mireka/transmission/immediate/dns/MxLookupException.java Sun
Mar 6 07:40:22 2011
+++ /trunk/src/mireka/transmission/immediate/dns/MxLookupException.java Thu
May 9 15:39:28 2013
@@ -1,7 +1,7 @@
package mireka.transmission.immediate.dns;

import mireka.smtp.EnhancedStatus;
-import mireka.transmission.immediate.SendException;
+import mireka.smtp.SendException;

public class MxLookupException extends SendException {
private static final long serialVersionUID = 3089456869348639027L;
=======================================
---
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitter.java
Sun May 5 07:37:27 2013
+++
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitter.java
Thu May 9 15:39:28 2013
@@ -1,23 +1,20 @@
package mireka.transmission.immediate.host;

import java.io.IOException;
-import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

-import javax.annotation.concurrent.NotThreadSafe;
-
import mireka.address.Recipient;
-import mireka.smtp.ClientFactory;
import mireka.smtp.EnhancedStatus;
+import mireka.smtp.SendException;
+import mireka.smtp.client.MtaAddress;
+import mireka.smtp.client.SmtpClient;
import mireka.transmission.Mail;
import mireka.transmission.immediate.PostponeException;
import mireka.transmission.immediate.RecipientRejection;
import mireka.transmission.immediate.RecipientsWereRejectedException;
-import mireka.transmission.immediate.RemoteMta;
import mireka.transmission.immediate.RemoteMtaErrorResponseException;
-import mireka.transmission.immediate.SendException;
import mireka.transmission.queuing.LogIdFactory;

import org.slf4j.Logger;
@@ -29,56 +26,41 @@
* MailToHostTransmitter transmits a mail to a specific host specified by
its IP
* address.
*/
-@NotThreadSafe
public class MailToHostTransmitter {
private final Logger logger = LoggerFactory
.getLogger(MailToHostTransmitter.class);
- private final ClientFactory clientFactory;
- private final OutgoingConnectionsRegistry outgoingConnectionsRegistry;
- private final LogIdFactory logIdFactory;
- private final RemoteMta remoteMta;
- private Mail mail;
-
- public MailToHostTransmitter(ClientFactory clientFactory,
- OutgoingConnectionsRegistry outgoingConnectionsRegistry,
- LogIdFactory logIdFactory, RemoteMta remoteMta) {
- this.clientFactory = clientFactory;
- this.outgoingConnectionsRegistry = outgoingConnectionsRegistry;
- this.logIdFactory = logIdFactory;
- this.remoteMta = remoteMta;
-
- }
+ private OutgoingConnectionsRegistry outgoingConnectionRegistry;
+ private LogIdFactory logIdFactory;

/**
* Delivers the mail to the SMTP server running on the specified host.
*
- * @param inetAddress
- * The receiving SMTP host. The name (if there is one) must
- * already be resolved (using dnsJava).
+ * @param client
+ * an unconnected, but otherwise fully initialized
+ * {@link SmtpClient}.
* @throws PostponeException
* if it has not even tried connecting to the host,
because it
* is likely that the host is busy at this moment.
*/
- public void transmit(Mail mail, InetAddress inetAddress)
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- this.mail = mail;
- SmartClient smartClient = null;
+ public void transmit(Mail mail, SmtpClient client) throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ MtaAddress remoteMta = client.getMtaAddress();
try {
- outgoingConnectionsRegistry.openConnection(inetAddress);
+ outgoingConnectionRegistry
+ .openConnection(client.getMtaAddress().address);
} catch (PostponeException e) {
e.setRemoteMta(remoteMta);
throw e;
}
try {
- smartClient = clientFactory.create(inetAddress);
- smartClient.from(mail.from.getSmtpText());
+ client.connect();
+ client.from(mail.from.getSmtpText());
List<RecipientRejection> recipientRejections =
new ArrayList<RecipientRejection>();
List<Recipient> acceptedRecipients = new
ArrayList<Recipient>();
for (Recipient recipient : mail.recipients) {
try {
- smartClient.to(recipient.sourceRouteStripped());
+ client.to(recipient.sourceRouteStripped());
acceptedRecipients.add(recipient);
} catch (SMTPException e) {
RemoteMtaErrorResponseException sendException =
@@ -97,9 +79,9 @@
logger.debug("All recipients were rejected");
throw new
RecipientsWereRejectedException(recipientRejections);
}
- smartClient.dataStart();
- writeDataTo(smartClient);
- smartClient.dataEnd();
+ client.dataStart();
+ writeMailConent(mail, client);
+ client.dataEnd();
if (!recipientRejections.isEmpty())
throw new
RecipientsWereRejectedException(recipientRejections);
else
@@ -112,18 +94,41 @@
} catch (IOException e) {
throw new SendException("Connection failed: " + e.toString(),
e,
new EnhancedStatus(450, "4.4.0",
- "No answer from host or bad connection"),
remoteMta);
+ "No answer from host or bad connection"));
} finally {
- if (smartClient != null) {
- smartClient.quit();
+ if (client != null) {
+ client.quit();
}
- outgoingConnectionsRegistry.releaseConnection(inetAddress);
+
outgoingConnectionRegistry.releaseConnection(remoteMta.address);
}
}

- private void writeDataTo(SmartClient smartClient) throws IOException {
+ private void writeMailConent(Mail mail, SmartClient smartClient)
+ throws IOException {
SmartClientOutputStreamAdapter out =
new SmartClientOutputStreamAdapter(smartClient);
mail.mailData.writeTo(out);
}
+
+ /**
+ * @category GETSET
+ */
+ public void setOutgoingConnectionRegistry(
+ OutgoingConnectionsRegistry outgoingConnectionRegistry) {
+ this.outgoingConnectionRegistry = outgoingConnectionRegistry;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public OutgoingConnectionsRegistry getOutgoingConnectionRegistry() {
+ return outgoingConnectionRegistry;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setLogIdFactory(LogIdFactory logIdFactory) {
+ this.logIdFactory = logIdFactory;
+ }
}
=======================================
--- /trunk/src/mireka/transmission/immediate/package-info.java Sun May 5
07:37:27 2013
+++ /trunk/src/mireka/transmission/immediate/package-info.java Thu May 9
15:39:28 2013
@@ -1,6 +1,7 @@
/**
- * Provides the API for services which make a one-time attempt to
synchronously
- * transmit a mail to a remote system.
+ * Provides the API and implementation for services which make a one-time
+ * attempt to synchronously transmit a mail to a remote system, either
+ * directly, or via a smart host.
*/
package mireka.transmission.immediate;

=======================================
--- /trunk/src/mireka/transmission/queuing/OutboundMtaMailProcessor.java
Tue Jun 12 13:44:33 2012
+++ /trunk/src/mireka/transmission/queuing/OutboundMtaMailProcessor.java
Thu May 9 15:39:28 2013
@@ -1,12 +1,11 @@
package mireka.transmission.queuing;

+import mireka.smtp.SendException;
import mireka.transmission.LocalMailSystemException;
import mireka.transmission.Mail;
import mireka.transmission.immediate.ImmediateSender;
-import mireka.transmission.immediate.ImmediateSenderFactory;
import mireka.transmission.immediate.PostponeException;
import mireka.transmission.immediate.RecipientsWereRejectedException;
-import mireka.transmission.immediate.SendException;
import mireka.transmission.queue.MailProcessor;
import mireka.transmission.queue.TransmitterSummary;

@@ -16,17 +15,17 @@
class OutboundMtaMailProcessor implements MailProcessor {
private final Logger logger = LoggerFactory
.getLogger(OutboundMtaMailProcessor.class);
- private final ImmediateSenderFactory immediateSenderFactory;
+ private final ImmediateSender immediateSender;
private final RetryPolicy retryPolicy;
private final LogIdFactory logIdFactory;
private final Mail mail;
private final TransmitterSummary summary;

public OutboundMtaMailProcessor(
- ImmediateSenderFactory immediateSenderFactory,
+ ImmediateSender immediateSender,
RetryPolicy retryPolicy, LogIdFactory logIdFactory,
TransmitterSummary summary, Mail mail) {
- this.immediateSenderFactory = immediateSenderFactory;
+ this.immediateSender = immediateSender;
this.mail = mail;
this.retryPolicy = retryPolicy;
this.logIdFactory = logIdFactory;
@@ -47,12 +46,11 @@
}

private void send() throws LocalMailSystemException {
- ImmediateSender sender = immediateSenderFactory.create();
try {
logger.debug("Sending mail " + mail + "...");
summary.mailTransactionsMeter().mark();

- sender.send(mail);
+ immediateSender.send(mail);

logger.debug("Sent successfully");
summary.successfulMailTransactionsMeter().mark();
=======================================
--- /trunk/src/mireka/transmission/queuing/QueuingTransmitter.java Sun May
5 07:37:27 2013
+++ /trunk/src/mireka/transmission/queuing/QueuingTransmitter.java Thu May
9 15:39:28 2013
@@ -10,7 +10,7 @@
import mireka.address.RemotePartContainingRecipient;
import mireka.transmission.Mail;
import mireka.transmission.Transmitter;
-import mireka.transmission.immediate.ImmediateSenderFactory;
+import mireka.transmission.immediate.ImmediateSender;
import mireka.transmission.queue.MailProcessor;
import mireka.transmission.queue.MailProcessorFactory;
import mireka.transmission.queue.QueueStorageException;
@@ -21,9 +21,10 @@
import org.slf4j.LoggerFactory;

public class QueuingTransmitter implements Transmitter,
MailProcessorFactory {
- private final Logger logger =
LoggerFactory.getLogger(QueuingTransmitter.class);
+ private final Logger logger = LoggerFactory
+ .getLogger(QueuingTransmitter.class);
private ScheduleFileDirQueue queue;
- private ImmediateSenderFactory immediateSenderFactory;
+ private ImmediateSender immediateSender;
private RetryPolicy retryPolicy;
private LogIdFactory logIdFactory;
private TransmitterSummary summary;
@@ -31,7 +32,12 @@
@Override
public void transmit(Mail mail) throws QueueStorageException {
logger.debug("Mail received for transmission: {}", mail);
- queueByRemotePart(mail);
+ if (immediateSender.singleDomainOnly()) {
+ queueByRemotePart(mail);
+ } else {
+ queue.add(mail);
+ logger.debug("Mail was added to queue: {}", mail);
+ }
}

private void queueByRemotePart(Mail mail) throws QueueStorageException
{
@@ -69,8 +75,8 @@

@Override
public MailProcessor create(Mail mail) {
- return new OutboundMtaMailProcessor(immediateSenderFactory,
- retryPolicy, logIdFactory, summary, mail);
+ return new OutboundMtaMailProcessor(immediateSender, retryPolicy,
+ logIdFactory, summary, mail);
}

/**
@@ -85,9 +91,8 @@
/**
* @category GETSET
*/
- public void setImmediateSenderFactory(
- ImmediateSenderFactory immediateSenderFactory) {
- this.immediateSenderFactory = immediateSenderFactory;
+ public void setImmediateSender(ImmediateSender immediateSender) {
+ this.immediateSender = immediateSender;
}

/**
=======================================
--- /trunk/src/mireka/transmission/queuing/RetryPolicy.java Sat Jun 2
00:59:37 2012
+++ /trunk/src/mireka/transmission/queuing/RetryPolicy.java Thu May 9
15:39:28 2013
@@ -5,6 +5,7 @@
import java.util.List;

import mireka.address.Recipient;
+import mireka.smtp.SendException;
import mireka.transmission.LocalMailSystemException;
import mireka.transmission.Mail;
import mireka.transmission.Transmitter;
@@ -16,7 +17,6 @@
import mireka.transmission.immediate.RecipientRejection;
import mireka.transmission.immediate.RecipientsWereRejectedException;
import mireka.transmission.immediate.RemoteMtaErrorResponseException;
-import mireka.transmission.immediate.SendException;

import org.joda.time.DateTime;
import org.joda.time.Instant;
@@ -40,7 +40,7 @@
* delayed DSN mail must be sent. For example 3 means that a DSN must
be
* issued after the third failed attempt.
*/
- private List<Integer> delayReportPoints = new ArrayList<Integer>();
+ private final List<Integer> delayReportPoints = new
ArrayList<Integer>();
private DsnMailCreator dsnMailCreator;
private Transmitter dsnTransmitter;
private Transmitter retryTransmitter;
@@ -106,7 +106,7 @@
SendException sendException =
new SendException(
"Too much postponings of delivery attempt,
attempt is considered to be a failure.",
- e, e.getEnhancedStatus(), e.getRemoteMta());
+ e, e.getEnhancedStatus());
EntireMailFailureHandler failureHandler =
new EntireMailFailureHandler(mail, sendException);
failureHandler.onFailure();
@@ -206,13 +206,13 @@
protected final Mail mail;

private List<SendingFailure> failures;
- private List<SendingFailure> permanentFailures =
+ private final List<SendingFailure> permanentFailures =
new ArrayList<SendingFailure>();
- private List<SendingFailure> transientFailures =
+ private final List<SendingFailure> transientFailures =
new ArrayList<SendingFailure>();
- private List<PermanentFailureReport> permanentFailureReports =
+ private final List<PermanentFailureReport> permanentFailureReports
=
new ArrayList<PermanentFailureReport>();
- private List<DelayReport> delayReports = new
ArrayList<DelayReport>();
+ private final List<DelayReport> delayReports = new
ArrayList<DelayReport>();

public FailureHandler(Mail mail) {
this.mail = mail;
@@ -262,11 +262,13 @@
SendException exception = failure.exception;
report.recipient = failure.recipient;
report.status = exception.errorStatus();
- report.remoteMta = exception.remoteMta();
- if (exception instanceof RemoteMtaErrorResponseException)
+ if (exception instanceof RemoteMtaErrorResponseException) {
+ RemoteMtaErrorResponseException rmerException =
+ (RemoteMtaErrorResponseException) exception;
+ report.remoteMta = rmerException.remoteMta();
report.remoteMtaDiagnosticStatus =
- ((RemoteMtaErrorResponseException) exception)
- .remoteMtaStatus();
+ rmerException.remoteMtaStatus();
+ }
report.failureDate = exception.failureDate;
report.logId = exception.getLogId();
}
@@ -290,9 +292,9 @@
if (reports.isEmpty())
return;
if (mail.from.isNull()) {
- logger.debug("Failure or delay, but reverse-path is null, "
+ logger.error("Failure or delay, but reverse-path is null, "
+ "DSN must not be sent. "
- + "Original mail itself was a notification.");
+ + "Original mail itself was a notification. {}",
mail.toString());
return;
}
Mail dsnMail = dsnMailCreator.create(mail, reports);
=======================================
--- /trunk/test/mireka/ExampleAddress.java Wed Jul 20 09:22:26 2011
+++ /trunk/test/mireka/ExampleAddress.java Thu May 9 15:39:28 2013
@@ -31,8 +31,11 @@
public static final String JANE = "ja...@example.com";
public static final String JOHN = "jo...@example.com";
public static final String NANCY_NET = "na...@example.net";
+ /** ja...@example.com **/
public static final Recipient JANE_AS_RECIPIENT;
+ /** jo...@example.com **/
public static final Recipient JOHN_AS_RECIPIENT;
+ /** na...@example.net **/
public static final Recipient NANCY_NET_AS_RECIPIENT;
public static final ReversePath JANE_AS_REVERSE_PATH;
public static final ReversePath JOHN_AS_REVERSE_PATH;
=======================================
--- /trunk/test/mireka/filter/proxy/RelayFunctionalTest.java Sun May 15
15:56:55 2011
+++ /trunk/test/mireka/filter/proxy/RelayFunctionalTest.java Thu May 9
15:39:28 2013
@@ -10,7 +10,8 @@
import mireka.filter.local.table.AnyRecipient;
import mireka.filter.local.table.RecipientSpecificationDestinationPair;
import mireka.filterchain.Filters;
-import mireka.smtp.ClientFactory;
+import mireka.smtp.client.BackendServer;
+import mireka.smtp.client.ClientFactory;
import mireka.smtp.server.MessageHandlerFactoryImpl;
import mireka.smtp.server.SMTPServer;

=======================================
--- /trunk/test/mireka/filter/proxy/RelayMailTransactionTest.java Wed Jul
20 09:22:26 2011
+++ /trunk/test/mireka/filter/proxy/RelayMailTransactionTest.java Thu May
9 15:39:28 2013
@@ -5,13 +5,14 @@
import mireka.address.NullReversePath;
import mireka.destination.Session;
import mireka.filter.RecipientContext;
+import mireka.smtp.client.BackendServer;
+import mireka.smtp.client.SmtpClient;
import mockit.Expectations;
import mockit.Mocked;
import mockit.NonStrict;

import org.junit.Before;
import org.junit.Test;
-import org.subethamail.smtp.client.SmartClient;

public class RelayMailTransactionTest {
@Mocked
@@ -19,11 +20,11 @@

@NonStrict
@Mocked(stubOutClassInitialization = false)
- private SmartClient smartClient;
+ private SmtpClient client;

- private RecipientContext recipientContextJane = new
RecipientContext(null,
+ private final RecipientContext recipientContextJane = new
RecipientContext(null,
ExampleAddress.JANE_AS_RECIPIENT);
- private RecipientContext recipientContextJohn = new
RecipientContext(null,
+ private final RecipientContext recipientContextJohn = new
RecipientContext(null,
ExampleAddress.JOHN_AS_RECIPIENT);

private Session session;
@@ -40,13 +41,13 @@

new Expectations() {
{
- backendServer.connect();
- result = smartClient;
+ backendServer.createClient();
+ result = client;

- smartClient.dataEnd();
+ client.dataEnd();
times = 1;

- smartClient.quit();
+ client.quit();
}
};

@@ -60,12 +61,12 @@

new Expectations() {
{
- backendServer.connect();
- result = smartClient;
+ backendServer.createClient();
+ result = client;

- smartClient.to(anyString);
+ client.to(anyString);
times = 2;
- smartClient.dataEnd();
+ client.dataEnd();
times = 1;
}
};
=======================================
***Additional files exist in this changeset.***
Reply all
Reply to author
Forward
0 new messages