2 * Copyright 2000-2017 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package jetbrains.buildServer.buildTriggers.vcs.git.agent;
19 import com.jcraft.jsch.ChannelExec;
20 import com.jcraft.jsch.JSch;
21 import com.jcraft.jsch.Logger;
22 import com.jcraft.jsch.Session;
23 import jetbrains.buildServer.buildTriggers.vcs.git.GitUtils;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26 import org.jetbrains.git4idea.ssh.GitSSHHandler;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Date;
33 import java.util.List;
35 public class JSchClient {
37 private final static int BUF_SIZE = 32 * 1024;
39 private final String myHost;
40 private final String myUsername;
41 private final Integer myPort;
42 private final String myCommand;
43 private final Logger myLogger;
45 private JSchClient(@NotNull String host,
46 @Nullable String username,
47 @Nullable Integer port,
48 @NotNull String command,
49 @NotNull Logger logger) {
51 myUsername = username;
58 public static void main(String... args) {
59 boolean debug = Boolean.parseBoolean(System.getenv(GitSSHHandler.TEAMCITY_DEBUG_SSH));
60 InMemoryLogger logger = new InMemoryLogger(debug ? Logger.DEBUG : Logger.INFO);
62 JSchClient ssh = createClient(logger, args);
64 } catch (Throwable t) {
66 System.err.println(t.getMessage());
67 if (t instanceof NullPointerException || debug)
74 private static JSchClient createClient(@NotNull Logger logger, String[] args) {
75 if (args.length != 2 && args.length != 4) {
76 System.err.println("Invalid arguments " + Arrays.asList(args));
82 //noinspection HardCodedStringLiteral
83 if ("-p".equals(args[i])) {
85 port = Integer.parseInt(args[i++]);
87 String host = args[i++];
89 int atIndex = host.lastIndexOf('@');
94 user = host.substring(0, atIndex);
95 host = host.substring(atIndex + 1);
97 String command = args[i];
98 return new JSchClient(host, user, port, command, logger);
102 public void run() throws Exception {
103 ChannelExec channel = null;
104 Session session = null;
106 JSch.setLogger(myLogger);
107 JSch jsch = new JSch();
108 String privateKeyPath = System.getenv(GitSSHHandler.TEAMCITY_PRIVATE_KEY_PATH);
109 if (privateKeyPath != null) {
110 jsch.addIdentity(privateKeyPath, System.getenv(GitSSHHandler.TEAMCITY_PASSPHRASE));
112 String userHome = System.getProperty("user.home");
113 if (userHome != null) {
114 File homeDir = new File(userHome);
115 File ssh = new File(homeDir, ".ssh");
116 File rsa = new File(ssh, "id_rsa");
118 jsch.addIdentity(rsa.getAbsolutePath());
120 File dsa = new File(ssh, "id_dsa");
122 jsch.addIdentity(dsa.getAbsolutePath());
126 session = jsch.getSession(myUsername, myHost, myPort != null ? myPort : 22);
128 String teamCityVersion = System.getenv(GitSSHHandler.TEAMCITY_VERSION);
129 if (teamCityVersion != null) {
130 session.setClientVersion(GitUtils.getSshClientVersion(session.getClientVersion(), teamCityVersion));
133 if (Boolean.parseBoolean(System.getenv(GitSSHHandler.SSH_IGNORE_KNOWN_HOSTS_ENV))) {
134 session.setConfig("StrictHostKeyChecking", "no");
139 channel = (ChannelExec) session.openChannel("exec");
140 channel.setPty(false);
141 channel.setCommand(myCommand);
142 channel.setInputStream(System.in);
143 channel.setErrStream(System.err);
146 InputStream input = channel.getInputStream();
147 byte[] buffer = new byte[BUF_SIZE];
149 while ((count = input.read(buffer)) != -1) {
150 System.out.write(buffer, 0, count);
154 channel.disconnect();
156 session.disconnect();
161 private static class InMemoryLogger implements Logger {
162 private final int myMinLogLevel;
163 private final List<LogEntry> myLogEntries;
164 InMemoryLogger(int minLogLevel) {
165 myMinLogLevel = minLogLevel;
166 myLogEntries = new ArrayList<LogEntry>();
170 public boolean isEnabled(final int level) {
171 return level >= myMinLogLevel;
175 public void log(final int level, final String message) {
176 if (isEnabled(level)) {
177 synchronized (myLogEntries) {
178 myLogEntries.add(new LogEntry(System.currentTimeMillis(), level, message));
184 SimpleDateFormat dateFormat = new SimpleDateFormat("[HH:mm:ss.SSS]");
185 synchronized (myLogEntries) {
186 for (LogEntry entry : myLogEntries) {
187 System.err.print(dateFormat.format(new Date(entry.myTimestamp)));
188 System.err.print(" ");
189 System.err.print(getLevel(entry.myLogLevel));
190 System.err.print(" ");
191 System.err.println(entry.myMessage);
197 private String getLevel(int level) {
214 private static class LogEntry {
215 private final long myTimestamp;
216 private final int myLogLevel;
217 private final String myMessage;
218 LogEntry(long timestamp, int logLevel, @NotNull String message) {
219 myTimestamp = timestamp;
220 myLogLevel = logLevel;