Subversion Repositories Programming Utils

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
86 rm5248 1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.sshd;
20
 
21
import java.io.BufferedReader;
22
import java.io.ByteArrayInputStream;
23
import java.io.ByteArrayOutputStream;
24
import java.io.File;
25
import java.io.IOException;
26
import java.io.InputStreamReader;
27
import java.io.OutputStreamWriter;
28
import java.io.PrintWriter;
29
import java.io.StringWriter;
30
import java.io.Writer;
31
import java.net.InetSocketAddress;
32
import java.net.SocketAddress;
33
import java.util.ArrayList;
34
import java.util.Arrays;
35
import java.util.Date;
36
import java.util.List;
37
import java.util.concurrent.Callable;
38
import java.util.logging.ConsoleHandler;
39
import java.util.logging.Formatter;
40
import java.util.logging.Handler;
41
import java.util.logging.Level;
42
import java.util.logging.LogRecord;
43
import java.util.logging.Logger;
44
 
45
import org.apache.sshd.client.ClientFactoryManager;
46
import org.apache.sshd.client.ServerKeyVerifier;
47
import org.apache.sshd.client.SessionFactory;
48
import org.apache.sshd.client.UserAuth;
49
import org.apache.sshd.client.UserInteraction;
50
import org.apache.sshd.client.auth.UserAuthKeyboardInteractive;
51
import org.apache.sshd.client.auth.UserAuthPassword;
52
import org.apache.sshd.client.auth.UserAuthPublicKey;
53
import org.apache.sshd.client.channel.ChannelShell;
54
import org.apache.sshd.client.future.ConnectFuture;
55
import org.apache.sshd.client.future.DefaultConnectFuture;
56
import org.apache.sshd.client.session.ClientConnectionService;
57
import org.apache.sshd.client.session.ClientSessionImpl;
58
import org.apache.sshd.client.session.ClientUserAuthService;
59
import org.apache.sshd.common.AbstractFactoryManager;
60
import org.apache.sshd.common.Channel;
61
import org.apache.sshd.common.Closeable;
62
import org.apache.sshd.common.Factory;
63
import org.apache.sshd.common.KeyPairProvider;
64
import org.apache.sshd.common.NamedFactory;
65
import org.apache.sshd.common.future.SshFutureListener;
66
import org.apache.sshd.common.io.DefaultIoServiceFactoryFactory;
67
import org.apache.sshd.common.io.IoConnectFuture;
68
import org.apache.sshd.common.io.IoConnector;
69
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
70
import org.apache.sshd.common.session.AbstractSession;
71
import org.apache.sshd.common.util.CloseableUtils;
72
import org.apache.sshd.common.util.NoCloseInputStream;
73
import org.apache.sshd.common.util.NoCloseOutputStream;
74
import org.apache.sshd.common.util.SecurityUtils;
75
import org.apache.sshd.common.util.ThreadUtils;
76
import org.bouncycastle.openssl.PasswordFinder;
77
 
78
/**
79
 * Entry point for the client side of the SSH protocol.
80
 *
81
 * The default configured client can be created using
82
 * the {@link #setUpDefaultClient()}.  The next step is to
83
 * start the client using the {@link #start()} method.
84
 *
85
 * Sessions can then be created using on of the
86
 * {@link #connect(String, int)} or {@link #connect(java.net.SocketAddress)}
87
 * methods.
88
 *
89
 * The client can be stopped at anytime using the {@link #stop()} method.
90
 *
91
 * Following is an example of using the SshClient:
92
 * <pre>
93
 *    SshClient client = SshClient.setUpDefaultClient();
94
 *    client.start();
95
 *    try {
96
 *        ClientSession session = client.connect(host, port).await().getSession();
97
 *
98
 *        int ret = ClientSession.WAIT_AUTH;
99
 *        while ((ret & ClientSession.WAIT_AUTH) != 0) {
100
 *            System.out.print("Password:");
101
 *            BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
102
 *            String password = r.readLine();
103
 *            session.authPassword(login, password);
104
 *            ret = session.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0);
105
 *        }
106
 *        if ((ret & ClientSession.CLOSED) != 0) {
107
 *            System.err.println("error");
108
 *            System.exit(-1);
109
 *        }
110
 *        ClientChannel channel = session.createChannel("shell");
111
 *        channel.setIn(new NoCloseInputStream(System.in));
112
 *        channel.setOut(new NoCloseOutputStream(System.out));
113
 *        channel.setErr(new NoCloseOutputStream(System.err));
114
 *        channel.open();
115
 *        channel.waitFor(ClientChannel.CLOSED, 0);
116
 *        session.close(false);
117
 *    } finally {
118
 *        client.stop();
119
 *    }
120
 * </pre>
121
 *
122
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
123
 */
