001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.plugins.repository.ssh;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.util.HashMap;
023: import java.util.Locale;
024: import java.util.Map;
025:
026: import org.apache.ivy.core.IvyContext;
027: import org.apache.ivy.core.event.IvyEvent;
028: import org.apache.ivy.core.event.IvyListener;
029: import org.apache.ivy.core.event.resolve.EndResolveEvent;
030: import org.apache.ivy.util.Credentials;
031: import org.apache.ivy.util.CredentialsUtil;
032: import org.apache.ivy.util.Message;
033:
034: import com.jcraft.jsch.ChannelSftp;
035: import com.jcraft.jsch.JSch;
036: import com.jcraft.jsch.JSchException;
037: import com.jcraft.jsch.Session;
038: import com.jcraft.jsch.UserInfo;
039:
040: /**
041: * a class to cache SSH Connections and Channel for the SSH Repository each session is defined by
042: * connecting user / host / port two maps are used to find cache entries one map is using the above
043: * keys, the other uses the session itself
044: */
045: public final class SshCache {
046:
047: private static final int SSH_DEFAULT_PORT = 22;
048:
049: private SshCache() {
050: };
051:
052: private static SshCache instance = new SshCache();
053:
054: public static SshCache getInstance() {
055: return instance;
056: }
057:
058: private class Entry {
059: private Session session = null;
060:
061: private ChannelSftp channelSftp = null;
062:
063: private String host = null;
064:
065: private String user = null;
066:
067: private int port = SSH_DEFAULT_PORT;
068:
069: /**
070: * @return the host
071: */
072: public String getHost() {
073: return host;
074: }
075:
076: /**
077: * @return the port
078: */
079: public int getPort() {
080: return port;
081: }
082:
083: /**
084: * @return the user
085: */
086: public String getUser() {
087: return user;
088: }
089:
090: public Entry(Session newSession, String newUser,
091: String newHost, int newPort) {
092: session = newSession;
093: host = newHost;
094: user = newUser;
095: port = newPort;
096: IvyContext.getContext().getEventManager().addIvyListener(
097: new IvyListener() {
098: public void progress(IvyEvent event) {
099: event.getSource().removeIvyListener(this );
100: clearSession(session);
101: }
102: }, EndResolveEvent.NAME);
103: }
104:
105: /**
106: * attach an sftp channel to this cache entry
107: *
108: * @param channelSftp
109: * to attach
110: */
111: public void setChannelSftp(ChannelSftp newChannel) {
112: if (channelSftp != null && newChannel != null) {
113: throw new IllegalStateException(
114: "Only one sftp channelSftp per session allowed");
115: }
116: this .channelSftp = newChannel;
117: }
118:
119: /**
120: * @return the attached sftp channel
121: */
122: public ChannelSftp getChannelSftp() {
123: return channelSftp;
124: }
125:
126: /**
127: * @return the session
128: */
129: private Session getSession() {
130: return session;
131: }
132:
133: /**
134: * remove channelSftp and disconnect if necessary
135: */
136: public void releaseChannelSftp() {
137: if (channelSftp != null) {
138: if (channelSftp.isConnected()) {
139: Message
140: .verbose(":: SFTP :: closing sftp connection from "
141: + host + "...");
142: channelSftp.disconnect();
143: channelSftp = null;
144: Message
145: .verbose(":: SFTP :: sftp connection closed from "
146: + host);
147: }
148: }
149: }
150: }
151:
152: /**
153: * key is username / host / port
154: *
155: * @see SshCache.createCacheKey() for details
156: */
157: private Map uriCacheMap = new HashMap();
158:
159: /**
160: * key is the session itself
161: */
162: private Map sessionCacheMap = new HashMap();
163:
164: /**
165: * retrieves a session entry for a given hostname from the cache
166: *
167: * @param hostname
168: * to retrieve session for
169: * @return null or the existing entry
170: */
171: private Entry getCacheEntry(String user, String host, int port) {
172: return (Entry) uriCacheMap
173: .get(createCacheKey(user, host, port));
174: }
175:
176: /**
177: * Creates a cobined cache key from the given key parts
178: *
179: * @param user
180: * name of the user
181: * @param host
182: * of the connection
183: * @param port
184: * of the connection
185: * @return key for the cache
186: */
187: private static String createCacheKey(String user, String host,
188: int port) {
189: String portToUse = "22";
190: if (port != -1 && port != SSH_DEFAULT_PORT) {
191: portToUse = Integer.toString(port);
192: }
193: return user.toLowerCase(Locale.US).trim() + "@"
194: + host.toLowerCase(Locale.US).trim() + ":" + portToUse;
195: }
196:
197: /**
198: * retrieves a session entry for a given session from the cache
199: *
200: * @param session
201: * to retrieve cache entry for
202: * @return null or the existing entry
203: */
204: private Entry getCacheEntry(Session session) {
205: return (Entry) sessionCacheMap.get(session);
206: }
207:
208: /**
209: * Sets a session to a given combined key into the cache If an old session object already
210: * exists, close and remove it
211: *
212: * @param user
213: * of the session
214: * @param host
215: * of the session
216: * @param port
217: * of the session
218: * @param newSession
219: * Session to save
220: */
221: private void setSession(String user, String host, int port,
222: Session newSession) {
223: Entry entry = (Entry) uriCacheMap.get(createCacheKey(user,
224: host, port));
225: Session oldSession = null;
226: if (entry != null) {
227: oldSession = entry.getSession();
228: }
229: if (oldSession != null && !oldSession.equals(newSession)
230: && oldSession.isConnected()) {
231: entry.releaseChannelSftp();
232: String oldhost = oldSession.getHost();
233: Message.verbose(":: SSH :: closing ssh connection from "
234: + oldhost + "...");
235: oldSession.disconnect();
236: Message.verbose(":: SSH :: ssh connection closed from "
237: + oldhost);
238: }
239: if ((newSession == null) && (entry != null)) {
240: uriCacheMap.remove(createCacheKey(user, host, port));
241: if (entry.getSession() != null) {
242: sessionCacheMap.remove(entry.getSession());
243: }
244: } else {
245: Entry newEntry = new Entry(newSession, user, host, port);
246: uriCacheMap.put(createCacheKey(user, host, port), newEntry);
247: sessionCacheMap.put(newSession, newEntry);
248: }
249: }
250:
251: /**
252: * discardes session entries from the cache
253: *
254: * @param session
255: * to clear
256: */
257: public void clearSession(Session session) {
258: Entry entry = (Entry) sessionCacheMap.get(session);
259: if (entry != null) {
260: setSession(entry.getUser(), entry.getHost(), entry
261: .getPort(), null);
262: }
263: }
264:
265: /**
266: * retrieves an sftp channel from the cache
267: *
268: * @param session
269: * to connect to
270: * @return channelSftp or null if not successful (channel not existent or dead)
271: */
272: public ChannelSftp getChannelSftp(Session session)
273: throws IOException {
274: ChannelSftp channel = null;
275: Entry entry = getCacheEntry(session);
276: if (entry != null) {
277: channel = entry.getChannelSftp();
278: if (channel != null && !channel.isConnected()) {
279: entry.releaseChannelSftp();
280: channel = null;
281: }
282: }
283: return channel;
284: }
285:
286: /**
287: * attaches a channelSftp to an existing session cache entry
288: *
289: * @param session
290: * to attach the channel to
291: * @param channel
292: * channel to attach
293: */
294: public void attachChannelSftp(Session session, ChannelSftp channel) {
295: Entry entry = getCacheEntry(session);
296: if (entry == null) {
297: throw new IllegalArgumentException("No entry for "
298: + session + " in the cache");
299: }
300: entry.setChannelSftp(channel);
301: }
302:
303: /**
304: * Gets a session from the cache or establishes a new session if necessary
305: *
306: * @param host
307: * to connect to
308: * @param port
309: * to use for session (-1 == use standard port)
310: * @param username
311: * for the session to use
312: * @param userPassword
313: * to use for authentication (optional)
314: * @param pemFile
315: * File to use for public key authentication
316: * @param pemPassword
317: * to use for accessing the pemFile (optional)
318: * @param passFile
319: * to store credentials
320: * @return session or null if not successful
321: */
322: public Session getSession(String host, int port, String username,
323: String userPassword, File pemFile, String pemPassword,
324: File passFile) throws IOException {
325: Entry entry = getCacheEntry(username, host, port);
326: Session session = null;
327: if (entry != null) {
328: session = entry.getSession();
329: }
330: if (session == null || !session.isConnected()) {
331: Message.verbose(":: SSH :: connecting to " + host + "...");
332: try {
333: JSch jsch = new JSch();
334: if (port != -1) {
335: session = jsch.getSession(username, host, port);
336: } else {
337: session = jsch.getSession(username, host);
338: }
339: if (pemFile != null) {
340: jsch.addIdentity(pemFile.getAbsolutePath(),
341: pemPassword);
342: }
343: session.setUserInfo(new CfUserInfo(host, username,
344: userPassword, pemFile, pemPassword, passFile));
345: session.connect();
346: Message.verbose(":: SSH :: connected to " + host + "!");
347: setSession(username, host, port, session);
348: } catch (JSchException e) {
349: if (passFile != null && passFile.exists()) {
350: passFile.delete();
351: }
352: IOException ex = new IOException(e.getMessage());
353: ex.initCause(e);
354: throw ex;
355: }
356: }
357: return session;
358: }
359:
360: /**
361: * feeds in password silently into JSch
362: */
363: private static class CfUserInfo implements UserInfo {
364:
365: private String userPassword;
366:
367: private String pemPassword;
368:
369: private String userName;
370:
371: private final File pemFile;
372:
373: private final String host;
374:
375: private final File passfile;
376:
377: public CfUserInfo(String host, String userName,
378: String userPassword, File pemFile, String pemPassword,
379: File passfile) {
380: this .userPassword = userPassword;
381: this .pemPassword = pemPassword;
382: this .host = host;
383: this .passfile = passfile;
384: this .userName = userName;
385: this .pemFile = pemFile;
386: }
387:
388: public void showMessage(String message) {
389: Message.info(message);
390: }
391:
392: public boolean promptYesNo(String message) {
393: return true;
394: }
395:
396: public boolean promptPassword(String message) {
397: return true;
398: }
399:
400: public boolean promptPassphrase(String message) {
401: return true;
402: }
403:
404: public String getPassword() {
405: if (userPassword == null) {
406: Credentials c = CredentialsUtil.promptCredentials(
407: new Credentials(null, host, userName,
408: userPassword), passfile);
409: if (c != null) {
410: userName = c.getUserName();
411: userPassword = c.getPasswd();
412: }
413: }
414: return userPassword;
415: }
416:
417: public String getPassphrase() {
418: if (pemPassword == null && pemFile != null) {
419: Credentials c = CredentialsUtil.promptCredentials(
420: new Credentials(null,
421: pemFile.getAbsolutePath(), userName,
422: pemPassword), passfile);
423: if (c != null) {
424: userName = c.getUserName();
425: pemPassword = c.getPasswd();
426: }
427: }
428: return pemPassword;
429: }
430: }
431: }
|