Repository: mina-sshd
Updated Branches:
  refs/heads/master f8f1e0872 -> 7df595887


[SSHD-688] Attempt to send the welcome banner sooner in the authentication 
protocol


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/7df59588
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/7df59588
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/7df59588

Branch: refs/heads/master
Commit: 7df59588718dccc2a2451d5514449e82edc17ae7
Parents: f8f1e08
Author: Lyor Goldstein <lyor.goldst...@gmail.com>
Authored: Wed Aug 17 21:49:12 2016 +0300
Committer: Lyor Goldstein <lyor.goldst...@gmail.com>
Committed: Wed Aug 17 21:49:12 2016 +0300

----------------------------------------------------------------------
 .../sshd/common/PropertyResolverUtils.java      |  56 +++++++
 .../java/org/apache/sshd/common/Service.java    |   1 -
 .../common/session/helpers/AbstractSession.java |   5 +-
 .../server/ServerAuthenticationManager.java     |  54 +++++++
 .../sshd/server/ServerFactoryManager.java       |  42 ------
 .../java/org/apache/sshd/server/SshServer.java  |   8 +-
 .../sshd/server/auth/WelcomeBannerPhase.java    |  53 +++++++
 .../server/session/AbstractServerSession.java   |  23 ++-
 .../server/session/ServerUserAuthService.java   | 110 ++++++++++----
 .../java/org/apache/sshd/WelcomeBannerTest.java | 129 -----------------
 .../sshd/common/PropertyResolverUtilsTest.java  |  50 ++++++-
 .../sshd/common/auth/AuthenticationTest.java    |  23 +--
 .../common/auth/SinglePublicKeyAuthTest.java    |   4 +-
 .../server/auth/WelcomeBannerPhaseTest.java     | 135 +++++++++++++++++
 .../sshd/server/auth/WelcomeBannerTest.java     | 145 +++++++++++++++++++
 15 files changed, 612 insertions(+), 226 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
index 903a796..40b2510 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
@@ -19,7 +19,9 @@
 
 package org.apache.sshd.common;
 
+import java.util.Collection;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 
 import org.apache.sshd.common.util.GenericUtils;
@@ -136,6 +138,60 @@ public final class PropertyResolverUtils {
         }
     }
 
+    /**
+     * Converts an enumerated configuration value:
+     * <UL>
+     *      <P><LI>
+     *      If value is {@code null} then return {@code null}
+     *      </LI></P>
+     *
+     *      <P><LI>
+     *      If value already of the expected type then simply
+     *      cast and return it.
+     *      </LI></P>
+     *
+     *      <P><LI>
+     *      If value is a {@link CharSequence} then convert it
+     *      to a string and look for a matching enumerated value
+     *      name - case <U>insensitive</U>.
+     *      </LI></P>>
+     * </UL>
+     *
+     * @param <E> Type of enumerated value
+     * @param enumType The enumerated class type
+     * @param value The configured value - ignored if {@code null}
+     * @param failIfNoMatch Whether to fail if no matching name found
+     * @param available The available values to compare the name
+     * @return The matching enumerated value - {@code null} if no match found
+     * @throws IllegalArgumentException If value is neither {@code null},
+     * nor the enumerated type nor a {@link CharSequence}
+     * @throws NoSuchElementException If no matching string name found and
+     * <tt>failIfNoMatch</tt> is {@code true}
+     */
+    public static <E extends Enum<E>> E toEnum(Class<E> enumType, Object 
value, boolean failIfNoMatch, Collection<E> available) {
+        if (value == null) {
+            return null;
+        } else if (enumType.isInstance(value)) {
+            return enumType.cast(value);
+        } else if (value instanceof CharSequence) {
+            String name = value.toString();
+            if (GenericUtils.size(available) > 0) {
+                for (E v : available) {
+                    if (name.equalsIgnoreCase(v.name())) {
+                        return v;
+                    }
+                }
+            }
+
+            if (failIfNoMatch) {
+                throw new NoSuchElementException("No match found for " + 
enumType.getSimpleName() + "[" + name + "]");
+            }
+
+            return null;
+        } else {
+            throw new IllegalArgumentException("Bad value type for enum 
conversion: " + value.getClass().getSimpleName());
+        }
+    }
     public static Object updateProperty(PropertyResolver resolver, String 
name, long value) {
         return updateProperty(resolver.getProperties(), name, value);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/common/Service.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Service.java 
b/sshd-core/src/main/java/org/apache/sshd/common/Service.java
index 35bc7c0..8807c87 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Service.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Service.java
@@ -41,5 +41,4 @@ public interface Service extends Closeable {
      * @throws Exception If failed to process the command
      */
     void process(int cmd, Buffer buffer) throws Exception;
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
index ad663e4..acf8e70 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
@@ -665,7 +665,7 @@ public abstract class AbstractSession extends 
AbstractKexFactoryManager implemen
         handleServiceRequest(buffer.getString(), buffer);
     }
 
-    protected void handleServiceRequest(String serviceName, Buffer buffer) 
throws Exception {
+    protected boolean handleServiceRequest(String serviceName, Buffer buffer) 
throws Exception {
         if (log.isDebugEnabled()) {
             log.debug("handleServiceRequest({}) SSH_MSG_SERVICE_REQUEST '{}'", 
this, serviceName);
         }
@@ -682,7 +682,7 @@ public abstract class AbstractSession extends 
AbstractKexFactoryManager implemen
                 log.trace("handleServiceRequest(" + this + ") service=" + 
serviceName + " rejection details", e);
             }
             disconnect(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, 
"Bad service request: " + serviceName);
-            return;
+            return false;
         }
 
         if (log.isDebugEnabled()) {
@@ -692,6 +692,7 @@ public abstract class AbstractSession extends 
AbstractKexFactoryManager implemen
         Buffer response = createBuffer(SshConstants.SSH_MSG_SERVICE_ACCEPT, 
Byte.SIZE + GenericUtils.length(serviceName));
         response.putString(serviceName);
         writePacket(response);
+        return true;
     }
 
     protected void handleServiceAccept(Buffer buffer) throws Exception {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
index 0d8a6e8..b1978e1 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
@@ -31,6 +31,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.server.auth.BuiltinUserAuthFactories;
 import org.apache.sshd.server.auth.UserAuth;
+import org.apache.sshd.server.auth.WelcomeBannerPhase;
 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
 import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
 import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
@@ -60,6 +61,59 @@ public interface ServerAuthenticationManager {
     int DEFAULT_MAX_AUTH_REQUESTS = 20;
 
     /**
+     * Key used to retrieve the value of welcome banner that will be displayed
+     * when a user connects to the server. If {@code null}/empty then no banner
+     * will be sent.
+     * @see <A HREF="https://www.ietf.org/rfc/rfc4252.txt";>RFC-4252 section 
5.4</A>
+     */
+    String WELCOME_BANNER = "welcome-banner";
+
+    /**
+     * Special value that can be set for the {@link #WELCOME_BANNER} property
+     * indicating that the server should generate a banner consisting of the
+     * random art of the server's keys (if any are provided). If no server
+     * keys are available, then no banner will be sent
+     */
+    String AUTO_WELCOME_BANNER_VALUE = "#auto-welcome-banner";
+
+    /**
+     * Key used to denote the language code for the welcome banner (if such
+     * a banner is configured). If not set, then {@link 
ServerAuthenticationManager#DEFAULT_WELCOME_BANNER_LANGUAGE}
+     * is used
+     */
+    String WELCOME_BANNER_LANGUAGE = "welcome-banner-language";
+
+    /**
+     * Default value for {@link #WELCOME_BANNER_LANGUAGE} is not overwritten
+     */
+    String DEFAULT_WELCOME_BANNER_LANGUAGE = "en";
+
+    /**
+     * The {@link WelcomeBannerPhase} value - either as an enum or
+     * a string
+     */
+    String WELCOME_BANNER_PHASE = "welcome-banner-phase";
+
+    /**
+     * Default value for {@link #WELCOME_BANNER_PHASE} if none specified
+     */
+    WelcomeBannerPhase DEFAULT_BANNER_PHASE = WelcomeBannerPhase.IMMEDIATE;
+
+    /**
+     * This key is used when configuring multi-step authentications.
+     * The value needs to be a blank separated list of comma separated list
+     * of authentication method names.
+     * For example, an argument of
+     * <code>publickey,password publickey,keyboard-interactive</code>
+     * would require the user to complete public key authentication,
+     * followed by either password or keyboard interactive authentication.
+     * Only methods that are next in one or more lists are offered at each
+     * stage, so for this example, it would not be possible to attempt
+     * password or keyboard-interactive authentication before public key.
+     */
+    String AUTH_METHODS = "auth-methods";
+
+    /**
      * Retrieve the list of named factories for <code>UserAuth</code> objects.
      *
      * @return a list of named <code>UserAuth</code> factories, never {@code 
null}/empty

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 0a488e8..a08fb30 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -67,48 +67,6 @@ public interface ServerFactoryManager
     String SERVER_IDENTIFICATION = "server-identification";
 
     /**
-     * Key used to retrieve the value of welcome banner that will be displayed
-     * when a user connects to the server. If {@code null}/empty then no banner
-     * will be sent.
-     * @see <A HREF="https://www.ietf.org/rfc/rfc4252.txt";>RFC-4252 section 
5.4</A>
-     */
-    String WELCOME_BANNER = "welcome-banner";
-
-    /**
-     * Special value that can be set for the {@link #WELCOME_BANNER} property
-     * indicating that the server should generate a banner consisting of the
-     * random art of the server's keys (if any are provided). If no server
-     * keys are available, then no banner will be sent
-     */
-    String AUTO_WELCOME_BANNER_VALUE = "#auto-welcome-banner";
-
-    /**
-     * Key used to denote the language code for the welcome banner (if such
-     * a banner is configured). If not set, then {@link 
#DEFAULT_WELCOME_BANNER_LANGUAGE}
-     * is used
-     */
-    String WELCOME_BANNER_LANGUAGE = "welcome-banner-language";
-
-    /**
-     * Default value for {@link #WELCOME_BANNER_LANGUAGE} is not overwritten
-     */
-    String DEFAULT_WELCOME_BANNER_LANGUAGE = "en";
-
-    /**
-     * This key is used when configuring multi-step authentications.
-     * The value needs to be a blank separated list of comma separated list
-     * of authentication method names.
-     * For example, an argument of
-     * <code>publickey,password publickey,keyboard-interactive</code>
-     * would require the user to complete public key authentication,
-     * followed by either password or keyboard interactive authentication.
-     * Only methods that are next in one or more lists are offered at each
-     * stage, so for this example, it would not be possible to attempt
-     * password or keyboard-interactive authentication before public key.
-     */
-    String AUTH_METHODS = "auth-methods";
-
-    /**
      * Key used to configure the timeout used when receiving a close request
      * on a channel to wait until the command cleanly exits after setting
      * an EOF on the input stream. In milliseconds.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java 
b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 35757c1..a6e635b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -617,7 +617,7 @@ public class SshServer extends AbstractFactoryManager 
implements ServerFactoryMa
                     ? null
                     : 
Objects.toString(options.remove(SshConfigFileReader.VISUAL_HOST_KEY), null);
             if (SshConfigFileReader.parseBooleanValue(bannerOption)) {
-                bannerOption = ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE;
+                bannerOption = 
ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE;
             }
         }
 
@@ -627,7 +627,7 @@ public class SshServer extends AbstractFactoryManager 
implements ServerFactoryMa
                 return null;
             }
 
-            if 
(ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(bannerOption)) 
{
+            if 
(ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(bannerOption))
 {
                 banner = KeyRandomArt.combine(' ', 
server.getKeyPairProvider());
             } else {
                 Path path = Paths.get(bannerOption);
@@ -642,9 +642,9 @@ public class SshServer extends AbstractFactoryManager 
implements ServerFactoryMa
         }
 
         if (GenericUtils.length(banner) > 0) {
-            PropertyResolverUtils.updateProperty(server, 
ServerFactoryManager.WELCOME_BANNER, banner);
+            PropertyResolverUtils.updateProperty(server, 
ServerAuthenticationManager.WELCOME_BANNER, banner);
         }
 
-        return PropertyResolverUtils.getString(server, 
ServerFactoryManager.WELCOME_BANNER);
+        return PropertyResolverUtils.getString(server, 
ServerAuthenticationManager.WELCOME_BANNER);
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/auth/WelcomeBannerPhase.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/WelcomeBannerPhase.java 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/WelcomeBannerPhase.java
new file mode 100644
index 0000000..66becc1
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/WelcomeBannerPhase.java
@@ -0,0 +1,53 @@
+/*
+ * 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.sshd.server.auth;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Used to indicate at which authentication phase to send the welcome
+ * banner (if any configured)
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ * @see <a href="https://tools.ietf.org/html/rfc4252#section-5.4";>RFC-4252 
section 5.4</a>
+ */
+public enum WelcomeBannerPhase {
+    /** Immediately after receiving &quot;ssh-userauth&quot; request */
+    IMMEDIATE,
+    /** On first {@code SSH_MSG_USERAUTH_REQUEST} */
+    FIRST_REQUEST,
+    /** On first {@code SSH_MSG_USERAUTH_XXX} extension command */
+    FIRST_AUTHCMD,
+    /** On first {@code SSH_MSG_USERAUTH_FAILURE} */
+    FIRST_FAILURE,
+    /** After user successfully authenticates */
+    POST_SUCCESS,
+    /**
+     * Do not send a welcome banner even if one is configured. <B>Note:</B>
+     * this option is useful when a global welcome banner has been configured
+     * but we want to disable it for a specific session.
+     */
+    NEVER;
+
+    public static final Set<WelcomeBannerPhase> VALUES =
+            
Collections.unmodifiableSet(EnumSet.allOf(WelcomeBannerPhase.class));
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
index 46879ad..2ec01da 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
@@ -35,6 +35,7 @@ import org.apache.sshd.common.RuntimeSshException;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.auth.AbstractUserAuthServiceFactory;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
@@ -49,6 +50,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.auth.UserAuth;
+import org.apache.sshd.server.auth.WelcomeBannerPhase;
 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
 import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
 import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
@@ -183,9 +185,28 @@ public abstract class AbstractServerSession extends 
AbstractSession implements S
     }
 
     @Override
+    protected boolean handleServiceRequest(String serviceName, Buffer buffer) 
throws Exception {
+        boolean started = super.handleServiceRequest(serviceName, buffer);
+        if (!started) {
+            return false;
+        }
+
+        if (AbstractUserAuthServiceFactory.DEFAULT_NAME.equals(serviceName)
+                && (currentService instanceof ServerUserAuthService)) {
+            ServerUserAuthService authService = (ServerUserAuthService) 
currentService;
+            if 
(WelcomeBannerPhase.IMMEDIATE.equals(authService.getWelcomePhase())) {
+                authService.sendWelcomeBanner(this);
+            }
+        }
+
+        return true;
+    }
+
+    @Override
     public void startService(String name) throws Exception {
+        FactoryManager factoryManager = getFactoryManager();
         currentService = ServiceFactory.Utils.create(
-                        getFactoryManager().getServiceFactories(),
+                        factoryManager.getServiceFactories(),
                         ValidateUtils.checkNotNullAndNotEmpty(name, "No 
service name"),
                         this);
         /*

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
index 0e0186e..b0593f6 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
@@ -18,10 +18,12 @@
  */
 package org.apache.sshd.server.session;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.NamedFactory;
@@ -31,6 +33,7 @@ import org.apache.sshd.common.Service;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.config.keys.KeyRandomArt;
+import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -40,13 +43,15 @@ import org.apache.sshd.server.ServerAuthenticationManager;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.auth.UserAuth;
 import org.apache.sshd.server.auth.UserAuthNoneFactory;
+import org.apache.sshd.server.auth.WelcomeBannerPhase;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public class ServerUserAuthService extends AbstractCloseable implements 
Service, ServerSessionHolder {
-
     private final ServerSession serverSession;
+    private final AtomicBoolean welcomeSent = new AtomicBoolean(false);
+    private final WelcomeBannerPhase welcomePhase;
     private List<NamedFactory<UserAuth>> userAuthFactories;
     private List<List<String>> authMethods;
     private String authUserName;
@@ -57,13 +62,16 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
     private int maxAuthRequests;
     private int nbAuthRequests;
 
-    public ServerUserAuthService(Session s) throws SshException {
+    public ServerUserAuthService(Session s) throws IOException {
         ValidateUtils.checkTrue(s instanceof ServerSession, "Server side 
service used on client side");
         if (s.isAuthenticated()) {
             throw new SshException("Session already authenticated");
         }
 
         serverSession = (ServerSession) s;
+        Object phase = PropertyResolverUtils.getObject(s, 
ServerAuthenticationManager.WELCOME_BANNER_PHASE);
+        phase = PropertyResolverUtils.toEnum(WelcomeBannerPhase.class, phase, 
true, WelcomeBannerPhase.VALUES);
+        welcomePhase = (phase == null) ? 
ServerAuthenticationManager.DEFAULT_BANNER_PHASE : (WelcomeBannerPhase) phase;
         maxAuthRequests = PropertyResolverUtils.getIntProperty(s, 
ServerAuthenticationManager.MAX_AUTH_REQUESTS, 
ServerAuthenticationManager.DEFAULT_MAX_AUTH_REQUESTS);
 
         List<NamedFactory<UserAuth>> factories = 
ValidateUtils.checkNotNullAndNotEmpty(
@@ -72,7 +80,7 @@ public class ServerUserAuthService extends AbstractCloseable 
implements Service,
         // Get authentication methods
         authMethods = new ArrayList<>();
 
-        String mths = PropertyResolverUtils.getString(s, 
ServerFactoryManager.AUTH_METHODS);
+        String mths = PropertyResolverUtils.getString(s, 
ServerAuthenticationManager.AUTH_METHODS);
         if (GenericUtils.isEmpty(mths)) {
             for (NamedFactory<UserAuth> uaf : factories) {
                 authMethods.add(new 
ArrayList<>(Collections.singletonList(uaf.getName())));
@@ -101,6 +109,10 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
         }
     }
 
+    public WelcomeBannerPhase getWelcomePhase() {
+        return welcomePhase;
+    }
+
     @Override
     public void start() {
         // do nothing
@@ -122,6 +134,10 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
         ServerSession session = getServerSession();
 
         if (cmd == SshConstants.SSH_MSG_USERAUTH_REQUEST) {
+            if (WelcomeBannerPhase.FIRST_REQUEST.equals(getWelcomePhase())) {
+                sendWelcomeBanner(session);
+            }
+
             if (currentAuth != null) {
                 try {
                     currentAuth.destroy();
@@ -180,6 +196,10 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
                 }
             }
         } else {
+            if (WelcomeBannerPhase.FIRST_AUTHCMD.equals(getWelcomePhase())) {
+                sendWelcomeBanner(session);
+            }
+
             if (this.currentAuth == null) {
                 // This should not happen
                 throw new IllegalStateException("No current authentication 
mechanism for cmd=" + SshConstants.getCommandMessageName(cmd));
@@ -249,34 +269,8 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
                 }
             }
 
-            /*
-             * TODO check if we can send the banner sooner. According to 
RFC-4252 section 5.4:
-             *
-             *      The SSH server may send an SSH_MSG_USERAUTH_BANNER message 
at any
-             *      time after this authentication protocol starts and before
-             *      authentication is successful.  This message contains text 
to be
-             *      displayed to the client user before authentication is 
attempted.
-             */
-            String welcomeBanner = PropertyResolverUtils.getString(session, 
ServerFactoryManager.WELCOME_BANNER);
-            if ((GenericUtils.length(welcomeBanner) > 0)
-                && 
ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(welcomeBanner)) 
{
-                welcomeBanner = KeyRandomArt.combine(' ', 
session.getKeyPairProvider());
-            }
-
-            if (GenericUtils.length(welcomeBanner) > 0) {
-                String lang = PropertyResolverUtils.getStringProperty(session,
-                                        
ServerFactoryManager.WELCOME_BANNER_LANGUAGE,
-                                        
ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
-                buffer = 
session.createBuffer(SshConstants.SSH_MSG_USERAUTH_BANNER,
-                        welcomeBanner.length() + GenericUtils.length(lang) + 
Long.SIZE);
-                buffer.putString(welcomeBanner);
-                buffer.putString(lang);
-
-                if (log.isDebugEnabled()) {
-                    log.debug("handleAuthenticationSuccess({}@{}) send banner 
(length={}, lang={})",
-                              username, session, welcomeBanner.length(), lang);
-                }
-                session.writePacket(buffer);
+            if (WelcomeBannerPhase.POST_SUCCESS.equals(getWelcomePhase())) {
+                sendWelcomeBanner(session);
             }
 
             buffer = 
session.createBuffer(SshConstants.SSH_MSG_USERAUTH_SUCCESS, Byte.SIZE);
@@ -316,8 +310,12 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
     }
 
     protected void handleAuthenticationFailure(int cmd, Buffer buffer) throws 
Exception {
-        String username = (currentAuth == null) ? null : 
currentAuth.getUsername();
         ServerSession session = getServerSession();
+        if (WelcomeBannerPhase.FIRST_FAILURE.equals(getWelcomePhase())) {
+            sendWelcomeBanner(session);
+        }
+
+        String username = (currentAuth == null) ? null : 
currentAuth.getUsername();
         if (log.isDebugEnabled()) {
             log.debug("handleAuthenticationFailure({}@{}) {}",
                       username, session, 
SshConstants.getCommandMessageName(cmd));
@@ -355,6 +353,54 @@ public class ServerUserAuthService extends 
AbstractCloseable implements Service,
         }
     }
 
+    /**
+     * Sends the welcome banner (if any configured) and if not already invoked
+     *
+     * @param session The {@link ServerSession} to send the welcome banner to
+     * @return The sent welcome banner {@link IoWriteFuture} - {@code null} if 
none sent
+     * @throws IOException If failed to send the banner
+     */
+    public IoWriteFuture sendWelcomeBanner(ServerSession session) throws 
IOException {
+        if (welcomeSent.getAndSet(true)) {
+            if (log.isDebugEnabled()) {
+                log.debug("sendWelcomeBanner({}) already sent", session);
+            }
+            return null;
+        }
+
+        String welcomeBanner = PropertyResolverUtils.getString(session, 
ServerAuthenticationManager.WELCOME_BANNER);
+        if ((GenericUtils.length(welcomeBanner) > 0)
+            && 
ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(welcomeBanner))
 {
+            try {
+                welcomeBanner = KeyRandomArt.combine(' ', 
session.getKeyPairProvider());
+            } catch (Exception e) {
+                if (e instanceof IOException) {
+                    throw (IOException) e;
+                }
+
+                throw new IOException(e);
+            }
+        }
+
+        if (GenericUtils.isEmpty(welcomeBanner)) {
+            return null;
+        }
+
+        String lang = PropertyResolverUtils.getStringProperty(session,
+                                
ServerAuthenticationManager.WELCOME_BANNER_LANGUAGE,
+                                
ServerAuthenticationManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
+        Buffer buffer = 
session.createBuffer(SshConstants.SSH_MSG_USERAUTH_BANNER,
+                welcomeBanner.length() + GenericUtils.length(lang) + 
Long.SIZE);
+        buffer.putString(welcomeBanner);
+        buffer.putString(lang);
+
+        if (log.isDebugEnabled()) {
+            log.debug("sendWelcomeBanner({}) send banner (length={}, lang={})",
+                      session, welcomeBanner.length(), lang);
+        }
+        return session.writePacket(buffer);
+    }
+
     public ServerFactoryManager getFactoryManager() {
         return serverSession.getFactoryManager();
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java 
b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
deleted file mode 100644
index 508b224..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.sshd;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.auth.keyboard.UserInteraction;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.config.keys.KeyRandomArt;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.util.test.BaseTestSupport;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class WelcomeBannerTest extends BaseTestSupport {
-    private SshServer sshd;
-    private int port;
-
-    public WelcomeBannerTest() {
-        super();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = setupTestServer();
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (sshd != null) {
-            sshd.stop(true);
-        }
-    }
-
-    @Test
-    public void testSimpleBanner() throws Exception {
-        final String expectedWelcome = "Welcome to SSHD WelcomeBannerTest";
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.WELCOME_BANNER, expectedWelcome);
-        testBanner(expectedWelcome);
-    }
-
-    @Test   // see SSHD-686
-    public void testAutoGeneratedBanner() throws Exception {
-        KeyPairProvider keys = sshd.getKeyPairProvider();
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.WELCOME_BANNER, 
ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE);
-        testBanner(KeyRandomArt.combine(' ', keys));
-    }
-
-    private void testBanner(String expectedWelcome) throws Exception {
-        try (SshClient client = setupTestClient()) {
-            final AtomicReference<String> welcomeHolder = new 
AtomicReference<>(null);
-            final AtomicReference<ClientSession> sessionHolder = new 
AtomicReference<>(null);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public boolean isInteractionAllowed(ClientSession session) {
-                    return true;
-                }
-
-                @Override
-                public void serverVersionInfo(ClientSession session, 
List<String> lines) {
-                    validateSession("serverVersionInfo", session);
-                }
-
-                @Override
-                public void welcome(ClientSession session, String banner, 
String lang) {
-                    validateSession("welcome", session);
-                    assertNull("Multiple banner invocations", 
welcomeHolder.getAndSet(banner));
-                }
-
-                @Override
-                public String[] interactive(ClientSession session, String 
name, String instruction, String lang, String[] prompt, boolean[] echo) {
-                    validateSession("interactive", session);
-                    return null;
-                }
-
-                @Override
-                public String getUpdatedPassword(ClientSession clientSession, 
String prompt, String lang) {
-                    throw new UnsupportedOperationException("Unexpected call");
-                }
-
-                private void validateSession(String phase, ClientSession 
session) {
-                    ClientSession prev = sessionHolder.getAndSet(session);
-                    if (prev != null) {
-                        assertSame("Mismatched " + phase + " client session", 
prev, session);
-                    }
-                }
-            });
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-                assertSame("Mismatched sessions", session, 
sessionHolder.get());
-            } finally {
-                client.stop();
-            }
-
-            assertEquals("Mismatched banner", expectedWelcome, 
welcomeHolder.get());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
index 4879ee5..d83a9b6 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
@@ -19,8 +19,13 @@
 
 package org.apache.sshd.common;
 
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
@@ -202,13 +207,54 @@ public class PropertyResolverUtilsTest extends 
BaseTestSupport {
         }
     }
 
+    @Test
+    public void testToEnumFromString() {
+        Collection<TimeUnit> units = EnumSet.allOf(TimeUnit.class);
+        for (TimeUnit expected : units) {
+            String name = expected.name();
+            for (int index = 1, count = name.length(); index <= count; 
index++) {
+                TimeUnit actual = PropertyResolverUtils.toEnum(TimeUnit.class, 
name, true, units);
+                assertSame("Mismatched instance for name=" + name, expected, 
actual);
+                name = shuffleCase(name);
+            }
+        }
+    }
+
+    @Test
+    public void testToEnumFromEnum() {
+        Collection<TimeUnit> units = EnumSet.allOf(TimeUnit.class);
+        for (TimeUnit expected : units) {
+            TimeUnit actual = PropertyResolverUtils.toEnum(TimeUnit.class, 
expected, true, null);
+            assertSame("Mismatched resolved value", expected, actual);
+        }
+    }
+
+    @Test
+    public void testToEnumFromNonString() {
+        Collection<TimeUnit> units = EnumSet.allOf(TimeUnit.class);
+        for (Object value : new Object[]{this, getClass(), new Date()}) {
+            try {
+                TimeUnit unit = PropertyResolverUtils.toEnum(TimeUnit.class, 
value, false, units);
+                fail("Unexpected success for value=" + value + ": " + unit);
+            } catch (IllegalArgumentException e) {
+               // expected - ignored
+            }
+        }
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testToEnumNoMatchFound() {
+        TimeUnit result = PropertyResolverUtils.toEnum(TimeUnit.class, 
getCurrentTestName(), true, EnumSet.allOf(TimeUnit.class));
+        fail("Unexpected success: " + result);
+    }
+
     private Session createMockSession() {
-        Map<String, Object> managerProps = new TreeMap<String, 
Object>(String.CASE_INSENSITIVE_ORDER);
+        Map<String, Object> managerProps = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         FactoryManager manager = Mockito.mock(FactoryManager.class);
         Mockito.when(manager.getProperties()).thenReturn(managerProps);
         Mockito.when(manager.getParentPropertyResolver()).thenReturn(null);
 
-        Map<String, Object> sessionProps = new TreeMap<String, 
Object>(String.CASE_INSENSITIVE_ORDER);
+        Map<String, Object> sessionProps = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         Session session = Mockito.mock(Session.class);
         Mockito.when(session.getUsername()).thenReturn(getCurrentTestName());
         Mockito.when(session.getFactoryManager()).thenReturn(manager);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
index 8e29598..7fefdb3 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -59,6 +59,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.ServerAuthenticationManager;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
@@ -163,7 +164,7 @@ public class AuthenticationTest extends BaseTestSupport {
             public boolean authenticate(String username, String password, 
ServerSession session)
                     throws PasswordChangeRequiredException {
                 if (attemptsCount.incrementAndGet() == 1) {
-                    throw new 
PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), 
ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
+                    throw new 
PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), 
ServerAuthenticationManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
                 }
 
                 return delegate.authenticate(username, password, session);
@@ -191,7 +192,7 @@ public class AuthenticationTest extends BaseTestSupport {
                 }
             }
         ));
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
 
         try (SshClient client = setupTestClient()) {
             final AtomicInteger updatesCount = new AtomicInteger(0);
@@ -209,7 +210,7 @@ public class AuthenticationTest extends BaseTestSupport {
                 @Override
                 public String getUpdatedPassword(ClientSession session, String 
prompt, String lang) {
                     assertEquals("Mismatched prompt", getCurrentTestName(), 
prompt);
-                    assertEquals("Mismatched language", 
ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
+                    assertEquals("Mismatched language", 
ServerAuthenticationManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
                     assertEquals("Unexpected repeated call", 1, 
updatesCount.incrementAndGet());
                     return getCurrentTestName();
                 }
@@ -235,7 +236,7 @@ public class AuthenticationTest extends BaseTestSupport {
                         };
                     }
             }));
-            PropertyResolverUtils.updateProperty(client, 
ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+            PropertyResolverUtils.updateProperty(client, 
ServerAuthenticationManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
 
             client.start();
 
@@ -396,7 +397,7 @@ public class AuthenticationTest extends BaseTestSupport {
             challenge.addPrompt(prompt, 
(GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
         }
 
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.AUTH_METHODS, 
UserAuthKeyboardInteractiveFactory.NAME);
         final AtomicInteger genCount = new AtomicInteger(0);
         final AtomicInteger authCount = new AtomicInteger(0);
         sshd.setKeyboardInteractiveAuthenticator(new 
KeyboardInteractiveAuthenticator() {
@@ -422,7 +423,7 @@ public class AuthenticationTest extends BaseTestSupport {
                 return true;
             }
         });
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.AUTH_METHODS, 
UserAuthKeyboardInteractiveFactory.NAME);
 
         try (SshClient client = setupTestClient()) {
             final AtomicInteger interactiveCount = new AtomicInteger(0);
@@ -459,7 +460,7 @@ public class AuthenticationTest extends BaseTestSupport {
                     throw new UnsupportedOperationException("Unexpected call");
                 }
             });
-            PropertyResolverUtils.updateProperty(client, 
ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+            PropertyResolverUtils.updateProperty(client, 
ServerAuthenticationManager.AUTH_METHODS, 
UserAuthKeyboardInteractiveFactory.NAME);
 
             client.start();
 
@@ -483,13 +484,13 @@ public class AuthenticationTest extends BaseTestSupport {
             public boolean authenticate(String username, String password, 
ServerSession session)
                     throws PasswordChangeRequiredException {
                 if (attemptsCount.incrementAndGet() == 1) {
-                    throw new 
PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), 
ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
+                    throw new 
PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), 
ServerAuthenticationManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
                 }
 
                 return delegate.authenticate(username, password, session);
             }
         });
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
 
         try (SshClient client = setupTestClient()) {
             final AtomicInteger updatesCount = new AtomicInteger(0);
@@ -507,12 +508,12 @@ public class AuthenticationTest extends BaseTestSupport {
                 @Override
                 public String getUpdatedPassword(ClientSession session, String 
prompt, String lang) {
                     assertEquals("Mismatched prompt", getCurrentTestName(), 
prompt);
-                    assertEquals("Mismatched language", 
ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
+                    assertEquals("Mismatched language", 
ServerAuthenticationManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
                     assertEquals("Unexpected repeated call", 1, 
updatesCount.incrementAndGet());
                     return getCurrentTestName();
                 }
             });
-            PropertyResolverUtils.updateProperty(client, 
ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+            PropertyResolverUtils.updateProperty(client, 
ServerAuthenticationManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
 
             client.start();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/common/auth/SinglePublicKeyAuthTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/SinglePublicKeyAuthTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/SinglePublicKeyAuthTest.java
index d35a5c8..96039bd 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/SinglePublicKeyAuthTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/SinglePublicKeyAuthTest.java
@@ -31,7 +31,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.ServerAuthenticationManager;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.pubkey.CachingPublicKeyAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
@@ -68,7 +68,7 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
     @Before
     public void setUp() throws Exception {
         sshd = setupTestServer();
-        PropertyResolverUtils.updateProperty(sshd, 
ServerFactoryManager.AUTH_METHODS, UserAuthPublicKeyFactory.NAME);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.AUTH_METHODS, UserAuthPublicKeyFactory.NAME);
         sshd.setPublickeyAuthenticator(new PublickeyAuthenticator() {
             @SuppressWarnings("synthetic-access")
             @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
new file mode 100644
index 0000000..2541558
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.sshd.server.auth;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.server.ServerAuthenticationManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see 
https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class WelcomeBannerPhaseTest extends BaseTestSupport {
+    private static SshServer sshd;
+    private static SshClient client;
+    private static int port;
+
+    private WelcomeBannerPhase phase;
+
+    public WelcomeBannerPhaseTest(WelcomeBannerPhase phase) {
+        this.phase = phase;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return parameterize(WelcomeBannerPhase.VALUES);
+    }
+
+    @BeforeClass
+    public static void setupClientAndServer() throws Exception {
+        sshd = Utils.setupTestServer(WelcomeBannerPhaseTest.class);
+        sshd.start();
+        port = sshd.getPort();
+
+        client = Utils.setupTestClient(WelcomeBannerPhaseTest.class);
+        client.start();
+    }
+
+    @AfterClass
+    public static void tearDownClientAndServer() throws Exception {
+        if (sshd != null) {
+            try {
+                sshd.stop(true);
+            } finally {
+                sshd = null;
+            }
+        }
+
+        if (client != null) {
+            try {
+                client.stop();
+            } finally {
+                client = null;
+            }
+        }
+    }
+
+    @Test
+    public void testWelcomeBannerPhase() throws Exception {
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.WELCOME_BANNER_PHASE, phase);
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.WELCOME_BANNER, phase.name());
+
+        AtomicReference<String> welcomeHolder = new AtomicReference<>(null);
+        client.setUserInteraction(new UserInteraction() {
+            @Override
+            public boolean isInteractionAllowed(ClientSession session) {
+                return true;
+            }
+
+            @Override
+            public void welcome(ClientSession session, String banner, String 
lang) {
+                assertNull("Multiple banner invocations", 
welcomeHolder.getAndSet(banner));
+            }
+
+            @Override
+            public String getUpdatedPassword(ClientSession clientSession, 
String prompt, String lang) {
+                throw new UnsupportedOperationException("Unexpected call");
+            }
+
+            @Override
+            public String[] interactive(ClientSession session, String name, 
String instruction, String lang, String[] prompt, boolean[] echo) {
+                return null;
+            }
+        });
+
+
+        try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(5L, TimeUnit.SECONDS);
+        }
+
+        Object banner = welcomeHolder.getAndSet(null);
+        if (WelcomeBannerPhase.NEVER.equals(phase)) {
+            assertNull("Unexpected banner", banner);
+        } else {
+            WelcomeBannerPhase value = 
PropertyResolverUtils.toEnum(WelcomeBannerPhase.class, banner, false, 
WelcomeBannerPhase.VALUES);
+            assertSame("Mismatched banner value", phase, value);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7df59588/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
new file mode 100644
index 0000000..3126b6b
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.sshd.server.auth;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.keys.KeyRandomArt;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.server.ServerAuthenticationManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class WelcomeBannerTest extends BaseTestSupport {
+    private static SshServer sshd;
+    private static int port;
+    private static SshClient client;
+
+    public WelcomeBannerTest() {
+        super();
+    }
+
+
+    @BeforeClass
+    public static void setupClientAndServer() throws Exception {
+        sshd = Utils.setupTestServer(WelcomeBannerPhaseTest.class);
+        sshd.start();
+        port = sshd.getPort();
+
+        client = Utils.setupTestClient(WelcomeBannerPhaseTest.class);
+        client.start();
+    }
+
+    @AfterClass
+    public static void tearDownClientAndServer() throws Exception {
+        if (sshd != null) {
+            try {
+                sshd.stop(true);
+            } finally {
+                sshd = null;
+            }
+        }
+
+        if (client != null) {
+            try {
+                client.stop();
+            } finally {
+                client = null;
+            }
+        }
+    }
+
+    @Test
+    public void testSimpleBanner() throws Exception {
+        final String expectedWelcome = "Welcome to SSHD WelcomeBannerTest";
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.WELCOME_BANNER, expectedWelcome);
+        testBanner(expectedWelcome);
+    }
+
+    @Test   // see SSHD-686
+    public void testAutoGeneratedBanner() throws Exception {
+        KeyPairProvider keys = sshd.getKeyPairProvider();
+        PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.WELCOME_BANNER, 
ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE);
+        testBanner(KeyRandomArt.combine(' ', keys));
+    }
+
+    private void testBanner(String expectedWelcome) throws Exception {
+        AtomicReference<String> welcomeHolder = new AtomicReference<>(null);
+        AtomicReference<ClientSession> sessionHolder = new 
AtomicReference<>(null);
+        client.setUserInteraction(new UserInteraction() {
+            @Override
+            public boolean isInteractionAllowed(ClientSession session) {
+                return true;
+            }
+
+            @Override
+            public void serverVersionInfo(ClientSession session, List<String> 
lines) {
+                validateSession("serverVersionInfo", session);
+            }
+
+            @Override
+            public void welcome(ClientSession session, String banner, String 
lang) {
+                validateSession("welcome", session);
+                assertNull("Multiple banner invocations", 
welcomeHolder.getAndSet(banner));
+            }
+
+            @Override
+            public String[] interactive(ClientSession session, String name, 
String instruction, String lang, String[] prompt, boolean[] echo) {
+                validateSession("interactive", session);
+                return null;
+            }
+
+            @Override
+            public String getUpdatedPassword(ClientSession clientSession, 
String prompt, String lang) {
+                throw new UnsupportedOperationException("Unexpected call");
+            }
+
+            private void validateSession(String phase, ClientSession session) {
+                ClientSession prev = sessionHolder.getAndSet(session);
+                if (prev != null) {
+                    assertSame("Mismatched " + phase + " client session", 
prev, session);
+                }
+            }
+        });
+
+        try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(5L, TimeUnit.SECONDS);
+            assertSame("Mismatched sessions", session, sessionHolder.get());
+        }
+
+        assertEquals("Mismatched banner", expectedWelcome, 
welcomeHolder.get());
+    }
+}

Reply via email to