/** * @param props The {@link Properties} holding the server's configuration - ignored if {@code * null}/empty * @param options The {@link LinkOption}s to use when checking files existence * @return A {@link Map} of the found identities where key=the identity type (case * <U>insensitive</U>) and value=the {@link Path} of the file holding the specific type key * @throws IOException If failed to access the file system * @see #getIdentityType(String) * @see #HOST_KEY_CONFIG_PROP * @see org.apache.sshd.common.config.SshConfigFileReader#readConfigFile(File) */ public static Map<String, Path> findIdentities(Properties props, LinkOption... options) throws IOException { if (GenericUtils.isEmpty(props)) { return Collections.emptyMap(); } String keyList = props.getProperty(HOST_KEY_CONFIG_PROP); String[] paths = GenericUtils.split(keyList, ','); if (GenericUtils.isEmpty(paths)) { return Collections.emptyMap(); } Map<String, Path> ids = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (String p : paths) { File file = new File(p); Path path = file.toPath(); if (!Files.exists(path, options)) { continue; } String type = getIdentityType(path.getFileName().toString()); if (GenericUtils.isEmpty(type)) { type = p; // just in case the file name does not adhere to the standard naming convention } Path prev = ids.put(type, path); ValidateUtils.checkTrue(prev == null, "Multiple mappings for type=%s", type); } return ids; }
protected Collection<Option> addTargetIsDirectory(Collection<Option> options) { if (GenericUtils.isEmpty(options) || (!options.contains(Option.TargetIsDirectory))) { // create a copy in case the original collection is un-modifiable options = GenericUtils.isEmpty(options) ? EnumSet.noneOf(Option.class) : GenericUtils.of(options); options.add(Option.TargetIsDirectory); } return options; }
public Collection<KeyPair> loadKeyPairs( String resourceKey, String pubData, String prvData, String prvEncryption, FilePasswordProvider passwordProvider) throws IOException, GeneralSecurityException { Decoder b64Decoder = Base64.getDecoder(); byte[] pubBytes = b64Decoder.decode(pubData); byte[] prvBytes = b64Decoder.decode(prvData); String password = null; if ((GenericUtils.length(prvEncryption) > 0) && (!NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption))) { password = passwordProvider.getPassword(resourceKey); } if (GenericUtils.isEmpty(prvEncryption) || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption) || GenericUtils.isEmpty(password)) { return loadKeyPairs(resourceKey, pubBytes, prvBytes); } // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc" int pos = prvEncryption.indexOf('-'); if (pos <= 0) { throw new StreamCorruptedException("Missing private key encryption mode in " + prvEncryption); } String mode = prvEncryption.substring(pos + 1).toUpperCase(); String algName = null; int numBits = 0; for (int index = 0; index < pos; index++) { char ch = prvEncryption.charAt(index); if ((ch >= '0') && (ch <= '9')) { algName = prvEncryption.substring(0, index).toUpperCase(); numBits = Integer.parseInt(prvEncryption.substring(index, pos)); break; } } if (GenericUtils.isEmpty(algName) || (numBits <= 0)) { throw new StreamCorruptedException( "Missing private key encryption algorithm details in " + prvEncryption); } prvBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes( prvBytes, algName, numBits, mode, password); return loadKeyPairs(resourceKey, pubBytes, prvBytes); }
@Override public void upload(Path[] local, String remote, Option... options) throws IOException { upload( local, remote, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options)); }
@Override public void upload(Path local, String remote, Collection<Option> options) throws IOException { upload( new Path[] {ValidateUtils.checkNotNull(local, "Invalid local argument: %s", local)}, remote, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options)); }
/** * @param <E> The generic entry type * @param entry The {@link PublicKeyEntry} whose contents are to be updated - ignored if {@code * null} * @param data Assumed to contain at least {@code key-type base64-data} (anything beyond the * BASE64 data is ignored) - ignored if {@code null}/empty * @return The updated entry instance * @throws IllegalArgumentException if bad format found */ public static final <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String data) throws IllegalArgumentException { if (GenericUtils.isEmpty(data) || (entry == null)) { return entry; } int startPos = data.indexOf(' '); if (startPos <= 0) { throw new IllegalArgumentException("Bad format (no key data delimiter): " + data); } int endPos = data.indexOf(' ', startPos + 1); if (endPos <= startPos) { // OK if no continuation beyond the BASE64 encoded data endPos = data.length(); } String keyType = data.substring(0, startPos); String b64Data = data.substring(startPos + 1, endPos).trim(); byte[] keyData = Base64.decodeString(b64Data); if (NumberUtils.isEmpty(keyData)) { throw new IllegalArgumentException("Bad format (no BASE64 key data): " + data); } entry.setKeyType(keyType); entry.setKeyData(keyData); return entry; }
@Override public void download(String[] remote, Path local, Option... options) throws IOException { download( remote, local, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options)); }
public SshdSocketAddress(String hostName, int port) { ValidateUtils.checkNotNull(hostName, "Host name may not be null"); this.hostName = GenericUtils.isEmpty(hostName) ? "0.0.0.0" : hostName; ValidateUtils.checkTrue(port >= 0, "Port must be >= 0", Integer.valueOf(port)); this.port = port; }
@Override public T getPath(String first, String... more) { StringBuilder sb = new StringBuilder(); if (!GenericUtils.isEmpty(first)) { appendDedupSep(sb, first.replace('\\', '/')); // in case we are running on Windows } if (GenericUtils.length(more) > 0) { for (String segment : more) { if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != '/')) { sb.append('/'); } // in case we are running on Windows appendDedupSep(sb, segment.replace('\\', '/')); } } if ((sb.length() > 1) && (sb.charAt(sb.length() - 1) == '/')) { sb.setLength(sb.length() - 1); } String path = sb.toString(); String root = null; if (path.startsWith("/")) { root = "/"; path = path.substring(1); } String[] names = GenericUtils.split(path, '/'); return create(root, names); }
/** * @param <R> The generic resource type * @param name Name of the resource - ignored if {@code null}/empty * @param c The {@link Comparator} to decide whether the {@link NamedResource#getName()} matches * the <tt>name</tt> parameter * @param resources The {@link NamedResource} to check - ignored if {@code null}/empty * @return The <U>first</U> resource whose name matches the parameter (by invoking {@link * Comparator#compare(Object, Object)} - {@code null} if no match found */ public static <R extends NamedResource> R findByName( String name, Comparator<? super String> c, Collection<? extends R> resources) { if (GenericUtils.isEmpty(name) || GenericUtils.isEmpty(resources)) { return null; } for (R r : resources) { String n = r.getName(); int nRes = c.compare(name, n); if (nRes == 0) { return r; } } return null; }
/** * @param data Assumed to contain at least {@code key-type base64-data} (anything beyond the * BASE64 data is ignored) - ignored if {@code null}/empty * @return A {@link PublicKeyEntry} or {@code null} if no data * @throws IllegalArgumentException if bad format found * @see #parsePublicKeyEntry(PublicKeyEntry, String) */ public static final PublicKeyEntry parsePublicKeyEntry(String data) throws IllegalArgumentException { if (GenericUtils.isEmpty(data)) { return null; } else { return parsePublicKeyEntry(new PublicKeyEntry(), data); } }
/** * Unregisters specified extension * * @param name The factory name - ignored if {@code null}/empty * @return The registered extension - {@code null} if not found */ public static SignatureFactory unregisterExtension(String name) { if (GenericUtils.isEmpty(name)) { return null; } synchronized (EXTENSIONS) { return EXTENSIONS.remove(name); } }
/** * @param name The file name - ignored if {@code null}/empty * @return The identity type - {@code null} if cannot determine it - e.g., does not start/end with * the {@link #ID_FILE_PREFIX}/{@link #ID_FILE_SUFFIX} */ public static String getIdentityType(String name) { if (GenericUtils.isEmpty(name) || (name.length() <= (ID_FILE_PREFIX.length() + ID_FILE_SUFFIX.length())) || (!name.startsWith(ID_FILE_PREFIX)) || (!name.endsWith(ID_FILE_SUFFIX))) { return null; } else { return name.substring(ID_FILE_PREFIX.length(), name.length() - ID_FILE_SUFFIX.length()); } }
/** * Checks if a path has strict permissions * * <UL> * <LI> * <p>(For {@code Unix}) The path may not have group or others write permissions * <LI> * <p>The path must be owned by current user. * <LI> * <p>(For {@code Unix}) The path may be owned by root. * </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 Pair} where {@link Pair#getClass()} is a loggable * message and {@link Pair#getSecond()} 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 Pair<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 Pair<String, Object>(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 Pair<String, Object>(String.format("Owner violation (%s)", owner), owner); } return null; }
/** * @param resources The named resources * @return A {@link List} of all the factories names - in same order as they appear in the input * collection */ public static List<String> getNameList(Collection<? extends NamedResource> resources) { if (GenericUtils.isEmpty(resources)) { return Collections.emptyList(); } List<String> names = new ArrayList<>(resources.size()); for (NamedResource r : resources) { names.add(r.getName()); } return names; }
/** * @param s The {@link Enum}'s name - ignored if {@code null}/empty * @return The matching {@link org.apache.sshd.common.signature.BuiltinSignatures} whose {@link * Enum#name()} matches (case <U>insensitive</U>) the provided argument - {@code null} if no * match */ public static BuiltinSignatures fromString(String s) { if (GenericUtils.isEmpty(s)) { return null; } for (BuiltinSignatures c : VALUES) { if (s.equalsIgnoreCase(c.name())) { return c; } } return null; }
/** * @param name The result name - ignored if {@code null}/empty * @return The matching {@link Result} value (case <U>insensitive</U>) or {@code null} if no * match found */ public static Result fromName(String name) { if (GenericUtils.isEmpty(name)) { return null; } for (Result r : VALUES) { if (name.equalsIgnoreCase(r.name())) { return r; } } return null; }
protected <T> void setOption( NetworkChannel socket, String property, SocketOption<T> option, T defaultValue) throws IOException { PropertyResolver manager = getFactoryManager(); String valStr = PropertyResolverUtils.getString(manager, property); T val = defaultValue; if (!GenericUtils.isEmpty(valStr)) { Class<T> type = option.type(); if (type == Integer.class) { val = type.cast(Integer.valueOf(valStr)); } else if (type == Boolean.class) { val = type.cast(Boolean.valueOf(valStr)); } else { throw new IllegalStateException("Unsupported socket option type " + type); } } if (val != null) { Collection<? extends SocketOption<?>> supported = socket.supportedOptions(); if ((GenericUtils.size(supported) <= 0) || (!supported.contains(option))) { log.warn( "Unsupported socket option (" + option + ") to set using property '" + property + "' value=" + val); return; } try { socket.setOption(option, val); if (log.isDebugEnabled()) { log.debug("setOption({})[{}] from property={}", option, val, property); } } catch (IOException | RuntimeException e) { log.warn( "Unable (" + e.getClass().getSimpleName() + ")" + " to set socket option " + option + " using property '" + property + "' value=" + val + ": " + e.getMessage()); } } }
/** * @param address The request bind address * @param handlerFactory A {@link Factory} to create an {@link IoHandler} if necessary * @return The {@link InetSocketAddress} to which the binding occurred * @throws IOException If failed to bind */ private InetSocketAddress doBind( SshdSocketAddress address, Factory<? extends IoHandler> handlerFactory) throws IOException { if (acceptor == null) { FactoryManager manager = session.getFactoryManager(); IoServiceFactory factory = manager.getIoServiceFactory(); IoHandler handler = handlerFactory.create(); acceptor = factory.createAcceptor(handler); } // TODO find a better way to determine the resulting bind address - what if multi-threaded // calls... Set<SocketAddress> before = acceptor.getBoundAddresses(); try { InetSocketAddress bindAddress = address.toInetSocketAddress(); acceptor.bind(bindAddress); Set<SocketAddress> after = acceptor.getBoundAddresses(); if (GenericUtils.size(after) > 0) { after.removeAll(before); } if (GenericUtils.isEmpty(after)) { throw new IOException( "Error binding to " + address + "[" + bindAddress + "]: no local addresses bound"); } if (after.size() > 1) { throw new IOException( "Multiple local addresses have been bound for " + address + "[" + bindAddress + "]"); } return (InetSocketAddress) after.iterator().next(); } catch (IOException bindErr) { Set<SocketAddress> after = acceptor.getBoundAddresses(); if (GenericUtils.isEmpty(after)) { close(); } throw bindErr; } }
/** * Reads configuration entries * * @param rdr The {@link BufferedReader} to use * @return The {@link List} of read {@link KnownHostEntry}-ies * @throws IOException If failed to parse the read configuration */ public static List<KnownHostEntry> readKnownHostEntries(BufferedReader rdr) throws IOException { List<KnownHostEntry> entries = null; int lineNumber = 1; for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) { line = GenericUtils.trimToEmpty(line); if (GenericUtils.isEmpty(line)) { continue; } int pos = line.indexOf(SshConfigFileReader.COMMENT_CHAR); if (pos == 0) { continue; } if (pos > 0) { line = line.substring(0, pos); line = line.trim(); } try { KnownHostEntry entry = parseKnownHostEntry(line); if (entry == null) { continue; } if (entries == null) { entries = new ArrayList<>(); } entries.add(entry); } catch (RuntimeException | Error e) { // TODO consider consulting a user callback throw new StreamCorruptedException( "Failed (" + e.getClass().getSimpleName() + ")" + " to parse line #" + lineNumber + " '" + line + "': " + e.getMessage()); } } if (entries == null) { return Collections.emptyList(); } else { return entries; } }
/** * @param name The factory name * @return The factory or {@code null} if it is neither a built-in one or a registered extension */ public static SignatureFactory resolveFactory(String name) { if (GenericUtils.isEmpty(name)) { return null; } SignatureFactory s = fromFactoryName(name); if (s != null) { return s; } synchronized (EXTENSIONS) { return EXTENSIONS.get(name); } }
public static <E extends KnownHostEntry> E parseKnownHostEntry(E entry, String data) { String line = data; if (GenericUtils.isEmpty(line) || (line.charAt(0) == PublicKeyEntry.COMMENT_CHAR)) { return entry; } entry.setConfigLine(line); if (line.charAt(0) == MARKER_INDICATOR) { int pos = line.indexOf(' '); ValidateUtils.checkTrue(pos > 0, "Missing marker name end delimiter in line=%s", data); ValidateUtils.checkTrue(pos > 1, "No marker name after indicator in line=%s", data); entry.setMarker(line.substring(1, pos)); line = line.substring(pos + 1).trim(); } else { entry.setMarker(null); } int pos = line.indexOf(' '); ValidateUtils.checkTrue(pos > 0, "Missing host patterns end delimiter in line=%s", data); String hostPattern = line.substring(0, pos); line = line.substring(pos + 1).trim(); if (hostPattern.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) { KnownHostHashValue hash = ValidateUtils.checkNotNull( KnownHostHashValue.parse(hostPattern), "Failed to extract host hash value from line=%s", data); entry.setHashedEntry(hash); entry.setPatterns(null); } else { entry.setHashedEntry(null); entry.setPatterns(parsePatterns(GenericUtils.split(hostPattern, ','))); } AuthorizedKeyEntry key = ValidateUtils.checkNotNull( AuthorizedKeyEntry.parseAuthorizedKeyEntry(line), "No valid key entry recovered from line=%s", data); entry.setKeyEntry(key); return entry; }
public static ParseResult parseSignatureList(Collection<String> sigs) { if (GenericUtils.isEmpty(sigs)) { return ParseResult.EMPTY; } List<SignatureFactory> factories = new ArrayList<>(sigs.size()); List<String> unknown = Collections.emptyList(); for (String name : sigs) { SignatureFactory s = resolveFactory(name); if (s != null) { factories.add(s); } else { // replace the (unmodifiable) empty list with a real one if (unknown.isEmpty()) { unknown = new ArrayList<>(); } unknown.add(name); } } return new ParseResult(factories, unknown); }
public static ParseResult parseSignatureList(String... sigs) { return parseSignatureList( GenericUtils.isEmpty((Object[]) sigs) ? Collections.emptyList() : Arrays.asList(sigs)); }
public static KnownHostEntry parseKnownHostEntry(String line) { return parseKnownHostEntry(GenericUtils.isEmpty(line) ? null : new KnownHostEntry(), line); }
protected AbstractSftpClientExtension( String name, SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions) { this( name, client, raw, GenericUtils.isEmpty(extensions) ? false : extensions.containsKey(name)); }
protected AbstractSftpClientExtension( String name, SftpClient client, RawSftpClient raw, Collection<String> extras) { this(name, client, raw, GenericUtils.isEmpty(extras) ? false : extras.contains(name)); }