001    /**
002     * Copyright 2010-2012 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     */
016    package org.kuali.common.util.secure;
017    
018    import java.io.File;
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.Collections;
022    import java.util.List;
023    import java.util.Properties;
024    
025    import org.apache.commons.io.FileUtils;
026    import org.apache.commons.lang3.StringUtils;
027    import org.kuali.common.util.LocationUtils;
028    import org.kuali.common.util.PropertyUtils;
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    import org.springframework.util.Assert;
032    
033    public 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    }