package mireka.filter.proxy;
import java.io.IOException;
import java.io.InputStream;
import mireka.address.ReversePath;
import mireka.destination.Session;
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;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.RejectException;
/**
* RelayDestination relays each step of the mail transaction in "realtime" to a
* gateway. Different recipients may be relayed to different backend servers.
* This filter handles only recipients whose destination is a
* {@link RelayDestination}. The algorithm is the same as in Baton. The backend
* server is only connected on the first RCPT SMTP statement. This means that
* with proper configuration the backend server is not connected at all if no
* recipient were accepted. Moreover, in this way the decision of which which
* server to use may depend on the recipient address. The delaying is useful
* because most mail transactions are SPAM, and they are aborted after the first
* RCPT TO command is received and rejected.
* <p>
* Note: it does not verify any recipient. In order to deliver the message some
* other filter must verify and accept the recipients.
*
* @see <a href="http://code.google.com/p/baton/">Baton SMTP proxy</a>
*/
public class RelayDestination implements SessionDestination {
private Logger logger = LoggerFactory.getLogger(RelayDestination.class);
private BackendServer backendServer;
@Override
public Session createSession() {
return new SessionImpl();
}
/**
* @x.category GETSET
*/
public BackendServer getBackendServer() {
return backendServer;
}
/**
* @x.category GETSET
*/
public void setBackendServer(BackendServer backendServer) {
this.backendServer = backendServer;
}
@Override
public String toString() {
return "RelayDestination [backendServer=" + backendServer.getHost()
+ ":" + backendServer.getPort() + "]";
}
private class SessionImpl implements Session {
private BackendClient client;
private ReversePath from;
@Override
public void from(ReversePath from) throws RejectExceptionExt {
this.from = from;
}
@Override
public void recipient(RecipientContext recipientContext)
throws RejectExceptionExt {
initClient();
// if there was an IO error, or the "from" was rejected, then
// this recipient will be rejected too
client.recipient(recipientContext.recipient);
}
private void initClient() {
if (client != null)
return;
client = new BackendClient(backendServer);
try {
client.connect();
client.from(from.getSmtpText());
} catch (RejectException e) {
logger.debug("Connection to backend server failed, "
+ "failed status is memorized, continuing...");
}
}
/**
* Relays the data to the backend server.
* <p>
* Note: there are two types of IOException here. One is related to the
* communication with the backend server, the second is related to the
* mail data received in the parameter. This implementation can throw an
* IOException in both cases. It would be more clear, if the
* communication failure with the backend would be indicated by a
* separate exception.
*/
@Override
public void data(Mail mail) throws RejectExceptionExt, IOException {
if (!client.hasAcceptedRecipient())
return;
InputStream dataStream = mail.mailData.getInputStream();
try {
client.data(dataStream);
} finally {
dataStream.close();
}
}
@Override
public void done() {
if (client != null)
client.quit();
}
}
}