You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:47 UTC

[37/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
new file mode 100644
index 0000000..8b4e9ba
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
@@ -0,0 +1,380 @@
+/*
+ * 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.util.io;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+
+/**
+ * <p>Class for scanning a directory for files/directories which match certain
+ * criteria.</p>
+ *
+ * <p>These criteria consist of selectors and patterns which have been specified.
+ * With the selectors you can select which files you want to have included.
+ * Files which are not selected are excluded. With patterns you can include
+ * or exclude files based on their filename.</p>
+ *
+ * <p>The idea is simple. A given directory is recursively scanned for all files
+ * and directories. Each file/directory is matched against a set of selectors,
+ * including special support for matching against filenames with include and
+ * and exclude patterns. Only files/directories which match at least one
+ * pattern of the include pattern list or other file selector, and don't match
+ * any pattern of the exclude pattern list or fail to match against a required
+ * selector will be placed in the list of files/directories found.</p>
+ *
+ * <p>When no list of include patterns is supplied, "**" will be used, which
+ * means that everything will be matched. When no list of exclude patterns is
+ * supplied, an empty list is used, such that nothing will be excluded. When
+ * no selectors are supplied, none are applied.</p>
+ *
+ * <p>The filename pattern matching is done as follows:
+ * The name to be matched is split up in path segments. A path segment is the
+ * name of a directory or file, which is bounded by
+ * <code>File.separator</code> ('/' under UNIX, '\' under Windows).
+ * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
+ * "def","ghi" and "xyz.java".
+ * The same is done for the pattern against which should be matched.</p>
+ *
+ * <p>The segments of the name and the pattern are then matched against each
+ * other. When '**' is used for a path segment in the pattern, it matches
+ * zero or more path segments of the name.</p>
+ *
+ * <p>There is a special case regarding the use of <code>File.separator</code>s
+ * at the beginning of the pattern and the string to match:<br>
+ * When a pattern starts with a <code>File.separator</code>, the string
+ * to match must also start with a <code>File.separator</code>.
+ * When a pattern does not start with a <code>File.separator</code>, the
+ * string to match may not start with a <code>File.separator</code>.
+ * When one of these rules is not obeyed, the string will not
+ * match.</p>
+ *
+ * <p>When a name path segment is matched against a pattern path segment, the
+ * following special characters can be used:<br>
+ * '*' matches zero or more characters<br>
+ * '?' matches one character.</p>
+ *
+ * <p>Examples:
+ * <br>
+ * <code>"**\*.class"</code> matches all <code>.class</code> files/dirs in a directory tree.
+ * <br>
+ * <code>"test\a??.java"</code> matches all files/dirs which start with an 'a', then two
+ * more characters and then <code>".java"</code>, in a directory called test.
+ * <br>
+ * <code>"**"</code> matches everything in a directory tree.
+ * <br>
+ * <code>"**\test\**\XYZ*"</code> matches all files/dirs which start with <code>"XYZ"</code> and where
+ * there is a parent directory called test (e.g. <code>"abc\test\def\ghi\XYZ123"</code>).
+ * </p>
+ *
+ * <p>Case sensitivity may be turned off if necessary. By default, it is
+ * turned on.</p>
+ *
+ * <p>Example of usage:</p>
+ * <pre>
+ *   String[] includes = {"**\\*.class"};
+ *   String[] excludes = {"modules\\*\\**"};
+ *   ds.setIncludes(includes);
+ *   ds.setExcludes(excludes);
+ *   ds.setBasedir(new File("test"));
+ *   ds.setCaseSensitive(true);
+ *   ds.scan();
+ *
+ *   System.out.println("FILES:");
+ *   String[] files = ds.getIncludedFiles();
+ *   for (int i = 0; i &lt; files.length; i++) {
+ *     System.out.println(files[i]);
+ *   }
+ * </pre>
+ * <p>This will scan a directory called test for .class files, but excludes all
+ * files in all proper subdirectories of a directory called "modules".</p>
+ *
+ * @author Arnout J. Kuiper
+ *         <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
+ * @author Magesh Umasankar
+ * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
+ * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
+ */
+public class DirectoryScanner {
+
+    /**
+     * The base directory to be scanned.
+     */
+    protected File basedir;
+
+    /**
+     * The patterns for the files to be included.
+     */
+    protected String[] includes;
+
+    /**
+     * The files which matched at least one include and no excludes
+     * and were selected.
+     */
+    protected List<String> filesIncluded;
+
+    /**
+     * Whether or not the file system should be treated as a case sensitive
+     * one.
+     */
+    protected boolean isCaseSensitive = true;
+
+    public DirectoryScanner() {
+        super();
+    }
+
+    public DirectoryScanner(String basedir, String... includes) {
+        setBasedir(basedir);
+        setIncludes(includes);
+    }
+
+    /**
+     * Sets the base directory to be scanned. This is the directory which is
+     * scanned recursively. All '/' and '\' characters are replaced by
+     * <code>File.separatorChar</code>, so the separator used need not match
+     * <code>File.separatorChar</code>.
+     *
+     * @param basedir The base directory to scan.
+     *                Must not be {@code null}.
+     */
+    public void setBasedir(String basedir) {
+        setBasedir(new File(basedir.replace('/', File.separatorChar).replace('\\', File.separatorChar)));
+    }
+
+    /**
+     * Sets the base directory to be scanned. This is the directory which is
+     * scanned recursively.
+     *
+     * @param basedir The base directory for scanning.
+     *                Should not be {@code null}.
+     */
+    public void setBasedir(File basedir) {
+        this.basedir = basedir;
+    }
+
+    /**
+     * Returns the base directory to be scanned.
+     * This is the directory which is scanned recursively.
+     *
+     * @return the base directory to be scanned
+     */
+    public File getBasedir() {
+        return basedir;
+    }
+
+    /**
+     * <p>Sets the list of include patterns to use. All '/' and '\' characters
+     * are replaced by <code>File.separatorChar</code>, so the separator used
+     * need not match <code>File.separatorChar</code>.</p>
+     *
+     * <p>When a pattern ends with a '/' or '\', "**" is appended.</p>
+     *
+     * @param includes A list of include patterns.
+     *                 May be {@code null}, indicating that all files
+     *                 should be included. If a non-{@code null}
+     *                 list is given, all elements must be
+     *                 non-{@code null}.
+     */
+    public void setIncludes(String[] includes) {
+        if (includes == null) {
+            this.includes = null;
+        } else {
+            this.includes = new String[includes.length];
+            for (int i = 0; i < includes.length; i++) {
+                this.includes[i] = normalizePattern(includes[i]);
+            }
+        }
+    }
+
+    /**
+     * Scans the base directory for files which match at least one include
+     * pattern and don't match any exclude patterns. If there are selectors
+     * then the files must pass muster there, as well.
+     *
+     * @return the matching files
+     * @throws IllegalStateException if the base directory was set
+     *                               incorrectly (i.e. if it is {@code null}, doesn't exist,
+     *                               or isn't a directory).
+     */
+    public String[] scan() throws IllegalStateException {
+        if (basedir == null) {
+            throw new IllegalStateException("No basedir set");
+        }
+        if (!basedir.exists()) {
+            throw new IllegalStateException("basedir " + basedir
+                    + " does not exist");
+        }
+        if (!basedir.isDirectory()) {
+            throw new IllegalStateException("basedir " + basedir
+                    + " is not a directory");
+        }
+        if (includes == null || includes.length == 0) {
+            throw new IllegalStateException("No includes set ");
+        }
+
+        filesIncluded = new ArrayList<>();
+
+        scandir(basedir, "");
+
+        return getIncludedFiles();
+    }
+
+    /**
+     * Scans the given directory for files and directories. Found files and
+     * directories are placed in their respective collections, based on the
+     * matching of includes, excludes, and the selectors.  When a directory
+     * is found, it is scanned recursively.
+     *
+     * @param dir   The directory to scan. Must not be {@code null}.
+     * @param vpath The path relative to the base directory (needed to
+     *              prevent problems with an absolute path when using
+     *              dir). Must not be {@code null}.
+     */
+    protected void scandir(File dir, String vpath) {
+        String[] newfiles = dir.list();
+        if (GenericUtils.isEmpty(newfiles)) {
+            newfiles = GenericUtils.EMPTY_STRING_ARRAY;
+        }
+
+        for (String newfile : newfiles) {
+            String name = vpath + newfile;
+            File file = new File(dir, newfile);
+            if (file.isDirectory()) {
+                if (isIncluded(name)) {
+                    filesIncluded.add(name);
+                    scandir(file, name + File.separator);
+                } else if (couldHoldIncluded(name)) {
+                    scandir(file, name + File.separator);
+                }
+            } else if (file.isFile()) {
+                if (isIncluded(name)) {
+                    filesIncluded.add(name);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the names of the files which matched at least one of the
+     * include patterns and none of the exclude patterns.
+     * The names are relative to the base directory.
+     *
+     * @return the names of the files which matched at least one of the
+     * include patterns and none of the exclude patterns.
+     */
+    public String[] getIncludedFiles() {
+        String[] files = new String[filesIncluded.size()];
+        return filesIncluded.toArray(files);
+    }
+
+    /**
+     * Tests whether or not a name matches against at least one include
+     * pattern.
+     *
+     * @param name The name to match. Must not be {@code null}.
+     * @return <code>true</code> when the name matches against at least one
+     * include pattern, or <code>false</code> otherwise.
+     */
+    protected boolean isIncluded(String name) {
+        for (String include : includes) {
+            if (SelectorUtils.matchPath(include, name, isCaseSensitive)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tests whether or not a name matches the start of at least one include
+     * pattern.
+     *
+     * @param name The name to match. Must not be {@code null}.
+     * @return <code>true</code> when the name matches against the start of at
+     * least one include pattern, or <code>false</code> otherwise.
+     */
+    protected boolean couldHoldIncluded(String name) {
+        for (String include : includes) {
+            if (SelectorUtils.matchPatternStart(include, name, isCaseSensitive)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Normalizes the pattern, e.g. converts forward and backward slashes to the platform-specific file separator.
+     *
+     * @param pattern The pattern to normalize, must not be {@code null}.
+     * @return The normalized pattern, never {@code null}.
+     */
+    private String normalizePattern(String pattern) {
+        pattern = pattern.trim();
+
+        if (pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) {
+            if (File.separatorChar == '\\') {
+                pattern = replace(pattern, "/", "\\\\", -1);
+            } else {
+                pattern = replace(pattern, "\\\\", "/", -1);
+            }
+        } else {
+            pattern = pattern.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);
+
+            if (pattern.endsWith(File.separator)) {
+                pattern += "**";
+            }
+        }
+
+        return pattern;
+    }
+
+    /**
+     * <p>Replace a String with another String inside a larger String,
+     * for the first <code>max</code> values of the search String.</p>
+     *
+     * <p>A {@code null} reference passed to this method is a no-op.</p>
+     *
+     * @param text text to search and replace in
+     * @param repl String to search for
+     * @param with String to replace with
+     * @param max  maximum number of values to replace, or <code>-1</code> if no maximum
+     * @return the text with any replacements processed
+     */
+    @SuppressWarnings("PMD.AssignmentInOperand")
+    public static String replace(String text, String repl, String with, int max) {
+        if ((text == null) || (repl == null) || (with == null) || (repl.length() == 0)) {
+            return text;
+        }
+
+        StringBuilder buf = new StringBuilder(text.length());
+        int start = 0;
+        for (int end = text.indexOf(repl, start); end != -1; end = text.indexOf(repl, start)) {
+            buf.append(text.substring(start, end)).append(with);
+            start = end + repl.length();
+
+            if (--max == 0) {
+                break;
+            }
+        }
+        buf.append(text.substring(start));
+        return buf.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
new file mode 100644
index 0000000..8a1a68d
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
@@ -0,0 +1,66 @@
+/*
+ * 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.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@code /dev/null} implementation - always open
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EmptyInputStream extends InputStream {
+    public static final EmptyInputStream DEV_NULL = new EmptyInputStream();
+
+    public EmptyInputStream() {
+        super();
+    }
+
+    @Override
+    public int read() throws IOException {
+        return -1;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        return -1;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        return 0L;
+    }
+
+    @Override
+    public int available() throws IOException {
+        return 0;
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+        throw new UnsupportedOperationException("mark(" + readlimit + ") called despite the fact that markSupported=" + markSupported());
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        // ignored
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java
new file mode 100644
index 0000000..feafd18
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.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.common.util.io;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * @param <T> Type of information being extracted
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface FileInfoExtractor<T> {
+
+    FileInfoExtractor<Boolean> EXISTS = Files::exists;
+
+    FileInfoExtractor<Boolean> ISDIR = Files::isDirectory;
+
+    FileInfoExtractor<Boolean> ISREG = Files::isRegularFile;
+
+    FileInfoExtractor<Boolean> ISSYMLINK = (file, options) -> Files.isSymbolicLink(file);
+
+    FileInfoExtractor<Long> SIZE = (file, options) -> Files.size(file);
+
+    FileInfoExtractor<Set<PosixFilePermission>> PERMISSIONS = IoUtils::getPermissions;
+
+    FileInfoExtractor<FileTime> LASTMODIFIED = Files::getLastModifiedTime;
+
+    T infoOf(Path file, LinkOption... options) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
new file mode 100644
index 0000000..d847079
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util.io;
+
+import java.io.InputStream;
+import java.nio.channels.Channel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class InputStreamWithChannel extends InputStream implements Channel {
+    protected InputStreamWithChannel() {
+        super();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
new file mode 100644
index 0000000..10aa59a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -0,0 +1,556 @@
+/*
+ * 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.util.io;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class IoUtils {
+
+    public static final OpenOption[] EMPTY_OPEN_OPTIONS = new OpenOption[0];
+    public static final CopyOption[] EMPTY_COPY_OPTIONS = new CopyOption[0];
+    public static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0];
+    public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTES = new FileAttribute<?>[0];
+
+    public static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd"));
+
+    /**
+     * Size of preferred work buffer when reading / writing data to / from streams
+     */
+    public static final int DEFAULT_COPY_SIZE = 8192;
+
+    /**
+     * The local O/S line separator
+     */
+    public static final String EOL = System.lineSeparator();
+
+    /**
+     * A {@link Set} of {@link StandardOpenOption}-s that indicate an intent
+     * to create/modify a file
+     */
+    public static final Set<StandardOpenOption> WRITEABLE_OPEN_OPTIONS =
+        Collections.unmodifiableSet(
+            EnumSet.of(
+                StandardOpenOption.APPEND, StandardOpenOption.CREATE,
+                StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE,
+                StandardOpenOption.DSYNC, StandardOpenOption.SYNC,
+                StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE));
+
+    private static final byte[] EOL_BYTES = EOL.getBytes(StandardCharsets.UTF_8);
+
+    private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
+
+    /**
+     * Private Constructor
+     */
+    private IoUtils() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    /**
+     * @return The local platform line separator bytes as UTF-8. <B>Note:</B>
+     * each call returns a <U>new</U> instance in order to avoid inadvertent
+     * changes in shared objects
+     * @see #EOL
+     */
+    public static byte[] getEOLBytes() {
+        return EOL_BYTES.clone();
+    }
+
+    public static LinkOption[] getLinkOptions(boolean followLinks) {
+        if (followLinks) {
+            return EMPTY_LINK_OPTIONS;
+        } else {    // return a clone that modifications to the array will not affect others
+            return NO_FOLLOW_OPTIONS.clone();
+        }
+    }
+
+    public static long copy(InputStream source, OutputStream sink) throws IOException {
+        return copy(source, sink, DEFAULT_COPY_SIZE);
+    }
+
+    public static long copy(InputStream source, OutputStream sink, int bufferSize) throws IOException {
+        long nread = 0L;
+        byte[] buf = new byte[bufferSize];
+        for (int n = source.read(buf); n > 0; n = source.read(buf)) {
+            sink.write(buf, 0, n);
+            nread += n;
+        }
+
+        return nread;
+    }
+
+    /**
+     * Closes a bunch of resources suppressing any {@link IOException}s their
+     * {@link Closeable#close()} method may have thrown
+     *
+     * @param closeables The {@link Closeable}s to close
+     * @return The <U>first</U> {@link IOException} that occurred during closing
+     * of a resource - if more than one exception occurred, they are added as
+     * suppressed exceptions to the first one
+     * @see Throwable#getSuppressed()
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public static IOException closeQuietly(Closeable... closeables) {
+        IOException err = null;
+        for (Closeable c : closeables) {
+            try {
+                if (c != null) {
+                    c.close();
+                }
+            } catch (IOException e) {
+                err = GenericUtils.accumulateException(err, e);
+            }
+        }
+
+        return err;
+    }
+
+    /**
+     * @param fileName The file name to be evaluated - ignored if {@code null}/empty
+     * @return {@code true} if the file ends in one of the {@link #WINDOWS_EXECUTABLE_EXTENSIONS}
+     */
+    public static boolean isWindowsExecutable(String fileName) {
+        if ((fileName == null) || (fileName.length() <= 0)) {
+            return false;
+        }
+        for (String suffix : WINDOWS_EXECUTABLE_EXTENSIONS) {
+            if (fileName.endsWith(suffix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * If the &quot;posix&quot; view is supported, then it returns
+     * {@link Files#getPosixFilePermissions(Path, LinkOption...)}, otherwise
+     * uses the {@link #getPermissionsFromFile(File)} method
+     *
+     * @param path    The {@link Path}
+     * @param options The {@link LinkOption}s to use when querying the permissions
+     * @return A {@link Set} of {@link PosixFilePermission}
+     * @throws IOException If failed to access the file system in order to
+     *                     retrieve the permissions
+     */
+    public static Set<PosixFilePermission> getPermissions(Path path, LinkOption... options) throws IOException {
+        FileSystem fs = path.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (views.contains("posix")) {
+            return Files.getPosixFilePermissions(path, options);
+        } else {
+            return getPermissionsFromFile(path.toFile());
+        }
+    }
+
+    /**
+     * @param f The {@link File} to be checked
+     * @return A {@link Set} of {@link PosixFilePermission}s based on whether
+     * the file is readable/writable/executable. If so, then <U>all</U> the
+     * relevant permissions are set (i.e., owner, group and others)
+     */
+    public static Set<PosixFilePermission> getPermissionsFromFile(File f) {
+        Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
+        if (f.canRead()) {
+            perms.add(PosixFilePermission.OWNER_READ);
+            perms.add(PosixFilePermission.GROUP_READ);
+            perms.add(PosixFilePermission.OTHERS_READ);
+        }
+
+        if (f.canWrite()) {
+            perms.add(PosixFilePermission.OWNER_WRITE);
+            perms.add(PosixFilePermission.GROUP_WRITE);
+            perms.add(PosixFilePermission.OTHERS_WRITE);
+        }
+
+        if (isExecutable(f)) {
+            perms.add(PosixFilePermission.OWNER_EXECUTE);
+            perms.add(PosixFilePermission.GROUP_EXECUTE);
+            perms.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+
+        return perms;
+    }
+
+    public static boolean isExecutable(File f) {
+        if (f == null) {
+            return false;
+        }
+
+        if (OsUtils.isWin32()) {
+            return isWindowsExecutable(f.getName());
+        } else {
+            return f.canExecute();
+        }
+    }
+
+    /**
+     * If the &quot;posix&quot; view is supported, then it invokes
+     * {@link Files#setPosixFilePermissions(Path, Set)}, otherwise
+     * uses the {@link #setPermissionsToFile(File, Collection)} method
+     *
+     * @param path  The {@link Path}
+     * @param perms The {@link Set} of {@link PosixFilePermission}s
+     * @throws IOException If failed to access the file system
+     */
+    public static void setPermissions(Path path, Set<PosixFilePermission> perms) throws IOException {
+        FileSystem fs = path.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (views.contains("posix")) {
+            Files.setPosixFilePermissions(path, perms);
+        } else {
+            setPermissionsToFile(path.toFile(), perms);
+        }
+    }
+
+    /**
+     * @param f     The {@link File}
+     * @param perms A {@link Collection} of {@link PosixFilePermission}s to set on it.
+     *              <B>Note:</B> the file is set to readable/writable/executable not only by the
+     *              owner if <U>any</U> of relevant the owner/group/others permission is set
+     */
+    public static void setPermissionsToFile(File f, Collection<PosixFilePermission> perms) {
+        boolean readable = perms != null
+                && (perms.contains(PosixFilePermission.OWNER_READ)
+                        || perms.contains(PosixFilePermission.GROUP_READ)
+                        || perms.contains(PosixFilePermission.OTHERS_READ));
+        f.setReadable(readable, false);
+
+        boolean writable = perms != null
+                && (perms.contains(PosixFilePermission.OWNER_WRITE)
+                        || perms.contains(PosixFilePermission.GROUP_WRITE)
+                        || perms.contains(PosixFilePermission.OTHERS_WRITE));
+        f.setWritable(writable, false);
+
+        boolean executable = perms != null
+                && (perms.contains(PosixFilePermission.OWNER_EXECUTE)
+                        || perms.contains(PosixFilePermission.GROUP_EXECUTE)
+                        || perms.contains(PosixFilePermission.OTHERS_EXECUTE));
+        f.setExecutable(executable, false);
+    }
+
+    /**
+     * <P>Get file owner.</P>
+     *
+     * @param path  The {@link Path}
+     * @param options The {@link LinkOption}s to use when querying the owner
+     * @return Owner of the file or null if unsupported. <B>Note:</B> for
+     * <I>Windows</I> it strips any prepended domain or group name
+     * @throws IOException If failed to access the file system
+     * @see Files#getOwner(Path, LinkOption...)
+     */
+    public static String getFileOwner(Path path, LinkOption... options) throws IOException {
+        try {
+            UserPrincipal principal = Files.getOwner(path, options);
+            String owner = (principal == null) ? null : principal.getName();
+            return OsUtils.getCanonicalUser(owner);
+        } catch (UnsupportedOperationException e) {
+            return null;
+        }
+    }
+
+    /**
+     * <P>Checks if a file exists - <B>Note:</B> according to the
+     * <A HREF="http://docs.oracle.com/javase/tutorial/essential/io/check.html">Java tutorial - Checking a File or Directory</A>:
+     * </P>
+     *
+     * <PRE>
+     * The methods in the Path class are syntactic, meaning that they operate
+     * on the Path instance. But eventually you must access the file system
+     * to verify that a particular Path exists, or does not exist. You can do
+     * so with the exists(Path, LinkOption...) and the notExists(Path, LinkOption...)
+     * methods. Note that !Files.exists(path) is not equivalent to Files.notExists(path).
+     * When you are testing a file's existence, three results are possible:
+     *
+     * - The file is verified to exist.
+     * - The file is verified to not exist.
+     * - The file's status is unknown.
+     *
+     * This result can occur when the program does not have access to the file.
+     * If both exists and notExists return false, the existence of the file cannot
+     * be verified.
+     * </PRE>
+     *
+     * @param path    The {@link Path} to be tested
+     * @param options The {@link LinkOption}s to use
+     * @return {@link Boolean#TRUE}/{@link Boolean#FALSE} or {@code null}
+     * according to the file status as explained above
+     */
+    public static Boolean checkFileExists(Path path, LinkOption... options) {
+        if (Files.exists(path, options)) {
+            return Boolean.TRUE;
+        } else if (Files.notExists(path, options)) {
+            return Boolean.FALSE;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Read the requested number of bytes or fail if there are not enough left.
+     *
+     * @param input  where to read input from
+     * @param buffer destination
+     * @throws IOException  if there is a problem reading the file
+     * @throws EOFException if the number of bytes read was incorrect
+     */
+    public static void readFully(InputStream input, byte[] buffer) throws IOException {
+        readFully(input, buffer, 0, buffer.length);
+    }
+
+    /**
+     * Read the requested number of bytes or fail if there are not enough left.
+     *
+     * @param input  where to read input from
+     * @param buffer destination
+     * @param offset initial offset into buffer
+     * @param length length to read, must be &ge; 0
+     * @throws IOException  if there is a problem reading the file
+     * @throws EOFException if the number of bytes read was incorrect
+     */
+    public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException {
+        int actual = read(input, buffer, offset, length);
+        if (actual != length) {
+            throw new EOFException("Premature EOF - expected=" + length + ", actual=" + actual);
+        }
+    }
+
+    /**
+     * Read as many bytes as possible until EOF or achieved required length
+     *
+     * @param input  where to read input from
+     * @param buffer destination
+     * @return actual length read; may be less than requested if EOF was reached
+     * @throws IOException if a read error occurs
+     */
+    public static int read(InputStream input, byte[] buffer) throws IOException {
+        return read(input, buffer, 0, buffer.length);
+    }
+
+    /**
+     * Read as many bytes as possible until EOF or achieved required length
+     *
+     * @param input  where to read input from
+     * @param buffer destination
+     * @param offset initial offset into buffer
+     * @param length length to read - ignored if non-positive
+     * @return actual length read; may be less than requested if EOF was reached
+     * @throws IOException if a read error occurs
+     */
+    public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException {
+        for (int remaining = length, curOffset = offset; remaining > 0;) {
+            int count = input.read(buffer, curOffset, remaining);
+            if (count == -1) { // EOF before achieved required length
+                return curOffset - offset;
+            }
+
+            remaining -= count;
+            curOffset += count;
+        }
+
+        return length;
+    }
+
+    /**
+     * @param perms    The current {@link PosixFilePermission}s - ignored if {@code null}/empty
+     * @param excluded The permissions <U>not</U> allowed to exist - ignored if {@code null}/empty
+     * @return The violating {@link PosixFilePermission} - {@code null}
+     * if no violating permission found
+     */
+    public static PosixFilePermission validateExcludedPermissions(Collection<PosixFilePermission> perms, Collection<PosixFilePermission> excluded) {
+        if (GenericUtils.isEmpty(perms) || GenericUtils.isEmpty(excluded)) {
+            return null;
+        }
+
+        for (PosixFilePermission p : excluded) {
+            if (perms.contains(p)) {
+                return p;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param path    The {@link Path} to check
+     * @param options The {@link LinkOption}s to use when checking if path is a directory
+     * @return The same input path if it is a directory
+     * @throws UnsupportedOperationException if input path not a directory
+     */
+    public static Path ensureDirectory(Path path, LinkOption... options) {
+        if (!Files.isDirectory(path, options)) {
+            throw new UnsupportedOperationException("Not a directory: " + path);
+        }
+        return path;
+    }
+
+    /**
+     * @param options The {@link LinkOption}s - OK if {@code null}/empty
+     * @return {@code true} if the link options are {@code null}/empty or do
+     * not contain {@link LinkOption#NOFOLLOW_LINKS}, {@code false} otherwise
+     * (i.e., the array is not empty and contains the special value)
+     */
+    public static boolean followLinks(LinkOption... options) {
+        if (GenericUtils.isEmpty(options)) {
+            return true;
+        }
+
+        for (LinkOption localLinkOption : options) {
+            if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static String appendPathComponent(String prefix, String component) {
+        if (GenericUtils.isEmpty(prefix)) {
+            return component;
+        }
+
+        if (GenericUtils.isEmpty(component)) {
+            return prefix;
+        }
+
+        StringBuilder sb = new StringBuilder(prefix.length() + component.length() + File.separator.length()).append(prefix);
+
+        if (sb.charAt(prefix.length() - 1) == File.separatorChar) {
+            if (component.charAt(0) == File.separatorChar) {
+                sb.append(component.substring(1));
+            } else {
+                sb.append(component);
+            }
+        } else {
+            if (component.charAt(0) != File.separatorChar) {
+                sb.append(File.separatorChar);
+            }
+            sb.append(component);
+        }
+
+        return sb.toString();
+    }
+
+    public static byte[] toByteArray(InputStream inStream) throws IOException {
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(DEFAULT_COPY_SIZE)) {
+            copy(inStream, baos);
+            return baos.toByteArray();
+        }
+    }
+
+    /**
+     * Reads all lines until no more available
+     *
+     * @param url The {@link URL} to read from
+     * @return The {@link List} of lines in the same <U>order</U> as it was read
+     * @throws IOException If failed to read the lines
+     * @see #readAllLines(InputStream)
+     */
+    public static List<String> readAllLines(URL url) throws IOException {
+        try (InputStream stream = Objects.requireNonNull(url, "No URL").openStream()) {
+            return readAllLines(stream);
+        }
+    }
+
+    /**
+     * Reads all lines until no more available
+     *
+     * @param stream The {@link InputStream} - <B>Note:</B> assumed to
+     * contain {@code UTF-8} encoded data
+     * @return The {@link List} of lines in the same <U>order</U> as it was read
+     * @throws IOException If failed to read the lines
+     * @see #readAllLines(Reader)
+     */
+    public static List<String> readAllLines(InputStream stream) throws IOException {
+        try (Reader reader = new InputStreamReader(Objects.requireNonNull(stream, "No stream instance"), StandardCharsets.UTF_8)) {
+            return readAllLines(reader);
+        }
+    }
+
+    public static List<String> readAllLines(Reader reader) throws IOException {
+        try (BufferedReader br = new BufferedReader(Objects.requireNonNull(reader, "No reader instance"), DEFAULT_COPY_SIZE)) {
+            return readAllLines(br);
+        }
+    }
+
+    /**
+     * Reads all lines until no more available
+     *
+     * @param reader The {@link BufferedReader} to read all lines
+     * @return The {@link List} of lines in the same <U>order</U> as it was read
+     * @throws IOException If failed to read the lines
+     * @see #readAllLines(BufferedReader, int)
+     */
+    public static List<String> readAllLines(BufferedReader reader) throws IOException {
+        return readAllLines(reader, -1);
+    }
+
+    /**
+     * Reads all lines until no more available
+     *
+     * @param reader The {@link BufferedReader} to read all lines
+     * @param lineCountHint A hint as to the expected number of lines - non-positive
+     * means unknown - in which case some initial default value will be used to
+     * initialize the list used to accumulate the lines.
+     * @return The {@link List} of lines in the same <U>order</U> as it was read
+     * @throws IOException If failed to read the lines
+     */
+    public static List<String> readAllLines(BufferedReader reader, int lineCountHint) throws IOException {
+        List<String> result = new ArrayList<>(Math.max(lineCountHint, Short.SIZE));
+        for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+            result.add(line);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
new file mode 100644
index 0000000..bbf956a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
@@ -0,0 +1,113 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Reads from another {@link InputStream} up to specified max. length
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LimitInputStream extends FilterInputStream implements Channel {
+    private final AtomicBoolean open = new AtomicBoolean(true);
+    private long remaining;
+
+    public LimitInputStream(InputStream in, long length) {
+        super(in);
+        remaining = length;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (!isOpen()) {
+            throw new IOException("read() - stream is closed (remaining=" + remaining + ")");
+        }
+
+        if (remaining > 0) {
+            remaining--;
+            return super.read();
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (!isOpen()) {
+            throw new IOException("read(len=" + len + ") stream is closed (remaining=" + remaining + ")");
+        }
+
+        int nb = len;
+        if (nb > remaining) {
+            nb = (int) remaining;
+        }
+        if (nb > 0) {
+            int read = super.read(b, off, nb);
+            remaining -= read;
+            return read;
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        if (!isOpen()) {
+            throw new IOException("skip(" + n + ") stream is closed (remaining=" + remaining + ")");
+        }
+
+        long skipped = super.skip(n);
+        remaining -= skipped;
+        return skipped;
+    }
+
+    @Override
+    public int available() throws IOException {
+        if (!isOpen()) {
+            throw new IOException("available() stream is closed (remaining=" + remaining + ")");
+        }
+
+        int av = super.available();
+        if (av > remaining) {
+            return (int) remaining;
+        } else {
+            return av;
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        // do not close the original input stream since it serves for ACK(s)
+        if (open.getAndSet(false)) {
+            //noinspection UnnecessaryReturnStatement
+            return; // debug breakpoint
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
new file mode 100644
index 0000000..ff4dbb6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.logging.LoggingUtils;
+import org.apache.sshd.common.util.logging.SimplifiedLog;
+import org.slf4j.Logger;
+
+/**
+ * Dumps everything that is written to the stream to the logger
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LoggingFilterOutputStream extends FilterOutputStream {
+
+    private final String msg;
+    private final SimplifiedLog log;
+    private final int chunkSize;
+    private final AtomicInteger writeCount = new AtomicInteger(0);
+
+    public LoggingFilterOutputStream(OutputStream out, String msg, Logger log, PropertyResolver resolver) {
+        this(out, msg, log, resolver.getIntProperty(BufferUtils.HEXDUMP_CHUNK_SIZE, BufferUtils.DEFAULT_HEXDUMP_CHUNK_SIZE));
+    }
+
+    public LoggingFilterOutputStream(OutputStream out, String msg, Logger log, int chunkSize) {
+        super(out);
+        this.msg = msg;
+        this.log = LoggingUtils.wrap(log);
+        this.chunkSize = chunkSize;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        byte[] d = new byte[1];
+        d[0] = (byte) b;
+        write(d, 0, 1);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        int count = writeCount.incrementAndGet();
+        BufferUtils.dumpHex(log, BufferUtils.DEFAULT_HEXDUMP_LEVEL, msg + "[" + count + "]", BufferUtils.DEFAULT_HEX_SEPARATOR, chunkSize, b, off, len);
+        out.write(b, off, len);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
new file mode 100644
index 0000000..032260b
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
@@ -0,0 +1,258 @@
+/*
+ * 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.util.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * Watches over changes for a file and re-loads them if file has changed - including
+ * if file is deleted or (re-)created
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ModifiableFileWatcher extends AbstractLoggingBean {
+
+    /**
+     * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if strict
+     * permissions are enforced on key files
+     */
+    public static final Set<PosixFilePermission> STRICTLY_PROHIBITED_FILE_PERMISSION =
+            Collections.unmodifiableSet(
+                    EnumSet.of(PosixFilePermission.GROUP_WRITE, PosixFilePermission.OTHERS_WRITE));
+
+    protected final LinkOption[] options;
+
+    private final Path file;
+    private final AtomicBoolean lastExisted = new AtomicBoolean(false);
+    private final AtomicLong lastSize = new AtomicLong(Long.MIN_VALUE);
+    private final AtomicLong lastModified = new AtomicLong(-1L);
+
+    public ModifiableFileWatcher(File file) {
+        this(Objects.requireNonNull(file, "No file to watch").toPath());
+    }
+
+    public ModifiableFileWatcher(Path file) {
+        this(file, IoUtils.getLinkOptions(true));
+    }
+
+    public ModifiableFileWatcher(Path file, LinkOption... options) {
+        this.file = Objects.requireNonNull(file, "No path to watch");
+        // use a clone to avoid being sensitive to changes in the passed array
+        this.options = (options == null) ? IoUtils.EMPTY_LINK_OPTIONS : options.clone();
+    }
+
+    /**
+     * @return The watched {@link Path}
+     */
+    public final Path getPath() {
+        return file;
+    }
+
+    public final boolean exists() throws IOException {
+        return Files.exists(getPath(), options);
+    }
+
+    public final long size() throws IOException {
+        if (exists()) {
+            return Files.size(getPath());
+        } else {
+            return -1L;
+        }
+    }
+
+    public final FileTime lastModified() throws IOException {
+        if (exists()) {
+            BasicFileAttributes attrs = Files.readAttributes(getPath(), BasicFileAttributes.class, options);
+            return attrs.lastModifiedTime();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return {@code true} if the watched file has probably been changed
+     * @throws IOException If failed to query file data
+     */
+    public boolean checkReloadRequired() throws IOException {
+        boolean exists = exists();
+        // if existence state changed from last time
+        if (exists != lastExisted.getAndSet(exists)) {
+            return true;
+        }
+
+        if (!exists) {
+            // file did not exist and still does not exist
+            resetReloadAttributes();
+            return false;
+        }
+
+        long size = size();
+        if (size < 0L) {
+            // means file no longer exists
+            resetReloadAttributes();
+            return true;
+        }
+
+        // if size changed then obviously need reload
+        if (size != lastSize.getAndSet(size)) {
+            return true;
+        }
+
+        FileTime modifiedTime = lastModified();
+        if (modifiedTime == null) {
+            // means file no longer exists
+            resetReloadAttributes();
+            return true;
+        }
+
+        long timestamp = modifiedTime.toMillis();
+        return timestamp != lastModified.getAndSet(timestamp);
+
+    }
+
+    /**
+     * Resets the state attributes used to detect changes to the initial
+     * construction values - i.e., file assumed not to exist and no known
+     * size of modify time
+     */
+    public void resetReloadAttributes() {
+        lastExisted.set(false);
+        lastSize.set(Long.MIN_VALUE);
+        lastModified.set(-1L);
+    }
+
+    /**
+     * May be called to refresh the state attributes used to detect changes
+     * e.g., file existence, size and last-modified time once re-loading is
+     * successfully completed. If the file does not exist then the attributes
+     * are reset to an &quot;unknown&quot; state.
+     *
+     * @throws IOException If failed to access the file (if exists)
+     * @see #resetReloadAttributes()
+     */
+    public void updateReloadAttributes() throws IOException {
+        if (exists()) {
+            long size = size();
+            FileTime modifiedTime = lastModified();
+
+            if ((size >= 0L) && (modifiedTime != null)) {
+                lastExisted.set(true);
+                lastSize.set(size);
+                lastModified.set(modifiedTime.toMillis());
+                return;
+            }
+        }
+
+        resetReloadAttributes();
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toString(getPath());
+    }
+
+    /**
+     * <P>Checks if a path has strict permissions</P>
+     * <UL>
+     *
+     * <LI><P>
+     * (For {@code Unix}) The path may not have group or others write permissions
+     * </P></LI>
+     *
+     * <LI><P>
+     * The path must be owned by current user.
+     * </P></LI>
+     *
+     * <LI><P>
+     * (For {@code Unix}) The path may be owned by root.
+     * </P></LI>
+     *
+     * </UL>
+     *
+     * @param path    The {@link Path} to be checked - ignored if {@code null}
+     *                or does not exist
+     * @param options The {@link LinkOption}s to use to query the file's permissions
+     * @return The violated permission as {@link SimpleImmutableEntry} where key
+     * is a loggable message and value is the offending object
+     * - e.g., {@link PosixFilePermission} or {@link String} for owner. Return
+     * value is {@code null} if no violations detected
+     * @throws IOException If failed to retrieve the permissions
+     * @see #STRICTLY_PROHIBITED_FILE_PERMISSION
+     */
+    public static SimpleImmutableEntry<String, Object> validateStrictConfigFilePermissions(Path path, LinkOption... options) throws IOException {
+        if ((path == null) || (!Files.exists(path, options))) {
+            return null;
+        }
+
+        Collection<PosixFilePermission> perms = IoUtils.getPermissions(path, options);
+        if (GenericUtils.isEmpty(perms)) {
+            return null;
+        }
+
+        if (OsUtils.isUNIX()) {
+            PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
+            if (p != null) {
+                return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
+            }
+        }
+
+        String owner = IoUtils.getFileOwner(path, options);
+        if (GenericUtils.isEmpty(owner)) {
+            // we cannot get owner
+            // general issue: jvm does not support permissions
+            // security issue: specific filesystem does not support permissions
+            return null;
+        }
+
+        String current = OsUtils.getCurrentUser();
+        Set<String> expected = new HashSet<>();
+        expected.add(current);
+        if (OsUtils.isUNIX()) {
+            // Windows "Administrator" was considered however in Windows most likely a group is used.
+            expected.add(OsUtils.ROOT_USER);
+        }
+
+        if (!expected.contains(owner)) {
+            return new SimpleImmutableEntry<>(String.format("Owner violation (%s)", owner), owner);
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
new file mode 100644
index 0000000..b9aedee
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NoCloseInputStream extends FilterInputStream {
+    public NoCloseInputStream(InputStream in) {
+        super(in);
+    }
+
+    @Override
+    public void close() throws IOException {
+        // ignored
+    }
+
+    public static InputStream resolveInputStream(InputStream input, boolean okToClose) {
+        if ((input == null) || okToClose) {
+            return input;
+        } else {
+            return new NoCloseInputStream(input);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
new file mode 100644
index 0000000..4ba16d3
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NoCloseOutputStream extends FilterOutputStream {
+    public NoCloseOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    @Override
+    public void close() throws IOException {
+        // ignored
+    }
+
+    public static OutputStream resolveOutputStream(OutputStream output, boolean okToClose) {
+        if ((output == null) || okToClose) {
+            return output;
+        } else {
+            return new NoCloseOutputStream(output);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
new file mode 100644
index 0000000..9c8b218
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NoCloseReader extends FilterReader {
+    public NoCloseReader(Reader in) {
+        super(in);
+    }
+
+    @Override
+    public void close() throws IOException {
+        // ignored
+    }
+
+    public static Reader resolveReader(Reader r, boolean okToClose) {
+        if ((r == null) || okToClose) {
+            return r;
+        } else {
+            return new NoCloseReader(r);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java
new file mode 100644
index 0000000..0f92697
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.io;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NoCloseWriter extends FilterWriter {
+    public NoCloseWriter(Writer out) {
+        super(out);
+    }
+
+    @Override
+    public void close() throws IOException {
+        // ignored
+    }
+
+    public static Writer resolveWriter(Writer r, boolean okToClose) {
+        if ((r == null) || okToClose) {
+            return r;
+        } else {
+            return new NoCloseWriter(r);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
new file mode 100644
index 0000000..eb21383
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
@@ -0,0 +1,90 @@
+/*
+ * 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.util.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@code /dev/null} input stream
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NullInputStream extends InputStream implements Channel {
+    private final AtomicBoolean open = new AtomicBoolean(true);
+
+    public NullInputStream() {
+        super();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for reading one value");
+        }
+        return -1;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for reading " + len + " bytes");
+        }
+        return -1;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for skipping " + n + " bytes");
+        }
+        return 0L;
+    }
+
+    @Override
+    public int available() throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for availability query");
+        }
+        return 0;
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for reset");
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (open.getAndSet(false)) {
+            //noinspection UnnecessaryReturnStatement
+            return; // debug breakpoint
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
new file mode 100644
index 0000000..67fa2d0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
@@ -0,0 +1,72 @@
+/*
+ * 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.util.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {code /dev/null} output stream
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NullOutputStream extends OutputStream implements Channel {
+    private final AtomicBoolean open = new AtomicBoolean(true);
+
+    public NullOutputStream() {
+        super();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for writing one byte");
+        }
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for writing " + len + " bytes");
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        if (!isOpen()) {
+            throw new EOFException("Stream is closed for flushing");
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (open.getAndSet(false)) {
+            //noinspection UnnecessaryReturnStatement
+            return; // debug breakpoint
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
new file mode 100644
index 0000000..6f81872
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util.io;
+
+import java.io.OutputStream;
+import java.nio.channels.Channel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class OutputStreamWithChannel extends OutputStream implements Channel {
+    protected OutputStreamWithChannel() {
+        super();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java
new file mode 100644
index 0000000..b8351f1
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java
@@ -0,0 +1,93 @@
+/*
+ * 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.util.io.der;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum ASN1Class {
+    // NOTE: order is crucial, so DON'T change it
+    UNIVERSAL((byte) 0x00),
+    APPLICATION((byte) 0x01),
+    CONTEXT((byte) 0x02),
+    PRIVATE((byte) 0x03);
+
+    public static final List<ASN1Class>  VALUES =
+            Collections.unmodifiableList(Arrays.asList(values()));
+
+    private final byte  byteValue;
+
+    ASN1Class(byte classValue) {
+        byteValue = classValue;
+    }
+
+    public byte getClassValue() {
+        return byteValue;
+    }
+
+    public static ASN1Class fromName(String s) {
+        if (GenericUtils.isEmpty(s)) {
+            return null;
+        }
+
+        for (ASN1Class c : VALUES) {
+            if (s.equalsIgnoreCase(c.name())) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * <P>The first byte in DER encoding is made of following fields</P>
+     * <pre>
+     *-------------------------------------------------
+     *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
+     *-------------------------------------------------
+     *|  Class    | CF  |        Type                 |
+     *-------------------------------------------------
+     * </pre>
+     * @param value The original DER encoded byte
+     * @return The {@link ASN1Class} value - {@code null} if no match found
+     * @see #fromTypeValue(int)
+     */
+    public static ASN1Class fromDERValue(int value) {
+        return fromTypeValue((value >> 6) & 0x03);
+    }
+
+    /**
+     * @param value The &quot;pure&quot; value - unshifted and with no extras
+     * @return The {@link ASN1Class} value - {@code null} if no match found
+     */
+    public static ASN1Class fromTypeValue(int value) {
+        // all 4 values are defined
+        if ((value < 0) || (value >= VALUES.size())) {
+            return null;
+        }
+
+        return VALUES.get(value);
+    }
+}