Add logger to ssh client
[teamcity/git-plugin.git] / git-agent / src / jetbrains / buildServer / buildTriggers / vcs / git / agent / JSchClient.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package jetbrains.buildServer.buildTriggers.vcs.git.agent;
18
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;
27
28 import java.io.*;
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;
34
35 public class JSchClient {
36
37   private final static int BUF_SIZE = 32 * 1024;
38
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;
44
45   private JSchClient(@NotNull String host,
46                      @Nullable String username,
47                      @Nullable Integer port,
48                      @NotNull String command,
49                      @NotNull Logger logger) {
50     myHost = host;
51     myUsername = username;
52     myPort = port;
53     myCommand = command;
54     myLogger = logger;
55   }
56
57
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);
61     try {
62       JSchClient ssh = createClient(logger, args);
63       ssh.run();
64     } catch (Throwable t) {
65       logger.printLog();
66       System.err.println(t.getMessage());
67       if (t instanceof NullPointerException || debug)
68         t.printStackTrace();
69       System.exit(1);
70     }
71   }
72
73
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));
77       System.exit(1);
78     }
79
80     int i = 0;
81     Integer port = null;
82     //noinspection HardCodedStringLiteral
83     if ("-p".equals(args[i])) {
84       i++;
85       port = Integer.parseInt(args[i++]);
86     }
87     String host = args[i++];
88     String user;
89     int atIndex = host.lastIndexOf('@');
90     if (atIndex == -1) {
91       user = null;
92     }
93     else {
94       user = host.substring(0, atIndex);
95       host = host.substring(atIndex + 1);
96     }
97     String command = args[i];
98     return new JSchClient(host, user, port, command, logger);
99   }
100
101
102   public void run() throws Exception {
103     ChannelExec channel = null;
104     Session session = null;
105     try {
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));
111       } else {
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");
117           if (rsa.isFile()) {
118             jsch.addIdentity(rsa.getAbsolutePath());
119           }
120           File dsa = new File(ssh, "id_dsa");
121           if (dsa.isFile()) {
122             jsch.addIdentity(dsa.getAbsolutePath());
123           }
124         }
125       }
126       session = jsch.getSession(myUsername, myHost, myPort != null ? myPort : 22);
127
128       String teamCityVersion = System.getenv(GitSSHHandler.TEAMCITY_VERSION);
129       if (teamCityVersion != null) {
130         session.setClientVersion(GitUtils.getSshClientVersion(session.getClientVersion(), teamCityVersion));
131       }
132
133       if (Boolean.parseBoolean(System.getenv(GitSSHHandler.SSH_IGNORE_KNOWN_HOSTS_ENV))) {
134         session.setConfig("StrictHostKeyChecking", "no");
135       }
136
137       session.connect();
138
139       channel = (ChannelExec) session.openChannel("exec");
140       channel.setPty(false);
141       channel.setCommand(myCommand);
142       channel.setInputStream(System.in);
143       channel.setErrStream(System.err);
144       channel.connect();
145
146       InputStream input = channel.getInputStream();
147       byte[] buffer = new byte[BUF_SIZE];
148       int count;
149       while ((count = input.read(buffer)) != -1) {
150         System.out.write(buffer, 0, count);
151       }
152     } finally {
153       if (channel != null)
154         channel.disconnect();
155       if (session != null)
156         session.disconnect();
157     }
158   }
159
160
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>();
167     }
168
169     @Override
170     public boolean isEnabled(final int level) {
171       return level >= myMinLogLevel;
172     }
173
174     @Override
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));
179         }
180       }
181     }
182
183     void printLog() {
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);
192         }
193       }
194     }
195
196     @NotNull
197     private String getLevel(int level) {
198       switch (level) {
199         case Logger.DEBUG:
200           return "DEBUG";
201         case Logger.INFO:
202           return "INFO";
203         case Logger.WARN:
204           return "WARN";
205         case Logger.ERROR:
206           return "ERROR";
207         case Logger.FATAL:
208           return "FATAL";
209         default:
210           return "UNKNOWN";
211       }
212     }
213
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;
221         myMessage = message;
222       }
223     }
224   }
225 }