001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util.channel.util;
017
018import java.io.File;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.List;
023import java.util.Properties;
024
025import org.apache.commons.io.FileUtils;
026import org.apache.commons.lang3.StringUtils;
027import org.kuali.common.util.LocationUtils;
028import org.kuali.common.util.PropertyUtils;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031import org.springframework.util.Assert;
032
033public class SSHUtils {
034
035        private static final Logger logger = LoggerFactory.getLogger(SSHUtils.class);
036
037        private static final String FS = File.separator;
038        private static final String IDENTITY_FILE = "IdentityFile";
039        private static final String TILDE = "~";
040        private static final String USER_HOME = FileUtils.getUserDirectoryPath();
041        private static final String SSHDIR = USER_HOME + FS + ".ssh";
042        private static final String IDENTITY = SSHDIR + FS + "identity";
043        private static final String ID_DSA = SSHDIR + FS + "id_dsa";
044        private static final String ID_RSA = SSHDIR + FS + "id_rsa";
045        private static final String ID_ECDSA = SSHDIR + FS + "id_ecdsa";
046        private static final int PORT_NUMBER_LOWEST = 1;
047        private static final int PORT_NUMBER_HIGHEST = 65535;
048
049        public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
050        public static final String NO = "no";
051        public static final List<String> PRIVATE_KEY_DEFAULTS = Arrays.asList(IDENTITY, ID_DSA, ID_RSA, ID_ECDSA);
052        public static final File DEFAULT_CONFIG_FILE = new File(SSHDIR + FS + "config");
053        public static final int DEFAULT_PORT = 22;
054        public static final File DEFAULT_KNOWN_HOSTS = new File(SSHDIR + FS + "known_hosts");
055
056        /**
057         * Return true if <code>port &gt;= 1</code> and <code>port &lt;= 65535</code>, false otherwise.
058         */
059        public static final boolean isValidPort(int port) {
060                return port >= PORT_NUMBER_LOWEST && port <= PORT_NUMBER_HIGHEST;
061        }
062
063        public static final void addPort(List<String> args, String portOption, int port, int defaultPort) {
064                if (port != defaultPort) {
065                        Assert.isTrue(SSHUtils.isValidPort(port));
066                        logger.debug("port={}", port);
067                        args.add(portOption);
068                        args.add(Integer.toString(port));
069                }
070        }
071
072        public static final void addOptions(List<String> args, Properties options) {
073                if (options == null) {
074                        return;
075                }
076                List<String> keys = PropertyUtils.getSortedKeys(options);
077                for (String key : keys) {
078                        String value = options.getProperty(key);
079                        logger.debug("Adding option [-o {}={}]", key, value);
080                        args.add("-o");
081                        args.add(key + "=" + value);
082                }
083        }
084
085        public static final void addConfigFile(List<String> args, File configFile, File defaultConfigFile) {
086                if (configFile == null) {
087                        return;
088                }
089                String defaultPath = LocationUtils.getCanonicalPath(defaultConfigFile);
090                String configFilePath = LocationUtils.getCanonicalPath(configFile);
091                if (!StringUtils.equals(defaultPath, configFilePath)) {
092                        logger.debug("SSH config=[{}]", configFilePath);
093                        args.add("-F");
094                        args.add(configFilePath);
095                }
096        }
097
098        public static final void addIdentityFile(List<String> args, File identityFile) {
099                if (identityFile != null) {
100                        String path = LocationUtils.getCanonicalPath(identityFile);
101                        logger.debug("Private key=[{}]", path);
102                        args.add("-i");
103                        args.add(path);
104                }
105        }
106
107        /**
108         * Return a non-null list containing any private keys found by examining default private key locations in <code>~/.ssh</code> and
109         * parsing <code>config</code>. Any files returned by this method are guaranteed to exist and be readable.
110         */
111        public static final List<File> getPrivateKeys(File config) {
112                List<String> paths = getFilenames(config);
113                return getExistingAndReadable(paths);
114        }
115
116        /**
117         * Return a non-null list containing any private keys found by examining default private key locations in <code>~/.ssh</code> and
118         * parsing <code>~/.ssh/config</code>. Any files returned by this method are guaranteed to exist and be readable.
119         */
120        public static final List<File> getDefaultPrivateKeys() {
121                return getPrivateKeys(DEFAULT_CONFIG_FILE);
122        }
123
124        public static final Properties getDefaultOptions() {
125                Properties options = new Properties();
126                options.setProperty(STRICT_HOST_KEY_CHECKING, NO);
127                return options;
128        }
129
130        public static final List<File> getExistingAndReadable(List<String> filenames) {
131                List<File> files = new ArrayList<File>();
132                for (String filename : filenames) {
133                        File file = new File(filename);
134                        if (file.exists() && file.canRead()) {
135                                files.add(file);
136                        }
137                }
138                return files;
139        }
140
141        public static final List<String> getFilenames(File config) {
142                if (config.exists() && config.canRead()) {
143                        List<String> lines = LocationUtils.readLines(config);
144                        List<String> identityFileLines = getIdentityFileLines(lines);
145                        return getFilenames(identityFileLines);
146                } else {
147                        return Collections.<String> emptyList();
148                }
149        }
150
151        public static final List<String> getIdentityFileLines(List<String> lines) {
152                List<String> identityFileLines = new ArrayList<String>();
153                for (String line : lines) {
154                        String trimmed = StringUtils.trim(line);
155                        if (StringUtils.startsWith(trimmed, IDENTITY_FILE)) {
156                                identityFileLines.add(trimmed);
157                        }
158                }
159                return identityFileLines;
160        }
161
162        public static final List<String> getFilenames(List<String> identityFileLines) {
163                List<String> filenames = new ArrayList<String>();
164                for (String identityFileLine : identityFileLines) {
165                        String originalFilename = StringUtils.substring(identityFileLine, IDENTITY_FILE.length());
166                        String resolvedFilename = StringUtils.replace(originalFilename, TILDE, USER_HOME);
167                        String trimmed = StringUtils.trim(resolvedFilename);
168                        filenames.add(trimmed);
169                }
170                return filenames;
171        }
172}