blob: a50bc4d7f6bcef390393bb1b25d9c898e45662a4 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.transport.mailets.remote.delivery;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Optional;
import jakarta.mail.MessagingException;
import jakarta.mail.SendFailedException;
import org.apache.james.core.MailAddress;
import org.apache.mailet.Attribute;
import org.apache.mailet.AttributeName;
import org.apache.mailet.AttributeValue;
import org.apache.mailet.Mail;
import org.apache.mailet.MailetContext;
import org.apache.mailet.ProcessingState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.fge.lambdas.Throwing;
public class Bouncer {
private static final Logger LOGGER = LoggerFactory.getLogger(Bouncer.class);
public static final AttributeName DELIVERY_ERROR = AttributeName.of("delivery-error");
public static final AttributeName DELIVERY_ERROR_CODE = AttributeName.of("delivery-error-code");
public static final AttributeName IS_DELIVERY_PERMANENT_ERROR = AttributeName.of("is-delivery-permanent-error");
private final RemoteDeliveryConfiguration configuration;
private final MailetContext mailetContext;
public Bouncer(RemoteDeliveryConfiguration configuration, MailetContext mailetContext) {
this.configuration = configuration;
this.mailetContext = mailetContext;
}
public void bounce(Mail mail, Exception ex) throws MessagingException {
configuration.getBounceProcessor().ifPresentOrElse(
Throwing.<ProcessingState>consumer(bounceProcessor -> {
computeErrorCode(ex).ifPresent(mail::setAttribute);
mail.setAttribute(new Attribute(DELIVERY_ERROR, AttributeValue.of(getErrorMsg(ex))));
mailetContext.sendMail(mail, bounceProcessor.getValue());
}).sneakyThrow(),
Throwing.runnable(() -> bounceWithMailetContext(mail, ex)).sneakyThrow());
}
private Optional<Attribute> computeErrorCode(Exception ex) {
return Optional.ofNullable(ex)
.filter(MessagingException.class::isInstance)
.map(MessagingException.class::cast)
.map(EnhancedMessagingException::new)
.flatMap(EnhancedMessagingException::getReturnCode)
.map(code -> new Attribute(DELIVERY_ERROR_CODE, AttributeValue.of(code)));
}
private void bounceWithMailetContext(Mail mail, Exception ex) throws MessagingException {
if (!mail.hasSender()) {
LOGGER.debug("Null Sender: no bounce will be generated for {}", mail.getName());
} else {
LOGGER.debug("Sending failure message {}", mail.getName());
mailetContext.bounce(mail, explanationText(mail, ex));
}
}
public String explanationText(Mail mail, Exception ex) {
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
out.println("Hi. This is the James mail server at " + resolveMachineName() + ".");
out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
out.println("I include the list of recipients and the reason why I was unable to deliver");
out.println("your message.");
out.println();
for (MailAddress mailAddress : mail.getRecipients()) {
out.println(mailAddress);
}
if (ex instanceof MessagingException) {
if (((MessagingException) ex).getNextException() == null) {
out.println(sanitizeExceptionMessage(ex));
} else {
Exception ex1 = ((MessagingException) ex).getNextException();
if (ex1 instanceof SendFailedException) {
out.println("Remote mail server told me: " + sanitizeExceptionMessage(ex1));
} else if (ex1 instanceof UnknownHostException) {
out.println("Unknown host: " + sanitizeExceptionMessage(ex1));
out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
} else if (ex1 instanceof ConnectException) {
// Already formatted as "Connection timed out: connect"
out.println(sanitizeExceptionMessage(ex1));
} else if (ex1 instanceof SocketException) {
out.println("Socket exception: " + sanitizeExceptionMessage(ex1));
} else {
out.println(sanitizeExceptionMessage(ex1));
}
}
}
out.println();
return sout.toString();
}
private String sanitizeExceptionMessage(Exception e) {
if (e == null || e.getMessage() == null) {
return "null";
} else {
return e.getMessage().trim();
}
}
private String resolveMachineName() {
try {
return configuration.getHeloNameProvider().getHeloName();
} catch (Exception e) {
return "[address unknown]";
}
}
public String getErrorMsg(Exception ex) {
if (ex instanceof MessagingException) {
return getNestedExceptionMessage((MessagingException) ex);
} else {
return sanitizeExceptionMessage(ex);
}
}
public String getNestedExceptionMessage(MessagingException me) {
if (me.getNextException() == null) {
return sanitizeExceptionMessage(me);
} else {
Exception ex1 = me.getNextException();
return sanitizeExceptionMessage(ex1);
}
}
}