124
public class SshClient extends AbstractFactoryManager implements ClientFactoryManager, Closeable {
125
 
126
    public static final Factory<SshClient> DEFAULT_SSH_CLIENT_FACTORY = new Factory<SshClient>() {
127
        public SshClient create() {
128
            return new SshClient();
129
        }
130
    };
131
 
132
    protected IoConnector connector;
133
    protected SessionFactory sessionFactory;
134
    protected UserInteraction userInteraction;
135
    protected List<NamedFactory<UserAuth>> userAuthFactories;
136
 
137
    private ServerKeyVerifier serverKeyVerifier;
138
 
139
    public SshClient() {
140
    }
141
 
142
    public SessionFactory getSessionFactory() {
143
        return sessionFactory;
144
    }
145
 
146
    public void setSessionFactory(SessionFactory sessionFactory) {
147
        this.sessionFactory = sessionFactory;
148
    }
149
 
150
    public ServerKeyVerifier getServerKeyVerifier() {
151
        return serverKeyVerifier;
152
    }
153
 
154
    public void setServerKeyVerifier(ServerKeyVerifier serverKeyVerifier) {
155
        this.serverKeyVerifier = serverKeyVerifier;
156
    }
157
 
158
    public UserInteraction getUserInteraction() {
159
        return userInteraction;
160
    }
161
 
162
    public void setUserInteraction(UserInteraction userInteraction) {
163
        this.userInteraction = userInteraction;
164
    }
165
 
166
    public List<NamedFactory<UserAuth>> getUserAuthFactories() {
167
        return userAuthFactories;
168
    }
169
 
170
    public void setUserAuthFactories(List<NamedFactory<UserAuth>> userAuthFactories) {
171
        this.userAuthFactories = userAuthFactories;
172
    }
173
 
174
    protected void checkConfig() {
175
        if (getKeyExchangeFactories() == null) {
176
            throw new IllegalArgumentException("KeyExchangeFactories not set");
177
        }
178
        if (getScheduledExecutorService() == null) {
179
            setScheduledExecutorService(
180
                    ThreadUtils.newSingleThreadScheduledExecutor(this.toString() + "-timer"),
181
                    true);
182
        }
183
        if (getCipherFactories() == null) {
184
            throw new IllegalArgumentException("CipherFactories not set");
185
        }
186
        if (getCompressionFactories() == null) {
187
            throw new IllegalArgumentException("CompressionFactories not set");
188
        }
189
        if (getMacFactories() == null) {
190
            throw new IllegalArgumentException("MacFactories not set");
191
        }
192
        if (getRandomFactory() == null) {
193
            throw new IllegalArgumentException("RandomFactory not set");
194
        }
195
        if (getTcpipForwarderFactory() == null) {
196
            throw new IllegalArgumentException("TcpipForwarderFactory not set");
197
        }
198
        if (getServerKeyVerifier() == null) {
199
            throw new IllegalArgumentException("ServerKeyVerifier not set");
200
        }
201
        // Register the additional agent forwarding channel if needed
202
        if (getAgentFactory() != null) {
203
            List<NamedFactory<Channel>> factories = getChannelFactories();
204
            if (factories == null) {
205
                factories = new ArrayList<NamedFactory<Channel>>();
206
            } else {
207
                factories = new ArrayList<NamedFactory<Channel>>(factories);
208
            }
209
            factories.add(getAgentFactory().getChannelForwardingFactory());
210
            setChannelFactories(factories);
211
        }
212
        if (getIoServiceFactoryFactory() == null) {
213
            setIoServiceFactoryFactory(new DefaultIoServiceFactoryFactory());
214
        }
215
        if (getServiceFactories() == null) {
216
            setServiceFactories(Arrays.asList(
217
                    new ClientUserAuthService.Factory(),
218
                    new ClientConnectionService.Factory()
219
            ));
220
        }
221
        if (getUserAuthFactories() == null) {
222
            setUserAuthFactories(Arrays.asList(
223
                    new UserAuthPublicKey.Factory(),
224
                    new UserAuthKeyboardInteractive.Factory(),
225
                    new UserAuthPassword.Factory()
226
            ));
227
        }
228
    }
229
 
230
    public void start() {
231
        checkConfig();
232
        if (sessionFactory == null) {
233
            sessionFactory = createSessionFactory();
234
        }
235
        sessionFactory.setClient(this);
236
        connector = createConnector();
237
    }
238
 
239
    public void stop() {
240
        try {
241
            close(true).await();
242
        } catch (InterruptedException e) {
243
            log.debug("Exception caught while stopping client", e);
244
        }
245
    }
246
 
247
    public void open() throws IOException {
248
        start();
249
    }
250
 
251
    @Override
252
    protected Closeable getInnerCloseable() {
253
        return builder()
254
                .sequential(connector, ioServiceFactory)
255
                .run(new Runnable() {
256
                    public void run() {
257
                        connector = null;
258
                        ioServiceFactory = null;
259
                        if (shutdownExecutor && executor != null) {
260
                            executor.shutdownNow();
261
                            executor = null;
262
                        }
263
                    }
264
                })
265
                .build();
266
    }
267
 
268
    /**
269
     * @deprecated Use {@link #connect(String, String, int)} instead
270
     */
271
    @Deprecated
272
    public ConnectFuture connect(String host, int port) throws IOException {
273
        return connect(null, host, port);
274
    }
275
 
276
    public ConnectFuture connect(String username, String host, int port) throws IOException {
277
        assert host != null;
278
        assert port >= 0;
279
        if (connector == null) {
280
            throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
281
        }
282
        SocketAddress address = new InetSocketAddress(host, port);
283
        return connect(username, address);
284
    }
285
 
286
    @Deprecated
287
    public ConnectFuture connect(SocketAddress address) {
288
        return connect(null, address);
289
    }
290
 
291
    public ConnectFuture connect(final String username, SocketAddress address) {
292
        assert address != null;
293
        if (connector == null) {
294
            throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
295
        }
296
        final ConnectFuture connectFuture = new DefaultConnectFuture(null);
297
        connector.connect(address).addListener(new SshFutureListener<IoConnectFuture>() {
298
            public void operationComplete(IoConnectFuture future) {
299
                if (future.isCanceled()) {
300
                    connectFuture.cancel();
301
                } else if (future.getException() != null) {
302
                    connectFuture.setException(future.getException());
303
                } else {
304
                    ClientSessionImpl session = (ClientSessionImpl) AbstractSession.getSession(future.getSession());
305
                    session.setUsername(username);
306
                    connectFuture.setSession(session);
307
                }
308
            }
309
        });
310
        return connectFuture;
311
    }
312
 
313
    protected IoConnector createConnector() {
314
        return getIoServiceFactory().createConnector(getSessionFactory());
315
    }
316
 
317
    protected SessionFactory createSessionFactory() {
318
        return new SessionFactory();
319
    }
320
 
321
    @Override
322
    public String toString() {
323
        return "SshClient[" + Integer.toHexString(hashCode()) + "]";
324
    }
325
 
326
    /**
327
     * Setup a default client.  The client does not require any additional setup.
328
     *
329
     * @return a newly create SSH client
330
     */
331
    public static SshClient setUpDefaultClient() {
332
        return SshBuilder.client().build();
333
    }
334
 
335
    /*=================================
336
          Main class implementation
337
     *=================================*/
338
 
339
    public static void main(String[] args) throws Exception {
340
        Handler fh = new ConsoleHandler();
341
        fh.setLevel(Level.FINEST);
342
        fh.setFormatter(new Formatter() {
343
            @Override
344
            public String format(LogRecord record) {
345
                String message = formatMessage(record);
346
                String throwable = "";
347
                if (record.getThrown() != null) {
348
                    StringWriter sw = new StringWriter();
349
                    PrintWriter pw = new PrintWriter(sw);
350
                    pw.println();
351
                    record.getThrown().printStackTrace(pw);
352
                    pw.close();
353
                    throwable = sw.toString();
354
                }
355
                return String.format("%1$tY-%1$tm-%1$td: %2$-7.7s: %3$-32.32s: %4$s%5$s%n",
356
                        new Date(record.getMillis()),
357
                        record.getLevel().getName(),
358
                        record.getLoggerName(),
359
                        message,
360
                        throwable);
361
            }
362
        });
363
        Logger root = Logger.getLogger("");
364
        for (Handler handler : root.getHandlers()) {
365
            root.removeHandler(handler);
366
        }
367
        root.addHandler(fh);
368
 
369
        int port = 22;
370
        String host = null;
371
        String login = System.getProperty("user.name");
372
        boolean agentForward = false;
373
        List<String> command = null;
374
        int logLevel = 0;
375
        boolean error = false;
376
        List<String> identities = new ArrayList<String>();
377
 
378
        for (int i = 0; i < args.length; i++) {
379
            if (command == null && "-p".equals(args[i])) {
380
                if (i + 1 >= args.length) {
381
                    System.err.println("option requires an argument: " + args[i]);
382
                    error = true;
383
                    break;
384
                }
385
                port = Integer.parseInt(args[++i]);
386
            } else if (command == null && "-l".equals(args[i])) {
387
                if (i + 1 >= args.length) {
388
                    System.err.println("option requires an argument: " + args[i]);
389
                    error = true;
390
                    break;
391
                }
392
                login = args[++i];
393
            } else if (command == null && "-v".equals(args[i])) {
394
                logLevel += 1;
395
            } else if (command == null && "-vv".equals(args[i])) {
396
                logLevel += 2;
397
            } else if (command == null && "-vvv".equals(args[i])) {
398
                logLevel += 3;
399
            } else if ("-A".equals(args[i])) {
400
                agentForward = true;
401
            } else if ("-a".equals(args[i])) {
402
                agentForward = false;
403
            } else if ("-i".equals(args[i])) {
404
                if (i + 1 >= args.length) {
405
                    System.err.println("option requires and argument: " + args[i]);
406
                    error = true;
407
                    break;
408
                }
409
                identities.add(args[++i]);
410
            } else if (command == null && args[i].startsWith("-")) {
411
                System.err.println("illegal option: " + args[i]);
412
                error = true;
413
                break;
414
            } else {
415
                if (host == null) {
416
                    host = args[i];
417
                } else {
418
                    if (command == null) {
419
                        command = new ArrayList<String>();
420
                    }
421
                    command.add(args[i]);
422
                }
423
            }
424
        }
425
        if (host == null) {
426
            System.err.println("hostname required");
427
            error = true;
428
        }
429
        if (error) {
430
            System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-l login] [-p port] hostname [command]");
431
            System.exit(-1);
432
        }
433
        if (logLevel <= 0) {
434
            root.setLevel(Level.WARNING);
435
        } else if (logLevel == 1) {
436
            root.setLevel(Level.INFO);
437
        } else if (logLevel == 2) {
438
            root.setLevel(Level.FINE);
439
        } else {
440
            root.setLevel(Level.FINEST);
441
        }
442
 
443
        KeyPairProvider provider = null;
444
        final List<String> files = new ArrayList<String>();
445
        File f = new File(System.getProperty("user.home"), ".ssh/id_dsa");
446
        if (f.exists() && f.isFile() && f.canRead()) {
447
            files.add(f.getAbsolutePath());
448
        }
449
        f = new File(System.getProperty("user.home"), ".ssh/id_rsa");
450
        if (f.exists() && f.isFile() && f.canRead()) {
451
            files.add(f.getAbsolutePath());
452
        }
453
        if (files.size() > 0) {
454
            // SSHD-292: we need to use a different class to load the FileKeyPairProvider
455
            //  in order to break the link between SshClient and BouncyCastle
456
            try {
457
                if (SecurityUtils.isBouncyCastleRegistered()) {
458
                    class KeyPairProviderLoader implements Callable<KeyPairProvider> {
459
                        public KeyPairProvider call() throws Exception {
460
                            return new FileKeyPairProvider(files.toArray(new String[files.size()]), new PasswordFinder() {
461
                                public char[] getPassword() {
462
                                    try {
463
                                        System.out.println("Enter password for private key: ");
464
                                        BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
465
                                        String password = r.readLine();
466
                                        return password.toCharArray();
467
                                    } catch (IOException e) {
468
                                        return null;
469
                                    }
470
                                }
471
                            });
472
                        }
473
                    }
474
                    provider = new KeyPairProviderLoader().call();
475
                }
476
            } catch (Throwable t) {
477
                System.out.println("Error loading user keys: " + t.getMessage());
478
            }
479
        }
480
 
481
        SshClient client = SshClient.setUpDefaultClient();
482
        client.start();
483
        client.setKeyPairProvider(provider);
484
        client.setUserInteraction(new UserInteraction() {
485
            public void welcome(String banner) {
486
                System.out.println(banner);
487
            }
488
            public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
489
                String[] answers = new String[prompt.length];
490
                try {
491
                    for (int i = 0; i < prompt.length; i++) {
492
                        BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
493
                        System.out.print(prompt[i] + " ");
494
                        answers[i] = r.readLine();
495
                    }
496
                } catch (IOException e) {
497
                }
498
                return answers;
499
            }
500
        });
