File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\cli\src\main\java\hudson\cli\CLI.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.cli;
25
26 import com.trilead.ssh2.crypto.PEMDecoder;
27 import hudson.cli.client.Messages;
28 import hudson.remoting.Channel;
29 import hudson.remoting.PingThread;
30 import hudson.remoting.Pipe;
31 import hudson.remoting.RemoteInputStream;
32 import hudson.remoting.RemoteOutputStream;
33 import hudson.remoting.SocketInputStream;
34 import hudson.remoting.SocketOutputStream;
35 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
36
37 import javax.crypto.SecretKey;
38 import javax.crypto.spec.SecretKeySpec;
39 import javax.net.ssl.HostnameVerifier;
40 import javax.net.ssl.HttpsURLConnection;
41 import javax.net.ssl.SSLContext;
42 import javax.net.ssl.SSLSession;
43 import javax.net.ssl.TrustManager;
44 import java.io.BufferedInputStream;
45 import java.io.BufferedOutputStream;
46 import java.io.BufferedReader;
47 import java.io.ByteArrayInputStream;
48 import java.io.ByteArrayOutputStream;
49 import java.io.Closeable;
50 import java.io.DataInputStream;
51 import java.io.DataOutputStream;
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.io.PrintStream;
58 import java.io.StringReader;
59 import java.net.HttpURLConnection;
60 import java.net.InetSocketAddress;
61 import java.net.Socket;
62 import java.net.URL;
63 import java.net.URLConnection;
64 import java.security.GeneralSecurityException;
65 import java.security.KeyFactory;
66 import java.security.KeyPair;
67 import java.security.PublicKey;
68 import java.security.SecureRandom;
69 import java.security.Signature;
70 import java.security.spec.DSAPrivateKeySpec;
71 import java.security.spec.DSAPublicKeySpec;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.Locale;
77 import java.util.Properties;
78 import java.util.concurrent.ExecutorService;
79 import java.util.concurrent.Executors;
80 import java.util.logging.Level;
81 import java.util.logging.Logger;
82 import java.io.Console;
83
84 import static java.util.logging.Level.*;
85
86 /**
87 * CLI entry point to Jenkins.
88 *
89 * @author Kohsuke Kawaguchi
90 */
91 public class CLI {
92 private final ExecutorService pool;
93 private final Channel channel;
94 private final CliEntryPoint entryPoint;
95 private final boolean ownsPool;
96 private final List<Closeable> closables = new ArrayList<Closeable>(); // stuff to close in the close method
97 private final String httpsProxyTunnel;
98 private final String authorization;
99
100 public CLI(URL jenkins) throws IOException, InterruptedException {
101 this(jenkins,null);
102 }
103
104 /**
105 * @deprecated
106 * Use {@link CLIConnectionFactory} to create {@link CLI}
107 */
108 public CLI(URL jenkins, ExecutorService exec) throws IOException, InterruptedException {
109 this(jenkins,exec,null);
110 }
111
112 /**
113 * @deprecated
114 * Use {@link CLIConnectionFactory} to create {@link CLI}
115 */
116 public CLI(URL jenkins, ExecutorService exec, String httpsProxyTunnel) throws IOException, InterruptedException {
117 this(new CLIConnectionFactory().url(jenkins).executorService(exec).httpsProxyTunnel(httpsProxyTunnel));
118 }
119
120 /*package*/ CLI(CLIConnectionFactory factory) throws IOException, InterruptedException {
121 URL jenkins = factory.jenkins;
122 this.httpsProxyTunnel = factory.httpsProxyTunnel;
123 this.authorization = factory.authorization;
124 ExecutorService exec = factory.exec;
125
126 String url = jenkins.toExternalForm();
127 if(!url.endsWith("/")) url+='/';
128
129 ownsPool = exec==null;
130 pool = exec!=null ? exec : Executors.newCachedThreadPool();
131
132 Channel _channel;
133 try {
134 _channel = connectViaCliPort(jenkins, getCliTcpPort(url));
135 } catch (IOException e) {
136 LOGGER.log(Level.FINE,"Failed to connect via CLI port. Falling back to HTTP",e);
137 try {
138 _channel = connectViaHttp(url);
139 } catch (IOException e2) {
140 try { // Java 7: e.addSuppressed(e2);
141 Throwable.class.getMethod("addSuppressed", Throwable.class).invoke(e, e2);
142 } catch (NoSuchMethodException _ignore) {
143 // Java 6
144 } catch (Exception _huh) {
145 LOGGER.log(Level.SEVERE, null, _huh);
146 }
147 throw e;
148 }
149 }
150 this.channel = _channel;
151
152 // execute the command
153 entryPoint = (CliEntryPoint)_channel.waitForRemoteProperty(CliEntryPoint.class.getName());
154
155 if(entryPoint.protocolVersion()!=CliEntryPoint.VERSION)
156 throw new IOException(Messages.CLI_VersionMismatch());
157 }
158
159 private Channel connectViaHttp(String url) throws IOException {
160 LOGGER.fine("Trying to connect to "+url+" via HTTP");
161 url+="cli";
162 URL jenkins = new URL(url);
163
164 FullDuplexHttpStream con = new FullDuplexHttpStream(jenkins,authorization);
165 Channel ch = new Channel("Chunked connection to "+jenkins,
166 pool,con.getInputStream(),con.getOutputStream());
167 final long interval = 15*1000;
168 final long timeout = (interval * 3) / 4;
169 new PingThread(ch,timeout,interval) {
170 protected void onDead() {
171 // noop. the point of ping is to keep the connection alive
172 // as most HTTP servers have a rather short read time out
173 }
174 }.start();
175 return ch;
176 }
177
178 private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException {
179 LOGGER.fine("Trying to connect directly via TCP/IP to "+clip.endpoint);
180 final Socket s;
181 OutputStream out;
182
183 if (httpsProxyTunnel!=null) {
184 String[] tokens = httpsProxyTunnel.split(":");
185 s = new Socket(tokens[0], Integer.parseInt(tokens[1]));
186 PrintStream o = new PrintStream(s.getOutputStream());
187 o.print("CONNECT " + clip.endpoint.getHostName() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n");
188
189 // read the response from the proxy
190 ByteArrayOutputStream rsp = new ByteArrayOutputStream();
191 while (!rsp.toString().endsWith("\r\n\r\n")) {
192 int ch = s.getInputStream().read();
193 if (ch<0) throw new IOException("Failed to read the HTTP proxy response: "+rsp);
194 rsp.write(ch);
195 }
196 String head = new BufferedReader(new StringReader(rsp.toString())).readLine();
197 if (!head.startsWith("HTTP/1.0 200 "))
198 throw new IOException("Failed to establish a connection through HTTP proxy: "+rsp);
199
200 // HTTP proxies (at least the one I tried --- squid) doesn't seem to do half-close very well.
201 // So instead of relying on it, we'll just send the close command and then let the server
202 // cut their side, then close the socket after the join.
203 out = new SocketOutputStream(s) {
204 @Override
205 public void close() throws IOException {
206 // ignore
207 }
208 };
209 } else {
210 s = new Socket();
211 s.connect(clip.endpoint,3000);
212 out = new SocketOutputStream(s);
213 }
214
215 closables.add(new Closeable() {
216 public void close() throws IOException {
217 s.close();
218 }
219 });
220
221 Connection c = new Connection(new SocketInputStream(s),out);
222
223 switch (clip.version) {
224 case 1:
225 DataOutputStream dos = new DataOutputStream(s.getOutputStream());
226 dos.writeUTF("Protocol:CLI-connect");
227 // we aren't checking greeting from the server here because I'm too lazy. It gets ignored by Channel constructor.
228 break;
229 case 2:
230 DataInputStream dis = new DataInputStream(s.getInputStream());
231 dos = new DataOutputStream(s.getOutputStream());
232 dos.writeUTF("Protocol:CLI2-connect");
233 String greeting = dis.readUTF();
234 if (!greeting.equals("Welcome"))
235 throw new IOException("Handshaking failed: "+greeting);
236 try {
237 byte[] secret = c.diffieHellman(false).generateSecret();
238 SecretKey sessionKey = new SecretKeySpec(Connection.fold(secret,128/8),"AES");
239 c = c.encryptConnection(sessionKey,"AES/CFB8/NoPadding");
240
241 // validate the instance identity, so that we can be sure that we are talking to the same server
242 // and there's no one in the middle.
243 byte[] signature = c.readByteArray();
244
245 if (clip.identity!=null) {
246 Signature verifier = Signature.getInstance("SHA1withRSA");
247 verifier.initVerify(clip.getIdentity());
248 verifier.update(secret);
249 if (!verifier.verify(signature))
250 throw new IOException("Server identity signature validation failed.");
251 }
252
253 } catch (GeneralSecurityException e) {
254 throw (IOException)new IOException("Failed to negotiate transport security").initCause(e);
255 }
256 }
257
258 return new Channel("CLI connection to "+jenkins, pool,
259 new BufferedInputStream(c.in), new BufferedOutputStream(c.out));
260 }
261
262 /**
263 * If the server advertises CLI endpoint, returns its location.
264 */
265 private CliPort getCliTcpPort(String url) throws IOException {
266 URL _url = new URL(url);
267 if (_url.getHost()==null || _url.getHost().length()==0) {
268 throw new IOException("Invalid URL: "+url);
269 }
270 URLConnection head = _url.openConnection();
271 try {
272 head.connect();
273 } catch (IOException e) {
274 throw (IOException)new IOException("Failed to connect to "+url).initCause(e);
275 }
276
277 String h = head.getHeaderField("X-Jenkins-CLI-Host");
278 if (h==null) h = head.getURL().getHost();
279 String p1 = head.getHeaderField("X-Jenkins-CLI-Port");
280 if (p1==null) p1 = head.getHeaderField("X-Hudson-CLI-Port"); // backward compatibility
281 String p2 = head.getHeaderField("X-Jenkins-CLI2-Port");
282
283 String identity = head.getHeaderField("X-Instance-Identity");
284
285 flushURLConnection(head);
286 if (p1==null && p2==null) {
287 throw new IOException("No X-Jenkins-CLI2-Port among " + head.getHeaderFields().keySet());
288 }
289
290 if (p2!=null) return new CliPort(new InetSocketAddress(h,Integer.parseInt(p2)),identity,2);
291 else return new CliPort(new InetSocketAddress(h,Integer.parseInt(p1)),identity,1);
292 }
293
294 /**
295 * Flush the supplied {@link URLConnection} input and close the
296 * connection nicely.
297 * @param conn the connection to flush/close
298 */
299 private void flushURLConnection(URLConnection conn) {
300 byte[] buf = new byte[1024];
301 try {
302 InputStream is = conn.getInputStream();
303 while (is.read(buf) > 0) {
304 // Ignore
305 }
306 is.close();
307 } catch (IOException e) {
308 try {
309 InputStream es = ((HttpURLConnection)conn).getErrorStream();
310 while (es.read(buf) > 0) {
311 // Ignore
312 }
313 es.close();
314 } catch (IOException ex) {
315 // Ignore
316 }
317 }
318 }
319
320 /**
321 * Shuts down the channel and closes the underlying connection.
322 */
323 public void close() throws IOException, InterruptedException {
324 channel.close();
325 channel.join();
326 if(ownsPool)
327 pool.shutdown();
328 for (Closeable c : closables)
329 c.close();
330 }
331
332 public int execute(List<String> args, InputStream stdin, OutputStream stdout, OutputStream stderr) {
333 return entryPoint.main(args, Locale.getDefault(),
334 new RemoteInputStream(stdin),
335 new RemoteOutputStream(stdout),
336 new RemoteOutputStream(stderr));
337 }
338
339 public int execute(List<String> args) {
340 return execute(args, System.in, System.out, System.err);
341 }
342
343 public int execute(String... args) {
344 return execute(Arrays.asList(args));
345 }
346
347 /**
348 * Returns true if the named command exists.
349 */
350 public boolean hasCommand(String name) {
351 return entryPoint.hasCommand(name);
352 }
353
354 /**
355 * Accesses the underlying communication channel.
356 * @since 1.419
357 */
358 public Channel getChannel() {
359 return channel;
360 }
361
362 /**
363 * Attempts to lift the security restriction on the underlying channel.
364 * This requires the administer privilege on the server.
365 *
366 * @throws SecurityException
367 * If we fail to upgrade the connection.
368 */
369 public void upgrade() {
370 ByteArrayOutputStream out = new ByteArrayOutputStream();
371 if (execute(Arrays.asList("groovy", "="),
372 new ByteArrayInputStream("hudson.remoting.Channel.current().setRestricted(false)".getBytes()),
373 out,out)!=0)
374 throw new SecurityException(out.toString()); // failed to upgrade
375 }
376
377 public static void main(final String[] _args) throws Exception {
378 // Logger l = Logger.getLogger(Channel.class.getName());
379 // l.setLevel(ALL);
380 // ConsoleHandler h = new ConsoleHandler();
381 // h.setLevel(ALL);
382 // l.addHandler(h);
383 //
384 System.exit(_main(_args));
385 }
386
387 public static int _main(String[] _args) throws Exception {
388 List<String> args = Arrays.asList(_args);
389 List<KeyPair> candidateKeys = new ArrayList<KeyPair>();
390 boolean sshAuthRequestedExplicitly = false;
391 String httpProxy=null;
392
393 String url = System.getenv("JENKINS_URL");
394
395 if (url==null)
396 url = System.getenv("HUDSON_URL");
397
398 while(!args.isEmpty()) {
399 String head = args.get(0);
400 if (head.equals("-version")) {
401 System.out.println("Version: "+computeVersion());
402 return 0;
403 }
404 if(head.equals("-s") && args.size()>=2) {
405 url = args.get(1);
406 args = args.subList(2,args.size());
407 continue;
408 }
409 if (head.equals("-noCertificateCheck")) {
410 System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
411 SSLContext context = SSLContext.getInstance("TLS");
412 context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom());
413 HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
414 // bypass host name check, too.
415 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
416 public boolean verify(String s, SSLSession sslSession) {
417 return true;
418 }
419 });
420 args = args.subList(1,args.size());
421 continue;
422 }
423 if(head.equals("-i") && args.size()>=2) {
424 File f = new File(args.get(1));
425 if (!f.exists()) {
426 printUsage(Messages.CLI_NoSuchFileExists(f));
427 return -1;
428 }
429 KeyPair kp;
430 try {
431 kp = loadKey(f);
432 } catch (IOException e) {
433 //if the PEM file is encrypted, IOException is thrown
434 kp = tryEncryptedFile(f);
435 } catch (GeneralSecurityException e) {
436 throw new Exception("Failed to load key: "+f,e);
437 }
438 if(kp != null)
439 candidateKeys.add(kp);
440 args = args.subList(2,args.size());
441 sshAuthRequestedExplicitly = true;
442 continue;
443 }
444 if(head.equals("-p") && args.size()>=2) {
445 httpProxy = args.get(1);
446 args = args.subList(2,args.size());
447 continue;
448 }
449 break;
450 }
451
452 if(url==null) {
453 printUsage(Messages.CLI_NoURL());
454 return -1;
455 }
456
457 if(args.isEmpty())
458 args = Arrays.asList("help"); // default to help
459
460 if (candidateKeys.isEmpty())
461 addDefaultPrivateKeyLocations(candidateKeys);
462
463 CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy);
464 String userInfo = new URL(url).getUserInfo();
465 if (userInfo != null) {
466 factory = factory.basicAuth(userInfo);
467 }
468
469 CLI cli = factory.connect();
470 try {
471 if (!candidateKeys.isEmpty()) {
472 try {
473 // TODO: server verification
474 cli.authenticate(candidateKeys);
475 } catch (IllegalStateException e) {
476 if (sshAuthRequestedExplicitly) {
477 System.err.println("The server doesn't support public key authentication");
478 return -1;
479 }
480 } catch (UnsupportedOperationException e) {
481 if (sshAuthRequestedExplicitly) {
482 System.err.println("The server doesn't support public key authentication");
483 return -1;
484 }
485 } catch (GeneralSecurityException e) {
486 if (sshAuthRequestedExplicitly) {
487 System.err.println(e.getMessage());
488 LOGGER.log(FINE,e.getMessage(),e);
489 return -1;
490 }
491 System.err.println("Failed to authenticate with your SSH keys.");
492 LOGGER.log(FINE,"Failed to authenticate with your SSH keys.",e);
493 }
494 }
495
496 // execute the command
497 // Arrays.asList is not serializable --- see 6835580
498 args = new ArrayList<String>(args);
499 return cli.execute(args, System.in, System.out, System.err);
500 } finally {
501 cli.close();
502 }
503 }
504
505 private static String computeVersion() {
506 Properties props = new Properties();
507 try {
508 InputStream is = CLI.class.getResourceAsStream("/jenkins/cli/jenkins-cli-version.properties");
509 if(is!=null)
510 props.load(is);
511 } catch (IOException e) {
512 e.printStackTrace(); // if the version properties is missing, that's OK.
513 }
514 return props.getProperty("version","?");
515 }
516
517 /**
518 * Loads RSA/DSA private key in a PEM format into {@link KeyPair}.
519 */
520 public static KeyPair loadKey(File f, String passwd) throws IOException, GeneralSecurityException {
521 return loadKey(readPemFile(f), passwd);
522 }
523
524 public static KeyPair loadKey(File f) throws IOException, GeneralSecurityException {
525 return loadKey(f, null);
526 }
527
528 private static String readPemFile(File f) throws IOException{
529 DataInputStream dis = new DataInputStream(new FileInputStream(f));
530 byte[] bytes = new byte[(int) f.length()];
531 dis.readFully(bytes);
532 dis.close();
533 return new String(bytes);
534 }
535
536 /**
537 * Loads RSA/DSA private key in a PEM format into {@link KeyPair}.
538 */
539 public static KeyPair loadKey(String pemString, String passwd) throws IOException, GeneralSecurityException {
540 Object key = PEMDecoder.decode(pemString.toCharArray(), passwd);
541 if (key instanceof com.trilead.ssh2.signature.RSAPrivateKey) {
542 com.trilead.ssh2.signature.RSAPrivateKey x = (com.trilead.ssh2.signature.RSAPrivateKey)key;
543 // System.out.println("ssh-rsa " + new String(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey(x.getPublicKey()))));
544
545 return x.toJCEKeyPair();
546 }
547 if (key instanceof com.trilead.ssh2.signature.DSAPrivateKey) {
548 com.trilead.ssh2.signature.DSAPrivateKey x = (com.trilead.ssh2.signature.DSAPrivateKey)key;
549 KeyFactory kf = KeyFactory.getInstance("DSA");
550 // System.out.println("ssh-dsa " + new String(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey(x.getPublicKey()))));
551
552 return new KeyPair(
553 kf.generatePublic(new DSAPublicKeySpec(x.getY(), x.getP(), x.getQ(), x.getG())),
554 kf.generatePrivate(new DSAPrivateKeySpec(x.getX(), x.getP(), x.getQ(), x.getG())));
555 }
556
557 throw new UnsupportedOperationException("Unrecognizable key format: "+key);
558 }
559
560 public static KeyPair loadKey(String pemString) throws IOException, GeneralSecurityException {
561 return loadKey(pemString, null);
562 }
563
564 private static KeyPair tryEncryptedFile(File f) throws IOException, GeneralSecurityException{
565 KeyPair kp = null;
566 if(isPemEncrypted(f)){
567 String passwd = askForPasswd(f.getCanonicalPath());
568 kp = loadKey(f,passwd);
569 }
570 return kp;
571 }
572
573 private static boolean isPemEncrypted(File f) throws IOException{
574 String pemString = readPemFile(f);
575 //simple check if the file is encrypted
576 return pemString.contains("4,ENCRYPTED");
577 }
578
579 @SuppressWarnings("Since15")
580 @IgnoreJRERequirement
581 private static String askForPasswd(String filePath){
582 try {
583 Console cons = System.console();
584 String passwd = null;
585 if (cons != null){
586 char[] p = cons.readPassword("%s", "Enter passphrase for "+filePath+":");
587 passwd = String.valueOf(p);
588 }
589 return passwd;
590 } catch (LinkageError e) {
591 throw new Error("Your private key is encrypted, but we need Java6 to ask you password safely",e);
592 }
593 }
594
595 /**
596 * try all the default key locations
597 */
598 private static void addDefaultPrivateKeyLocations(List<KeyPair> keyFileCandidates) {
599 File home = new File(System.getProperty("user.home"));
600 for (String path : new String[]{".ssh/id_rsa",".ssh/id_dsa",".ssh/identity"}) {
601 File key = new File(home,path);
602 if (key.exists()) {
603 try {
604 keyFileCandidates.add(loadKey(key));
605 } catch (IOException e) {
606 // don't report an error. the user can still see it by using the -i option
607 LOGGER.log(FINE, "Failed to load "+key,e);
608 } catch (GeneralSecurityException e) {
609 LOGGER.log(FINE, "Failed to load " + key, e);
610 }
611 }
612 }
613 }
614
615 /**
616 * Authenticate ourselves against the server.
617 *
618 * @return
619 * identity of the server represented as a public key.
620 */
621 public PublicKey authenticate(Iterable<KeyPair> privateKeys) throws IOException, GeneralSecurityException {
622 Pipe c2s = Pipe.createLocalToRemote();
623 Pipe s2c = Pipe.createRemoteToLocal();
624 entryPoint.authenticate("ssh",c2s, s2c);
625 Connection c = new Connection(s2c.getIn(), c2s.getOut());
626
627 try {
628 byte[] sharedSecret = c.diffieHellman(false).generateSecret();
629 PublicKey serverIdentity = c.verifyIdentity(sharedSecret);
630
631 // try all the public keys
632 for (KeyPair key : privateKeys) {
633 c.proveIdentity(sharedSecret,key);
634 if (c.readBoolean())
635 return serverIdentity; // succeeded
636 }
637 if (privateKeys.iterator().hasNext())
638 throw new GeneralSecurityException("Authentication failed. No private key accepted.");
639 else
640 throw new GeneralSecurityException("No private key is available for use in authentication");
641 } finally {
642 c.close();
643 }
644 }
645
646 public PublicKey authenticate(KeyPair key) throws IOException, GeneralSecurityException {
647 return authenticate(Collections.singleton(key));
648 }
649
650 private static void printUsage(String msg) {
651 if(msg!=null) System.out.println(msg);
652 System.err.println(Messages.CLI_Usage());
653 }
654
655 private static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
656 }
657