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 >= 1</code> and <code>port <= 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 }