35
35
36
36
import java .io .BufferedInputStream ;
37
37
import java .io .BufferedOutputStream ;
38
+ import java .io .BufferedReader ;
38
39
import java .io .ByteArrayInputStream ;
39
40
import java .io .ByteArrayOutputStream ;
41
+ import java .io .Closeable ;
40
42
import java .io .DataInputStream ;
41
43
import java .io .DataOutputStream ;
42
44
import java .io .File ;
43
45
import java .io .FileInputStream ;
44
46
import java .io .IOException ;
45
47
import java .io .InputStream ;
46
48
import java .io .OutputStream ;
49
+ import java .io .PrintStream ;
50
+ import java .io .StringReader ;
47
51
import java .net .HttpURLConnection ;
48
52
import java .net .InetSocketAddress ;
53
+ import java .net .Proxy ;
54
+ import java .net .ProxySelector ;
49
55
import java .net .Socket ;
56
+ import java .net .URI ;
50
57
import java .net .URL ;
51
58
import java .net .URLConnection ;
52
59
import java .security .GeneralSecurityException ;
63
70
import java .util .Properties ;
64
71
import java .util .concurrent .ExecutorService ;
65
72
import java .util .concurrent .Executors ;
73
+ import java .util .logging .ConsoleHandler ;
66
74
import java .util .logging .Level ;
67
75
import java .util .logging .Logger ;
68
76
@@ -78,17 +86,30 @@ public class CLI {
78
86
private final Channel channel ;
79
87
private final CliEntryPoint entryPoint ;
80
88
private final boolean ownsPool ;
89
+ private final List <Closeable > closables = new ArrayList <Closeable >(); // stuff to close in the close method
90
+ private final String httpsProxyTunnel ;
81
91
82
92
public CLI (URL jenkins ) throws IOException , InterruptedException {
83
93
this (jenkins ,null );
84
94
}
85
95
86
96
public CLI (URL jenkins , ExecutorService exec ) throws IOException , InterruptedException {
97
+ this (jenkins ,exec ,null );
98
+ }
99
+
100
+ /**
101
+ *
102
+ * @param httpsProxyTunnel
103
+ * Configures the HTTP proxy that we use for making a plain TCP/IP connection.
104
+ * "host:port" that points to an HTTP proxy or null.
105
+ */
106
+ public CLI (URL jenkins , ExecutorService exec , String httpsProxyTunnel ) throws IOException , InterruptedException {
87
107
String url = jenkins .toExternalForm ();
88
108
if (!url .endsWith ("/" )) url +='/' ;
89
109
90
110
ownsPool = exec ==null ;
91
111
pool = exec !=null ? exec : Executors .newCachedThreadPool ();
112
+ this .httpsProxyTunnel = httpsProxyTunnel ;
92
113
93
114
Channel channel = null ;
94
115
InetSocketAddress clip = getCliTcpPort (url );
@@ -132,13 +153,51 @@ protected void onDead() {
132
153
133
154
private Channel connectViaCliPort (URL jenkins , InetSocketAddress endpoint ) throws IOException {
134
155
LOGGER .fine ("Trying to connect directly via TCP/IP to " +endpoint );
135
- Socket s = new Socket (endpoint .getHostName (),endpoint .getPort ());
156
+ final Socket s ;
157
+ OutputStream out ;
158
+
159
+ if (httpsProxyTunnel !=null ) {
160
+ String [] tokens = httpsProxyTunnel .split (":" );
161
+ s = new Socket (tokens [0 ], Integer .parseInt (tokens [1 ]));
162
+ PrintStream o = new PrintStream (s .getOutputStream ());
163
+ o .print ("CONNECT " + endpoint .getHostName () + ":" + endpoint .getPort () + " HTTP/1.0\r \n \r \n " );
164
+
165
+ // read the response from the proxy
166
+ ByteArrayOutputStream rsp = new ByteArrayOutputStream ();
167
+ while (!rsp .toString ().endsWith ("\r \n \r \n " )) {
168
+ int ch = s .getInputStream ().read ();
169
+ if (ch <0 ) throw new IOException ("Failed to read the HTTP proxy response: " +rsp );
170
+ rsp .write (ch );
171
+ }
172
+ String head = new BufferedReader (new StringReader (rsp .toString ())).readLine ();
173
+ if (!head .startsWith ("HTTP/1.0 200 " ))
174
+ throw new IOException ("Failed to establish a connection through HTTP proxy: " +rsp );
175
+
176
+ // HTTP proxies (at least the one I tried --- squid) doesn't seem to do half-close very well.
177
+ // So instead of relying on it, we'll just send the close command and then let the server
178
+ // cut their side, then close the socket after the join.
179
+ closables .add (new Closeable () {
180
+ public void close () throws IOException {
181
+ s .close ();
182
+ }
183
+ });
184
+ out = new SocketOutputStream (s ) {
185
+ @ Override
186
+ public void close () throws IOException {
187
+ // ignore
188
+ }
189
+ };
190
+ } else {
191
+ s = new Socket (endpoint .getHostName (),endpoint .getPort ());
192
+ out = new SocketOutputStream (s );
193
+ }
194
+
136
195
DataOutputStream dos = new DataOutputStream (s .getOutputStream ());
137
196
dos .writeUTF ("Protocol:CLI-connect" );
138
197
139
198
return new Channel ("CLI connection to " +jenkins , pool ,
140
199
new BufferedInputStream (new SocketInputStream (s )),
141
- new BufferedOutputStream (new SocketOutputStream ( s ) ));
200
+ new BufferedOutputStream (out ));
142
201
}
143
202
144
203
/**
@@ -196,6 +255,8 @@ public void close() throws IOException, InterruptedException {
196
255
channel .join ();
197
256
if (ownsPool )
198
257
pool .shutdown ();
258
+ for (Closeable c : closables )
259
+ c .close ();
199
260
}
200
261
201
262
public int execute (List <String > args , InputStream stdin , OutputStream stdout , OutputStream stderr ) {
@@ -244,13 +305,20 @@ public void upgrade() {
244
305
}
245
306
246
307
public static void main (final String [] _args ) throws Exception {
308
+ // Logger l = Logger.getLogger(Channel.class.getName());
309
+ // l.setLevel(ALL);
310
+ // ConsoleHandler h = new ConsoleHandler();
311
+ // h.setLevel(ALL);
312
+ // l.addHandler(h);
313
+ //
247
314
System .exit (_main (_args ));
248
315
}
249
316
250
317
public static int _main (String [] _args ) throws Exception {
251
318
List <String > args = Arrays .asList (_args );
252
319
List <KeyPair > candidateKeys = new ArrayList <KeyPair >();
253
320
boolean sshAuthRequestedExplicitly = false ;
321
+ String httpProxy =null ;
254
322
255
323
String url = System .getenv ("JENKINS_URL" );
256
324
@@ -285,6 +353,11 @@ public static int _main(String[] _args) throws Exception {
285
353
sshAuthRequestedExplicitly = true ;
286
354
continue ;
287
355
}
356
+ if (head .equals ("-p" ) && args .size ()>=2 ) {
357
+ httpProxy = args .get (1 );
358
+ args = args .subList (2 ,args .size ());
359
+ continue ;
360
+ }
288
361
break ;
289
362
}
290
363
@@ -299,7 +372,7 @@ public static int _main(String[] _args) throws Exception {
299
372
if (candidateKeys .isEmpty ())
300
373
addDefaultPrivateKeyLocations (candidateKeys );
301
374
302
- CLI cli = new CLI (new URL (url ));
375
+ CLI cli = new CLI (new URL (url ), null , httpProxy );
303
376
try {
304
377
if (!candidateKeys .isEmpty ()) {
305
378
try {
0 commit comments