501
 
502
        /*
503
        String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
504
        if (authSock == null && provider != null) {
505
            Iterable<KeyPair> keys = provider.loadKeys();
506
            AgentServer server = new AgentServer();
507
            authSock = server.start();
508
            SshAgent agent = new AgentClient(authSock);
509
            for (KeyPair key : keys) {
510
                agent.addIdentity(key, "");
511
            }
512
            agent.close();
513
            client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
514
        }
515
        */
516
 
517
        ClientSession session = client.connect(login, host, port).await().getSession();
518
        session.auth().verify();
519
 
520
        ClientChannel channel;
521
        if (command == null) {
522
            channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
523
            ((ChannelShell) channel).setAgentForwarding(agentForward);
524
            channel.setIn(new NoCloseInputStream(System.in));
525
        } else {
526
            channel = session.createChannel(ClientChannel.CHANNEL_EXEC);
527
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
528
            Writer w = new OutputStreamWriter(baos);
529
            for (String cmd : command) {
530
                w.append(cmd).append(" ");
531
            }
532
            w.append("\n");
533
            w.close();
534
            channel.setIn(new ByteArrayInputStream(baos.toByteArray()));
535
        }
536
        channel.setOut(new NoCloseOutputStream(System.out));
537
        channel.setErr(new NoCloseOutputStream(System.err));
538
        channel.open().await();
539
        channel.waitFor(ClientChannel.CLOSED, 0);
540
        session.close(false);
541
        client.stop();
542
    }
543
 
544
}