Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 567f45514eb6383f9cae364b37c2df23f68e8d9d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Atsuhiko Yamanaka, JCraft,Inc. - initial API and implementation.
 *     IBM Corporation - ongoing maintenance
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.ssh2;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.NoRouteToHostException;
import java.net.UnknownHostException;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.IServerConnection;
import org.eclipse.team.internal.ccvs.core.connection.CVSAuthenticationException;
import org.eclipse.team.internal.core.streams.PollingInputStream;
import org.eclipse.team.internal.core.streams.PollingOutputStream;
import org.eclipse.team.internal.core.streams.TimeoutInputStream;
import org.eclipse.team.internal.core.streams.TimeoutOutputStream;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;

/**
 * SSH2 connection method. Has the property of defaulting to SSH1 if the server
 * doesn't support SSH2. 
 */
public class CVSSSH2ServerConnection implements IServerConnection {
	
	private static final String SSH1_COMPATIBILITY_CLASS = "org.eclipse.team.internal.ccvs.ssh.SSHServerConnection"; //$NON-NLS-1$
	
	private final class SSH2IOException extends IOException {
		private static final long serialVersionUID = 1L;

		private final JSchException e;

		SSH2IOException(String s, JSchException e) {
			super(s);
			this.e = e;
		}

		public Throwable getCause() {
			return e;
		}
	}
	private static final String COMMAND = "cvs server"; //$NON-NLS-1$
	private ICVSRepositoryLocation location;
	private String password;
	private InputStream inputStream;
	private OutputStream outputStream;
	private JSchSession session;
	private Channel channel;
	private IServerConnection ssh1;
	
