io.vertx.ext.mail.impl.SMTPAuthentication Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2011-2015 The original author or authors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.mail.impl;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.ext.mail.LoginOption;
import io.vertx.ext.mail.MailConfig;
import io.vertx.ext.mail.impl.sasl.AuthOperation;
import io.vertx.ext.mail.impl.sasl.AuthOperationFactory;
import io.vertx.ext.mail.impl.sasl.CryptUtils;
import java.util.List;
/**
* Handle the authentication flow
*
* @author Alexander Lehmann
*/
class SMTPAuthentication {
private static final Logger log = LoggerFactory.getLogger(SMTPAuthentication.class);
private final SMTPConnection connection;
private final MailConfig config;
private final AuthOperationFactory authOperationFactory;
private final Promise promise;
SMTPAuthentication(ContextInternal context, SMTPConnection connection, MailConfig config, AuthOperationFactory authOperationFactory) {
this.connection = connection;
this.config = config;
this.authOperationFactory = authOperationFactory;
this.promise = context.promise();
}
public Future start() {
List auths = intersectAllowedMethods();
final boolean foundAllowedMethods = !auths.isEmpty();
if (config.getLogin() != LoginOption.DISABLED && config.getUsername() != null && config.getPassword() != null
&& foundAllowedMethods) {
authCmd(auths);
} else {
if (config.getLogin() == LoginOption.REQUIRED) {
if (!foundAllowedMethods) {
promise.fail("login is required, but no allowed AUTH methods available. You may need to do STARTTLS");
} else {
promise.fail("login is required, but no credentials supplied");
}
} else {
promise.complete();
}
}
return promise.future();
}
/**
* find the auth methods we can use
*
* @return intersection between supported and allowed auth methods
*/
private List intersectAllowedMethods() {
List supported = this.authOperationFactory.supportedAuths(config);
supported.retainAll(connection.getCapa().getCapaAuth());
return supported;
}
private void authCmd(List auths) {
// if we have defined a choice of methods, only use these
// this works for example to avoid plain text pw methods with
// "CRAM-SHA1 CRAM-MD5"
String defaultAuth = this.authOperationFactory.getAuthMethod();
if (defaultAuth != null) {
if (log.isDebugEnabled()) {
log.debug("Using default auth method: " + defaultAuth);
}
authMethod(defaultAuth, error -> authChain(auths, 0, null));
} else {
authChain(auths, 0, null);
}
}
private void authChain(List auths, int i, Throwable e) {
if (i < auths.size()) {
if (e != null) {
log.warn(e);
}
authMethod(auths.get(i), error -> authChain(auths, i + 1, error));
} else {
promise.fail(e);
}
}
private void authMethod(String auth, Handler onError) {
AuthOperation authMethod;
try {
authMethod = authOperationFactory.createAuth(config, auth);
} catch (IllegalArgumentException | SecurityException ex) {
log.warn("authentication factory threw exception", ex);
promise.fail(ex);
return;
}
authCmdStep(authMethod, null, onError);
}
private void authCmdStep(AuthOperation authMethod, String message, Handler onError) {
String nextLine;
int blank;
try {
if (message == null) {
String authParameter = authMethod.nextStep(null);
if (!authParameter.isEmpty()) {
if (!authMethod.handleCoding()) {
authParameter = CryptUtils.base64(authParameter);
}
nextLine = "AUTH " + authMethod.getName() + " " + authParameter;
blank = authMethod.getName().length() + 6;
} else {
nextLine = "AUTH " + authMethod.getName();
blank = -1;
}
} else {
if (!authMethod.handleCoding()) {
nextLine = CryptUtils.base64(authMethod.nextStep(CryptUtils.decodeb64(message.substring(4))));
} else {
nextLine = authMethod.nextStep(message.substring(4));
}
blank = 0;
}
} catch (Exception e) {
log.warn("Failed to handle server auth message: " + message, e);
onError.handle(e);
return;
}
connection.write(nextLine, blank).onComplete(ar -> {
if (ar.failed()) {
onError.handle(ar.cause());
return;
}
SMTPResponse response = ar.result();
if (response.isStatusOk()) {
if (response.isStatusContinue()) {
log.debug("Auth Continue with response: " + response.getValue());
authCmdStep(authMethod, response.getValue(), onError);
} else {
authOperationFactory.setAuthMethod(authMethod.getName());
promise.complete();
}
} else {
onError.handle(response.toException("AUTH " + authMethod.getName() + " failed", connection.getCapa().isCapaEnhancedStatusCodes()));
}
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy