mina-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lgoldst...@apache.org
Subject mina-sshd git commit: [SSHD-686] Provide configurable option to auto-generate welcome banner consisting of the server's keys random art values
Date Wed, 03 Aug 2016 17:11:52 GMT
Repository: mina-sshd
Updated Branches:
  refs/heads/master 9d34cd6e8 -> 5a133292b


[SSHD-686] Provide configurable option to auto-generate welcome banner consisting of the server's
keys random art values


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

Branch: refs/heads/master
Commit: 5a133292b7701dd06a6e5f56766c4df59d2097ce
Parents: 9d34cd6
Author: Lyor Goldstein <lyor.goldstein@gmail.com>
Authored: Wed Aug 3 20:13:20 2016 +0300
Committer: Lyor Goldstein <lyor.goldstein@gmail.com>
Committed: Wed Aug 3 20:13:20 2016 +0300

----------------------------------------------------------------------
 .../sshd/common/config/keys/KeyRandomArt.java   | 310 -------------------
 .../common/config/keys/KeyRandomArtTest.java    | 107 -------
 .../sshd/common/config/SshConfigFileReader.java |   2 +
 .../sshd/common/config/keys/KeyRandomArt.java   | 310 +++++++++++++++++++
 .../sshd/server/ServerFactoryManager.java       |   8 +
 .../java/org/apache/sshd/server/SshServer.java  |  55 ++--
 .../server/session/ServerUserAuthService.java   |   6 +
 .../java/org/apache/sshd/WelcomeBannerTest.java |  24 +-
 .../common/config/keys/KeyRandomArtTest.java    | 107 +++++++
 9 files changed, 484 insertions(+), 445 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
deleted file mode 100644
index 1d775d2..0000000
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
+++ /dev/null
@@ -1,310 +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.common.config.keys;
-
-import java.io.IOException;
-import java.io.StreamCorruptedException;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.sshd.common.Factory;
-import org.apache.sshd.common.digest.Digest;
-import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * Draw an ASCII-Art representing the fingerprint so human brain can
- * profit from its built-in pattern recognition ability.
- * This technique is called "random art" and can be found in some
- * scientific publications like this original paper:
- *
- * &quot;Hash Visualization: a New Technique to improve Real-World Security&quot;,
- * Perrig A. and Song D., 1999, International Workshop on Cryptographic
- * Techniques and E-Commerce (CrypTEC '99)
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf">Original
article</a>
- * @see <a href="http://opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c">C
implementation</a>
- */
-public class KeyRandomArt {
-    public static final int FLDBASE = 8;
-    public static final int FLDSIZE_Y = FLDBASE + 1;
-    public static final int FLDSIZE_X = FLDBASE * 2 + 1;
-    public static final String AUGMENTATION_STRING = " .o+=*BOX@%&#/^SE";
-
-    private final String algorithm;
-    private final int keySize;
-    private final char[][] field = new char[FLDSIZE_X][FLDSIZE_Y];
-
-    public KeyRandomArt(PublicKey key) throws Exception {
-        this(key, KeyUtils.getDefaultFingerPrintFactory());
-    }
-
-    public KeyRandomArt(PublicKey key, Factory<? extends Digest> f) throws Exception
{
-        this(key, Objects.requireNonNull(f, "No digest factory").create());
-    }
-
-    public KeyRandomArt(PublicKey key, Digest d) throws Exception {
-        this(Objects.requireNonNull(key, "No key provided").getAlgorithm(),
-             KeyUtils.getKeySize(key),
-             KeyUtils.getRawFingerprint(Objects.requireNonNull(d, "No key digest"), key));
-    }
-
-    /**
-     * @param algorithm The key algorithm
-     * @param keySize The key size in bits
-     * @param digest The key digest
-     */
-    public KeyRandomArt(String algorithm, int keySize, byte[] digest) {
-        this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm provided");
-        ValidateUtils.checkTrue(keySize > 0, "Invalid key size: %d", keySize);
-        this.keySize = keySize;
-        Objects.requireNonNull(digest, "No key digest provided");
-
-        int x = FLDSIZE_X / 2;
-        int y = FLDSIZE_Y / 2;
-        int len = AUGMENTATION_STRING.length() - 1;
-        for (int i = 0; i < digest.length; i++) {
-            /* each byte conveys four 2-bit move commands */
-            int input = digest[i] & 0xFF;
-            for (int b = 0; b < 4; b++) {
-                /* evaluate 2 bit, rest is shifted later */
-                x += ((input & 0x1) != 0) ? 1 : -1;
-                y += ((input & 0x2) != 0) ? 1 : -1;
-
-                /* assure we are still in bounds */
-                x = Math.max(x, 0);
-                y = Math.max(y, 0);
-                x = Math.min(x, FLDSIZE_X - 1);
-                y = Math.min(y, FLDSIZE_Y - 1);
-
-                /* augment the field */
-                if (field[x][y] < (len - 2)) {
-                    field[x][y]++;
-                }
-                input = input >> 2;
-            }
-        }
-
-        /* mark starting point and end point*/
-        field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = (char) (len - 1);
-        field[x][y] = (char) len;
-    }
-
-    public String getAlgorithm() {
-        return algorithm;
-    }
-
-    public int getKeySize() {
-        return keySize;
-    }
-
-    /**
-     * Outputs the generated random art
-     *
-     * @param <A> The {@link Appendable} output writer
-     * @param sb The writer
-     * @return The updated writer instance
-     * @throws IOException If failed to write the combined result
-     */
-    public <A extends Appendable> A append(A sb) throws IOException {
-        // Upper border
-        String s = String.format("+--[%4s %4d]", getAlgorithm(), getKeySize());
-        sb.append(s);
-        for (int index = s.length(); index <= FLDSIZE_X; index++) {
-            sb.append('-');
-        }
-        sb.append('+');
-        sb.append('\n');
-
-        // contents
-        int len = AUGMENTATION_STRING.length() - 1;
-        for (int y = 0; y < FLDSIZE_Y; y++) {
-            sb.append('|');
-            for (int x = 0; x < FLDSIZE_X; x++) {
-                char ch = field[x][y];
-                sb.append(AUGMENTATION_STRING.charAt(Math.min(ch, len)));
-            }
-            sb.append('|');
-            sb.append('\n');
-        }
-
-        // lower border
-        sb.append('+');
-        for (int index = 0; index < FLDSIZE_X; index++) {
-            sb.append('-');
-        }
-
-        sb.append('+');
-        sb.append('\n');
-        return sb;
-    }
-
-    @Override
-    public String toString() {
-        try {
-            return append(new StringBuilder((FLDSIZE_X + 4) * (FLDSIZE_Y + 3))).toString();
-        } catch (IOException e) {
-            return e.getClass().getSimpleName();    // unexpected
-        }
-    }
-
-    /**
-     * Combines the arts in a user-friendly way so they are aligned with each other
-     *
-     * @param separator The separator to use between the arts - if empty char
-     * ('\0') then no separation is done
-     * @param arts The {@link KeyRandomArt}s to combine - ignored if {@code null}/empty
-     * @return The combined result
-     */
-    public static String combine(char separator, Collection<? extends KeyRandomArt>
arts) {
-        if (GenericUtils.isEmpty(arts)) {
-            return "";
-        }
-
-        try {
-            return combine(new StringBuilder(arts.size() * (FLDSIZE_X + 4) * (FLDSIZE_Y +
3)), separator, arts).toString();
-        } catch (IOException e) {
-            return e.getClass().getSimpleName();    // unexpected
-        }
-    }
-
-    /**
-     * Creates the combined representation of the random art entries for the provided keys
-     *
-     * @param separator The separator to use between the arts - if empty char
-     * ('\0') then no separation is done
-     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
-     * or has no keys to provide
-     * @return The combined representation
-     * @throws Exception If failed to extract or combine the entries
-     * @see #combine(Appendable, char, KeyIdentityProvider)
-     */
-    public static String combine(char separator, KeyIdentityProvider provider) throws Exception
{
-        return combine(new StringBuilder(4 * (FLDSIZE_X + 4) * (FLDSIZE_Y + 3)), separator,
provider).toString();
-    }
-
-    /**
-     * Appends the combined random art entries for the provided keys
-     *
-     * @param <A> The {@link Appendable} output writer
-     * @param sb The writer
-     * @param separator The separator to use between the arts - if empty char
-     * ('\0') then no separation is done
-     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
-     * or has no keys to provide
-     * @return The updated writer instance
-     * @throws Exception If failed to extract or write the entries
-     * @see #generate(KeyIdentityProvider)
-     * @see #combine(Appendable, char, Collection)
-     */
-    public static <A extends Appendable> A combine(A sb, char separator, KeyIdentityProvider
provider) throws Exception {
-        return combine(sb, separator, generate(provider));
-    }
-
-    /**
-     * Extracts and generates random art entries for all key in the provider
-     *
-     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
-     * or has no keys to provide
-     * @return The extracted {@link KeyRandomArt}s
-     * @throws Exception If failed to extract the entries
-     * @see KeyIdentityProvider#loadKeys()
-     */
-    public static Collection<KeyRandomArt> generate(KeyIdentityProvider provider) throws
Exception {
-        Iterable<KeyPair> keys = (provider == null) ? null : provider.loadKeys();
-        Iterator<KeyPair> iter = (keys == null) ? null : keys.iterator();
-        if ((iter == null) || (!iter.hasNext())) {
-            return Collections.emptyList();
-        }
-
-        Collection<KeyRandomArt> arts = new LinkedList<>();
-        do {
-            KeyPair kp = iter.next();
-            KeyRandomArt a = new KeyRandomArt(kp.getPublic());
-            arts.add(a);
-        } while(iter.hasNext());
-
-        return arts;
-    }
-
-    /**
-     * Combines the arts in a user-friendly way so they are aligned with each other
-     *
-     * @param <A> The {@link Appendable} output writer
-     * @param sb The writer
-     * @param separator The separator to use between the arts - if empty char
-     * ('\0') then no separation is done
-     * @param arts The {@link KeyRandomArt}s to combine - ignored if {@code null}/empty
-     * @return The updated writer instance
-     * @throws IOException If failed to write the combined result
-     */
-    public static <A extends Appendable> A combine(A sb, char separator, Collection<?
extends KeyRandomArt> arts) throws IOException {
-        if (GenericUtils.isEmpty(arts)) {
-            return sb;
-        }
-
-        List<String[]> allLines = new ArrayList<>(arts.size());
-        int numLines = -1;
-        for (KeyRandomArt a : arts) {
-            String s = a.toString();
-            String[] lines = GenericUtils.split(s, '\n');
-            if (numLines <= 0) {
-                numLines = lines.length;
-            } else {
-                if (numLines != lines.length) {
-                    throw new StreamCorruptedException("Mismatched lines count: expected="
+ numLines + ", actual=" + lines.length);
-                }
-            }
-
-            for (int index = 0; index < lines.length; index++) {
-                String l = lines[index];
-                if ((l.length() > 0) && (l.charAt(l.length() - 1) == '\r')) {
-                    l = l.substring(0, l.length() - 1);
-                    lines[index] = l;
-                }
-            }
-
-            allLines.add(lines);
-        }
-
-        for (int row = 0; row < numLines; row++) {
-            for (int index = 0; index < allLines.size(); index++) {
-                String[] lines = allLines.get(index);
-                String l = lines[row];
-                sb.append(l);
-                if ((index > 0) && (separator != '\0')) {
-                    sb.append(separator);
-                }
-            }
-            sb.append('\n');
-        }
-
-        return sb;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
deleted file mode 100644
index a0c472c..0000000
--- a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
+++ /dev/null
@@ -1,107 +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.common.config.keys;
-
-import java.security.KeyPair;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.sshd.common.cipher.ECCurves;
-import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.util.test.BaseTestSupport;
-import org.apache.sshd.util.test.Utils;
-import org.junit.AfterClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
-public class KeyRandomArtTest extends BaseTestSupport {
-    private static final Collection<KeyPair> KEYS = new LinkedList<>();
-
-    private final String algorithm;
-    private final int keySize;
-    private final KeyPair keyPair;
-
-    public KeyRandomArtTest(String algorithm, int keySize) throws Exception {
-        this.algorithm = algorithm;
-        this.keySize = keySize;
-        this.keyPair = Utils.generateKeyPair(algorithm, keySize);
-        KEYS.add(this.keyPair);
-    }
-
-    @Parameters(name = "algorithm={0}, key-size={1}")
-    public static List<Object[]> parameters() {
-        List<Object[]> params = new ArrayList<>();
-        for (int keySize : new int[]{1024, 2048, 4096}) {
-            params.add(new Object[]{KeyUtils.RSA_ALGORITHM, keySize});
-        }
-
-        for (int keySize : new int[]{512, 1024}) {
-            params.add(new Object[]{KeyUtils.DSS_ALGORITHM, keySize});
-        }
-
-        for (ECCurves curve : ECCurves.VALUES) {
-            params.add(new Object[]{KeyUtils.EC_ALGORITHM, curve.getKeySize()});
-        }
-
-        return params;
-    }
-
-    @AfterClass
-    public static void dumpAllArts() throws Exception {
-        KeyRandomArt.combine(System.out, ' ', new KeyIdentityProvider() {
-            @Override
-            @SuppressWarnings("synthetic-access")
-            public Iterable<KeyPair> loadKeys() {
-                return KEYS;
-            }
-        });
-    }
-
-    @Test
-    public void testRandomArtString() throws Exception {
-        KeyRandomArt art = new KeyRandomArt(keyPair.getPublic());
-        assertEquals("Mismatched algorithem", algorithm, art.getAlgorithm());
-        assertEquals("Mismatched key size", keySize, art.getKeySize());
-
-        String s = art.toString();
-        String[] lines = GenericUtils.split(s, '\n');
-        assertEquals("Mismatched lines count", KeyRandomArt.FLDSIZE_Y + 2, lines.length);
-
-        for (int index = 0; index < lines.length; index++) {
-            String l = lines[index];
-            if ((l.length() > 0) && (l.charAt(l.length() - 1) == '\r')) {
-                l = l.substring(0, l.length() - 1);
-                lines[index] = l;
-            }
-            System.out.append('\t').println(l);
-
-            assertTrue("Mismatched line length #" + (index + 1) + ": " + l.length(), l.length()
>= (KeyRandomArt.FLDSIZE_X + 2));
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
index 211b1fb..4b08beb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
@@ -79,6 +79,8 @@ public final class SshConfigFileReader {
 
     // Some well known configuration properties names and values
     public static final String BANNER_CONFIG_PROP = "Banner";
+    public static final String VISUAL_HOST_KEY = "VisualHostKey";
+    public static final String DEFAULT_VISUAL_HOST_KEY = "no";
     public static final String COMPRESSION_PROP = "Compression";
     public static final String DEFAULT_COMPRESSION = CompressionConfigValue.NO.getName();
     public static final String ALLOW_TCP_FORWARDING_CONFIG_PROP = "AllowTcpForwarding";

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
new file mode 100644
index 0000000..1d775d2
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
@@ -0,0 +1,310 @@
+/*
+ * 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.common.config.keys;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.digest.Digest;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Draw an ASCII-Art representing the fingerprint so human brain can
+ * profit from its built-in pattern recognition ability.
+ * This technique is called "random art" and can be found in some
+ * scientific publications like this original paper:
+ *
+ * &quot;Hash Visualization: a New Technique to improve Real-World Security&quot;,
+ * Perrig A. and Song D., 1999, International Workshop on Cryptographic
+ * Techniques and E-Commerce (CrypTEC '99)
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf">Original
article</a>
+ * @see <a href="http://opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c">C
implementation</a>
+ */
+public class KeyRandomArt {
+    public static final int FLDBASE = 8;
+    public static final int FLDSIZE_Y = FLDBASE + 1;
+    public static final int FLDSIZE_X = FLDBASE * 2 + 1;
+    public static final String AUGMENTATION_STRING = " .o+=*BOX@%&#/^SE";
+
+    private final String algorithm;
+    private final int keySize;
+    private final char[][] field = new char[FLDSIZE_X][FLDSIZE_Y];
+
+    public KeyRandomArt(PublicKey key) throws Exception {
+        this(key, KeyUtils.getDefaultFingerPrintFactory());
+    }
+
+    public KeyRandomArt(PublicKey key, Factory<? extends Digest> f) throws Exception
{
+        this(key, Objects.requireNonNull(f, "No digest factory").create());
+    }
+
+    public KeyRandomArt(PublicKey key, Digest d) throws Exception {
+        this(Objects.requireNonNull(key, "No key provided").getAlgorithm(),
+             KeyUtils.getKeySize(key),
+             KeyUtils.getRawFingerprint(Objects.requireNonNull(d, "No key digest"), key));
+    }
+
+    /**
+     * @param algorithm The key algorithm
+     * @param keySize The key size in bits
+     * @param digest The key digest
+     */
+    public KeyRandomArt(String algorithm, int keySize, byte[] digest) {
+        this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm provided");
+        ValidateUtils.checkTrue(keySize > 0, "Invalid key size: %d", keySize);
+        this.keySize = keySize;
+        Objects.requireNonNull(digest, "No key digest provided");
+
+        int x = FLDSIZE_X / 2;
+        int y = FLDSIZE_Y / 2;
+        int len = AUGMENTATION_STRING.length() - 1;
+        for (int i = 0; i < digest.length; i++) {
+            /* each byte conveys four 2-bit move commands */
+            int input = digest[i] & 0xFF;
+            for (int b = 0; b < 4; b++) {
+                /* evaluate 2 bit, rest is shifted later */
+                x += ((input & 0x1) != 0) ? 1 : -1;
+                y += ((input & 0x2) != 0) ? 1 : -1;
+
+                /* assure we are still in bounds */
+                x = Math.max(x, 0);
+                y = Math.max(y, 0);
+                x = Math.min(x, FLDSIZE_X - 1);
+                y = Math.min(y, FLDSIZE_Y - 1);
+
+                /* augment the field */
+                if (field[x][y] < (len - 2)) {
+                    field[x][y]++;
+                }
+                input = input >> 2;
+            }
+        }
+
+        /* mark starting point and end point*/
+        field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = (char) (len - 1);
+        field[x][y] = (char) len;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public int getKeySize() {
+        return keySize;
+    }
+
+    /**
+     * Outputs the generated random art
+     *
+     * @param <A> The {@link Appendable} output writer
+     * @param sb The writer
+     * @return The updated writer instance
+     * @throws IOException If failed to write the combined result
+     */
+    public <A extends Appendable> A append(A sb) throws IOException {
+        // Upper border
+        String s = String.format("+--[%4s %4d]", getAlgorithm(), getKeySize());
+        sb.append(s);
+        for (int index = s.length(); index <= FLDSIZE_X; index++) {
+            sb.append('-');
+        }
+        sb.append('+');
+        sb.append('\n');
+
+        // contents
+        int len = AUGMENTATION_STRING.length() - 1;
+        for (int y = 0; y < FLDSIZE_Y; y++) {
+            sb.append('|');
+            for (int x = 0; x < FLDSIZE_X; x++) {
+                char ch = field[x][y];
+                sb.append(AUGMENTATION_STRING.charAt(Math.min(ch, len)));
+            }
+            sb.append('|');
+            sb.append('\n');
+        }
+
+        // lower border
+        sb.append('+');
+        for (int index = 0; index < FLDSIZE_X; index++) {
+            sb.append('-');
+        }
+
+        sb.append('+');
+        sb.append('\n');
+        return sb;
+    }
+
+    @Override
+    public String toString() {
+        try {
+            return append(new StringBuilder((FLDSIZE_X + 4) * (FLDSIZE_Y + 3))).toString();
+        } catch (IOException e) {
+            return e.getClass().getSimpleName();    // unexpected
+        }
+    }
+
+    /**
+     * Combines the arts in a user-friendly way so they are aligned with each other
+     *
+     * @param separator The separator to use between the arts - if empty char
+     * ('\0') then no separation is done
+     * @param arts The {@link KeyRandomArt}s to combine - ignored if {@code null}/empty
+     * @return The combined result
+     */
+    public static String combine(char separator, Collection<? extends KeyRandomArt>
arts) {
+        if (GenericUtils.isEmpty(arts)) {
+            return "";
+        }
+
+        try {
+            return combine(new StringBuilder(arts.size() * (FLDSIZE_X + 4) * (FLDSIZE_Y +
3)), separator, arts).toString();
+        } catch (IOException e) {
+            return e.getClass().getSimpleName();    // unexpected
+        }
+    }
+
+    /**
+     * Creates the combined representation of the random art entries for the provided keys
+     *
+     * @param separator The separator to use between the arts - if empty char
+     * ('\0') then no separation is done
+     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
+     * or has no keys to provide
+     * @return The combined representation
+     * @throws Exception If failed to extract or combine the entries
+     * @see #combine(Appendable, char, KeyIdentityProvider)
+     */
+    public static String combine(char separator, KeyIdentityProvider provider) throws Exception
{
+        return combine(new StringBuilder(4 * (FLDSIZE_X + 4) * (FLDSIZE_Y + 3)), separator,
provider).toString();
+    }
+
+    /**
+     * Appends the combined random art entries for the provided keys
+     *
+     * @param <A> The {@link Appendable} output writer
+     * @param sb The writer
+     * @param separator The separator to use between the arts - if empty char
+     * ('\0') then no separation is done
+     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
+     * or has no keys to provide
+     * @return The updated writer instance
+     * @throws Exception If failed to extract or write the entries
+     * @see #generate(KeyIdentityProvider)
+     * @see #combine(Appendable, char, Collection)
+     */
+    public static <A extends Appendable> A combine(A sb, char separator, KeyIdentityProvider
provider) throws Exception {
+        return combine(sb, separator, generate(provider));
+    }
+
+    /**
+     * Extracts and generates random art entries for all key in the provider
+     *
+     * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
+     * or has no keys to provide
+     * @return The extracted {@link KeyRandomArt}s
+     * @throws Exception If failed to extract the entries
+     * @see KeyIdentityProvider#loadKeys()
+     */
+    public static Collection<KeyRandomArt> generate(KeyIdentityProvider provider) throws
Exception {
+        Iterable<KeyPair> keys = (provider == null) ? null : provider.loadKeys();
+        Iterator<KeyPair> iter = (keys == null) ? null : keys.iterator();
+        if ((iter == null) || (!iter.hasNext())) {
+            return Collections.emptyList();
+        }
+
+        Collection<KeyRandomArt> arts = new LinkedList<>();
+        do {
+            KeyPair kp = iter.next();
+            KeyRandomArt a = new KeyRandomArt(kp.getPublic());
+            arts.add(a);
+        } while(iter.hasNext());
+
+        return arts;
+    }
+
+    /**
+     * Combines the arts in a user-friendly way so they are aligned with each other
+     *
+     * @param <A> The {@link Appendable} output writer
+     * @param sb The writer
+     * @param separator The separator to use between the arts - if empty char
+     * ('\0') then no separation is done
+     * @param arts The {@link KeyRandomArt}s to combine - ignored if {@code null}/empty
+     * @return The updated writer instance
+     * @throws IOException If failed to write the combined result
+     */
+    public static <A extends Appendable> A combine(A sb, char separator, Collection<?
extends KeyRandomArt> arts) throws IOException {
+        if (GenericUtils.isEmpty(arts)) {
+            return sb;
+        }
+
+        List<String[]> allLines = new ArrayList<>(arts.size());
+        int numLines = -1;
+        for (KeyRandomArt a : arts) {
+            String s = a.toString();
+            String[] lines = GenericUtils.split(s, '\n');
+            if (numLines <= 0) {
+                numLines = lines.length;
+            } else {
+                if (numLines != lines.length) {
+                    throw new StreamCorruptedException("Mismatched lines count: expected="
+ numLines + ", actual=" + lines.length);
+                }
+            }
+
+            for (int index = 0; index < lines.length; index++) {
+                String l = lines[index];
+                if ((l.length() > 0) && (l.charAt(l.length() - 1) == '\r')) {
+                    l = l.substring(0, l.length() - 1);
+                    lines[index] = l;
+                }
+            }
+
+            allLines.add(lines);
+        }
+
+        for (int row = 0; row < numLines; row++) {
+            for (int index = 0; index < allLines.size(); index++) {
+                String[] lines = allLines.get(index);
+                String l = lines[row];
+                sb.append(l);
+                if ((index > 0) && (separator != '\0')) {
+                    sb.append(separator);
+                }
+            }
+            sb.append('\n');
+        }
+
+        return sb;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/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 12d65c6..0a488e8 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
@@ -75,6 +75,14 @@ public interface ServerFactoryManager
     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

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/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 b350b21..35757c1 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
@@ -18,15 +18,12 @@
  */
 package org.apache.sshd.server;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketTimeoutException;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -48,6 +45,7 @@ import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.KeyRandomArt;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.helpers.AbstractFactoryManager;
 import org.apache.sshd.common.io.IoAcceptor;
@@ -582,11 +580,12 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         SshServer sshd = SshServer.setUpDefaultServer();
         Map<String, Object> props = sshd.getProperties();
         props.putAll(options);
-        setupServerBanner(sshd, options);
-        sshd.setPort(port);
 
         KeyPairProvider hostKeyProvider = setupServerKeys(sshd, hostKeyType, hostKeySize,
keyFiles);
         sshd.setKeyPairProvider(hostKeyProvider);
+        // Should come AFTER key pair provider setup so auto-welcome can be generated
+        setupServerBanner(sshd, options);
+        sshd.setPort(port);
 
         sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
         sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
@@ -609,31 +608,43 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         Thread.sleep(Long.MAX_VALUE);
     }
 
-    public static String setupServerBanner(ServerFactoryManager server, Map<String, ?>
options) throws IOException {
-        String filePath = GenericUtils.isEmpty(options) ? null : Objects.toString(options.remove(SshConfigFileReader.BANNER_CONFIG_PROP),
null);
-        if (GenericUtils.length(filePath) > 0) {
-            if ("none".equals(filePath)) {
+    public static String setupServerBanner(ServerFactoryManager server, Map<String, ?>
options) throws Exception {
+        String bannerOption = GenericUtils.isEmpty(options)
+                ? null
+                : Objects.toString(options.remove(SshConfigFileReader.BANNER_CONFIG_PROP),
null);
+        if (GenericUtils.isEmpty(bannerOption)) {
+            bannerOption = GenericUtils.isEmpty(options)
+                    ? null
+                    : Objects.toString(options.remove(SshConfigFileReader.VISUAL_HOST_KEY),
null);
+            if (SshConfigFileReader.parseBooleanValue(bannerOption)) {
+                bannerOption = ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE;
+            }
+        }
+
+        String banner;
+        if (GenericUtils.length(bannerOption) > 0) {
+            if ("none".equals(bannerOption)) {
                 return null;
             }
 
-            Path path = Paths.get(filePath);
-            long fileSize = Files.size(path);
-            ValidateUtils.checkTrue(fileSize > 0L, "No banner contents in file=%s", filePath);
+            if (ServerFactoryManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(bannerOption))
{
+                banner = KeyRandomArt.combine(' ', server.getKeyPairProvider());
+            } else {
+                Path path = Paths.get(bannerOption);
+                long fileSize = Files.size(path);
+                ValidateUtils.checkTrue(fileSize > 0L, "No banner contents in file=%s",
bannerOption);
 
-            StringBuilder sb = new StringBuilder((int) fileSize + Long.SIZE);
-            try (BufferedReader rdr = new BufferedReader(new InputStreamReader(Files.newInputStream(path),
StandardCharsets.UTF_8))) {
-                for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
-                    sb.append(line).append('\n');
-                }
+                List<String> lines = Files.readAllLines(path);
+                banner = GenericUtils.join(lines, '\n');
             }
-
-            PropertyResolverUtils.updateProperty(server, ServerFactoryManager.WELCOME_BANNER,
sb.toString());
         } else {
-            PropertyResolverUtils.updateProperty(server, ServerFactoryManager.WELCOME_BANNER,
"Welcome to SSHD\n");
+            banner = "Welcome to SSHD\n";
         }
 
-        return PropertyResolverUtils.getString(server, ServerFactoryManager.WELCOME_BANNER);
+        if (GenericUtils.length(banner) > 0) {
+            PropertyResolverUtils.updateProperty(server, ServerFactoryManager.WELCOME_BANNER,
banner);
+        }
 
+        return PropertyResolverUtils.getString(server, ServerFactoryManager.WELCOME_BANNER);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/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 319e42c..0e0186e 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
@@ -30,6 +30,7 @@ import org.apache.sshd.common.PropertyResolverUtils;
 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.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -257,6 +258,11 @@ public class ServerUserAuthService extends AbstractCloseable implements
Service,
              *      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,

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/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
index 13b9375..508b224 100644
--- a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
@@ -26,6 +26,8 @@ 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;
@@ -37,9 +39,6 @@ import org.junit.runners.MethodSorters;
 
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class WelcomeBannerTest extends BaseTestSupport {
-
-    private static final String WELCOME = "Welcome to SSHD WelcomeBannerTest";
-
     private SshServer sshd;
     private int port;
 
@@ -50,7 +49,6 @@ public class WelcomeBannerTest extends BaseTestSupport {
     @Before
     public void setUp() throws Exception {
         sshd = setupTestServer();
-        PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
         sshd.start();
         port = sshd.getPort();
     }
@@ -63,7 +61,20 @@ public class WelcomeBannerTest extends BaseTestSupport {
     }
 
     @Test
-    public void testBanner() throws Exception {
+    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);
@@ -108,10 +119,11 @@ public class WelcomeBannerTest extends BaseTestSupport {
                 session.addPasswordIdentity(getCurrentTestName());
                 session.auth().verify(5L, TimeUnit.SECONDS);
                 assertSame("Mismatched sessions", session, sessionHolder.get());
-                assertEquals("Mismatched banner", WELCOME, welcomeHolder.get());
             } finally {
                 client.stop();
             }
+
+            assertEquals("Mismatched banner", expectedWelcome, welcomeHolder.get());
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5a133292/sshd-core/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
new file mode 100644
index 0000000..a0c472c
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.common.config.keys;
+
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class KeyRandomArtTest extends BaseTestSupport {
+    private static final Collection<KeyPair> KEYS = new LinkedList<>();
+
+    private final String algorithm;
+    private final int keySize;
+    private final KeyPair keyPair;
+
+    public KeyRandomArtTest(String algorithm, int keySize) throws Exception {
+        this.algorithm = algorithm;
+        this.keySize = keySize;
+        this.keyPair = Utils.generateKeyPair(algorithm, keySize);
+        KEYS.add(this.keyPair);
+    }
+
+    @Parameters(name = "algorithm={0}, key-size={1}")
+    public static List<Object[]> parameters() {
+        List<Object[]> params = new ArrayList<>();
+        for (int keySize : new int[]{1024, 2048, 4096}) {
+            params.add(new Object[]{KeyUtils.RSA_ALGORITHM, keySize});
+        }
+
+        for (int keySize : new int[]{512, 1024}) {
+            params.add(new Object[]{KeyUtils.DSS_ALGORITHM, keySize});
+        }
+
+        for (ECCurves curve : ECCurves.VALUES) {
+            params.add(new Object[]{KeyUtils.EC_ALGORITHM, curve.getKeySize()});
+        }
+
+        return params;
+    }
+
+    @AfterClass
+    public static void dumpAllArts() throws Exception {
+        KeyRandomArt.combine(System.out, ' ', new KeyIdentityProvider() {
+            @Override
+            @SuppressWarnings("synthetic-access")
+            public Iterable<KeyPair> loadKeys() {
+                return KEYS;
+            }
+        });
+    }
+
+    @Test
+    public void testRandomArtString() throws Exception {
+        KeyRandomArt art = new KeyRandomArt(keyPair.getPublic());
+        assertEquals("Mismatched algorithem", algorithm, art.getAlgorithm());
+        assertEquals("Mismatched key size", keySize, art.getKeySize());
+
+        String s = art.toString();
+        String[] lines = GenericUtils.split(s, '\n');
+        assertEquals("Mismatched lines count", KeyRandomArt.FLDSIZE_Y + 2, lines.length);
+
+        for (int index = 0; index < lines.length; index++) {
+            String l = lines[index];
+            if ((l.length() > 0) && (l.charAt(l.length() - 1) == '\r')) {
+                l = l.substring(0, l.length() - 1);
+                lines[index] = l;
+            }
+            System.out.append('\t').println(l);
+
+            assertTrue("Mismatched line length #" + (index + 1) + ": " + l.length(), l.length()
>= (KeyRandomArt.FLDSIZE_X + 2));
+        }
+    }
+}


Mime
View raw message