	protected CVSSSH2ServerConnection(ICVSRepositoryLocation location, String password) {
		this.location = location;
		this.password = password;
	}
	public void close() throws IOException {
		if (ssh1 != null) {
			ssh1.close();
			ssh1 = null;
			return;
		}
		try {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					// Ignore I/O Exception on close
				}
			}
		} finally {
			try {
				if (outputStream != null) {
					try {
						outputStream.close();
					} catch (IOException e) {
						// Ignore I/O Exception on close
					}
				}
			} finally {
				if (channel != null)
					channel.disconnect();
			}
		} 
	}
	public InputStream getInputStream() {
		if (ssh1 != null) {
			return ssh1.getInputStream();
		}
		return inputStream;
	}
	public OutputStream getOutputStream() {
		if (ssh1 != null) {
			return ssh1.getOutputStream();
		}
		return outputStream;
	}
	public void open(IProgressMonitor monitor) throws IOException, CVSAuthenticationException {
		if (ssh1 != null) {
			ssh1.open(monitor);
			return;
		}
		monitor.subTask(NLS.bind(CVSSSH2Messages.CVSSSH2ServerConnection_open, new String[] { location.getHost() })); 
		monitor.worked(1);
		internalOpen(monitor);
	}
	/**
	 * @param monitor
	 * @throws IOException
	 * @throws CVSAuthenticationException
	 */
	private void internalOpen(IProgressMonitor monitor) throws IOException, CVSAuthenticationException {
		try {
			OutputStream channel_out = null;
			InputStream channel_in = null;
			boolean firstTime = true;
			boolean tryAgain = false;
			while (firstTime || tryAgain) {
				tryAgain = false; // reset the try again flag
				session = JSchSession.getSession(location, location.getUsername(), password, location.getHost(), location.getPort(), monitor);
				channel = session.getSession().openChannel("exec"); //$NON-NLS-1$
				((ChannelExec) channel).setCommand(COMMAND);
				channel_out = channel.getOutputStream();
				channel_in = channel.getInputStream();
				try {
					channel.connect();
				} catch (JSchException ee) {
					// This strange logic is here due to how the JSch client shares sessions.
					// It is possible that we have obtained a session that thinks it is connected
					// but is not. Channel connection only works if the session is connected so the
					// above channel connect may fail because the session is down. For this reason,
					// we want to retry if the connection fails.
					try {
						if (firstTime && (isSessionDownError(ee) || isChannelNotOpenError(ee))) {
							tryAgain = true;
						}
						if (!tryAgain) {
							throw ee;
						}
					} finally {
						// Always dispose of the current session when a failure occurs so we can start from scratch
						session.dispose();
					}
				}
				firstTime = false; // the first time is done
			}
			int timeout = location.getTimeout();
			inputStream = new PollingInputStream(new TimeoutInputStream(new FilterInputStream(channel_in) {
						public void close() {
							// Don't close the underlying stream as it belongs to the session
						}
					},
					8192 /*bufferSize*/, (timeout>0 ? 1000 : 0) /*readTimeout*/, -1 /*closeTimeout*/, true /* growWhenFull */), timeout > 0 ? timeout : 1, monitor);
			outputStream = new PollingOutputStream(new TimeoutOutputStream(new FilterOutputStream(channel_out) {
						public void close() {
							// Don't close the underlying stream as it belongs to the session
						}
					},
					8192 /*buffersize*/, (timeout>0 ? 1000 : 0) /*writeTimeout*/, (timeout>0 ? 1000 : 0) /*closeTimeout*/), timeout > 0 ? timeout : 1, monitor);
		} catch (final JSchException e) {
			if (isSSH2Unsupported(e)) {
				ssh1 = createSSH1Connection();
				if (ssh1 == null) {
					throw new SSH2IOException(
							CVSSSH2Messages.CVSSSH2ServerConnection_4, e);
				}
				ssh1.open(monitor);
			} else {
				String message = e.getMessage();
				if (JSchSession.isAuthenticationFailure(e)) {
					// Do not retry as the Jsh library has it's own retry logic
					throw new CVSAuthenticationException(CVSSSH2Messages.CVSSSH2ServerConnection_0, CVSAuthenticationException.NO_RETRY,location, e); 
				} else if (message.startsWith("Session.connect: ")) { //$NON-NLS-1$
					// Jsh has messages formatted like "Session.connect: java.net.NoRouteToHostException: ..."
					// Strip of the exception and try to convert it to a more meaningfull string
					int start = message.indexOf(": ") + 1; //$NON-NLS-1$
					if (start != -1) {
						int end = message.indexOf(": ", start); //$NON-NLS-1$
						if (end != -1) {
							String exception = message.substring(start, end).trim();
							if (exception.indexOf("NoRouteToHostException") != -1) { //$NON-NLS-1$
								message = NLS.bind(CVSSSH2Messages.CVSSSH2ServerConnection_1, new String[] { location.getHost() }); 
								throw new NoRouteToHostException(message);
							} else if (exception.indexOf("java.net.UnknownHostException") != -1) { //$NON-NLS-1$
								throw new UnknownHostException(location.getHost());
							} else {
								message = message.substring(end + 1).trim();
							}
						}
					}
				}
				throw new SSH2IOException(message, e);
			}
		}
	}
	
	/**
	 * Returns SSH-1 connection.
	 * 
	 * @return a connection or <code>null</code>, if SSH-1 is not supported
	 */
	private IServerConnection createSSH1Connection() {
		try {
			return (IServerConnection) Class.forName(SSH1_COMPATIBILITY_CLASS)
					.getConstructor(
							new Class[] { ICVSRepositoryLocation.class,
									String.class }).newInstance(
							new Object[] { location, password });
		} catch (IllegalArgumentException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (SecurityException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (InstantiationException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (InvocationTargetException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (NoSuchMethodException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		} catch (ClassNotFoundException e1) {
			if (Policy.DEBUG)
				e1.printStackTrace();
		}
		return null;
	}
	
	private boolean isChannelNotOpenError(JSchException ee) {
		return ee.getMessage().indexOf("channel is not opened") != -1; //$NON-NLS-1$
	}
	private boolean isSessionDownError(JSchException ee) {
		return ee.getMessage().equals("session is down"); //$NON-NLS-1$
	}
	private boolean isSSH2Unsupported(JSchException e) {
		return e.toString().indexOf("invalid server's version string") != -1; //$NON-NLS-1$
	}
}

Back to the top