summaryrefslogtreecommitdiffstats
path: root/kioslave/sftp/ksshprocess.h
blob: 7fe8dd81479c855ccb36318d587f6f0cb086f333 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
/***************************************************************************
                          ksshprocess.h  -  description
                             -------------------
    begin                : Tue Jul 31 2001
    copyright            : (C) 2001 by Lucas Fisher
    email                : ljfisher@purdue.edu
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef KSSHPROCESS_H
#define KSSHPROCESS_H

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

#include <tqvaluelist.h>

#include <kdebug.h>

#include "process.h"

#define KSSHPROC 7120

/**
 * Provides version independent access to ssh. Currently supported
 * versions of SSH are:
 *   OpenSSH 2.9p1
 *   OpenSSH 2.9p2
 *   OpenSSH 3.0
 *   OpenSSH 3.1
 *   Commercial SSH 3.0.0
 * Other versions of OpenSSH and commerical SSH will probably work also.
 *
 * To setup a SSH connection first create a list of options to use and tell
 * KSshProcess about your options. Then start the ssh connection. Once the
 * connection is setup use the  stdin, stdout, stderr, and pty file descriptors
 * to communicate with ssh. For a detailed example of how to use, see
 * ksshprocesstest.cpp.
 *
 * @author Lucas Fisher
 *
 * Example: Connect to ssh server on localhost
 *   KSshProcess::SshOpt opt;
 *   KSshProcess::SshOptList options;
 *
 *   opt.opt = KSshProcess::SSH_HOST;
 *   opt.str = "localhost";
 *   options.append(opt);
 *
 *   opt.opt = KSshProcess::SSH_USERNAME;
 *   opt.str = "me";
 *   options.append(opt);
 *
 *   KSshProcess ssh;
 *   if( !ssh.setOptions(options) ) {
 *       int err = ssh.error();
 *       // process error
 *        return false;
 *   }
 *
 *   int err;
 *   TQString errMsg;
 *   while( !ssh.connect() ) {
 *       err = ssh.error(errMsg);
 *       
 *       switch( err ) {
 *       case KSshProcess::ERR_NEW_HOST_KEY:
 *       case KSshProcess::ERR_DIFF_HOST_KEY:
 *           // ask user to accept key
 *           if( acceptHostKey ) {
 *               ssh.acceptKey(true);
 *           }
 *           break;
 *
 *       case KSshProcess::ERR_NEED_PASSWORD:
 *           // ask user for password
 *           ssh.password(userPassword);
 *           break;
 *       
 *       case KSshProcess::ERR_NEED_KEY_PASSPHRASE:
 *           // ask user for their key passphrase
 *           ssh.keyPassphrase(keyPassphrase);
 *           break;
 *
 *       default:
 *           // somethings wrong, alert user
 *           return;
 *       }
 *   }
 *   // We have an open ssh connection to localhost
 *
 */

class KSshProcess {
public:
    /**
     * SSH Option
     *
     * Stores SSH options for use with KSshProcess.
     *
     * SSH options are configured much like UDS entries.
     * Each option is assigned a constant and a string, bool,
     * or number is assigned based on the option.
     *
     * @author Lucas Fisher (ljfisher@iastate.edu)
     */
    class SshOpt {
    public:
        Q_UINT32 opt;
        TQString  str;
        Q_INT32  num;
        bool     boolean;
    };

    /**
     * List of SshOptions and associated iterators
     */
    typedef TQValueList<SshOpt> SshOptList;
    typedef TQValueListIterator<SshOpt> SshOptListIterator;
    typedef TQValueListConstIterator<SshOpt> SshOptListConstIterator;

    /**
     * Ssh versions supported by KSshProcess. Subject to change
     * at any time.
     */
    enum SshVersion {
	OPENSSH_3_6,
        OPENSSH,
        SSH,
        SSH_VER_MAX,
        UNKNOWN_VER
    };	

    /**
     * SSH options supported by KSshProcess.  Set SshOpt::opt to one of these
     * values.
     */
    // we cannot do this like UDSAtomType (ORing the type with the name) because
    // we have too many options for ssh and not enough bits.
    enum SshOptType {
        /**
         * Request server to invoke subsystem. (str)
         */
        SSH_SUBSYSTEM,
        /**
         * Connect to port on the server. (num)
         */
        SSH_PORT,
        /**
         * Connect to host. (str)
         */
        SSH_HOST,
        /**
         * connect using this username. (str)
         */
        SSH_USERNAME,
        /** 
         * connect using this password. (str)
         */
        SSH_PASSWD,
        /**
         * connect using this version of the SSH protocol. num == 1 or 2
         */
        SSH_PROTOCOL,
        /**
         * whether to forward X11 connections. (boolean)
         */
        SSH_FORWARDX11,
        /**
         * whether to do agent forwarding. (boolean)
         */
        SSH_FORWARDAGENT,
        /**
         * use as escape character. 0 for none  (num)
         */
        SSH_ESCAPE_CHAR,
        /**
         * command for ssh to perform once it is connected (str)
         */
        SSH_COMMAND,
        /**
         * Set ssh verbosity. This may be added multiple times. It may also cause KSSHProcess
         * to fail since we don't understand all the debug messages.
         */
        SSH_VERBOSE,
        /**
         * Set a ssh option as one would find in the ssh_config file
         * The str member should be set to 'optName value'
         */
        SSH_OPTION,
        /**
         * Set some other option not supported by KSSHProcess. The option should
         * be specified in the str member of SshOpt. Careful with this since
         * not all versions of SSH support the same options.
         */
        SSH_OTHER,
        SSH_OPT_MAX // always last
    }; // that's all for now

    /**
     * Errors that KSshProcess can encounter.  When a member function returns
     * false, call error() to retrieve one of these error codes.
     */
    enum SshError {
        /**
         * Don't recognize the ssh version
         */
        ERR_UNKNOWN_VERSION,
        /**
         * Cannot lauch ssh client
         */
        ERR_CANNOT_LAUNCH,
        /**
         * Interaction with the ssh client failed. This happens when we can't
         * find the password prompt or something similar
         */
        ERR_INTERACT,
        /**
         * Arguments for both a remotely executed subsystem and command were provide.
         * Only one or the other may be used
         */
        ERR_CMD_SUBSYS_CONFLICT,
        /**
         * No password was supplied
         */
        ERR_NEED_PASSWD,
        /**
         * No passphrase was supplied.
         */
        ERR_NEED_PASSPHRASE,
        /**
         * No usename was supplied
         */
        ERR_NEED_USERNAME,
        /**
         * Timed out waiting for a response from ssh or the server
         */
        ERR_TIMED_OUT,
        /**
         * Internal error, probably from a system call
         */
        ERR_INTERNAL,
        /**
         * ssh was disconnect from the host
         */
        ERR_DISCONNECTED,
        /**
         * No ssh options have been set. Call setArgs() before calling connect.
         */
        ERR_NO_OPTIONS,
        /**
         * A host key was received from an unknown host. 
         * Call connect() with the acceptHostKey argument to accept the key.
         */
        ERR_NEW_HOST_KEY,
        /**
         * A host key different from what is stored in the user's known_hosts file
         * has be received. This is an indication of an attack
         */
        ERR_DIFF_HOST_KEY,
        /**
         * A new or different host key was rejected by the caller. The ssh
         * connection was terminated and the ssh process killed.
         */
        ERR_HOST_KEY_REJECTED,
        /**
         * An invalid option was found in the SSH option list
         */
        ERR_INVALID_OPT,
        /**
         * SSH accepted host key without prompting user.
         */
        ERR_ACCEPTED_KEY,
        /**
         * Authentication failed
         */
        ERR_AUTH_FAILED,
        /**
         * Authentication failed because a new host key was detected and 
         * SSH is configured with strict host key checking enabled.
         */
        ERR_AUTH_FAILED_NEW_KEY,
        /**
         * Authentication failed because a changed host key was detected and 
         * SSH is configured with strict host key checking enabled.
         */
        ERR_AUTH_FAILED_DIFF_KEY,
        /**
         * The remote host closed the connection for unknown reasons.
         */
        ERR_CLOSED_BY_REMOTE_HOST,
        /**
         * We have no idea what happened
         */
        ERR_UNKNOWN,
        /**
         * The connect state machine entered an invalid state.
         */
        ERR_INVALID_STATE,
        ERR_MAX
    };

    /**
     * Initialize a SSH process using the first SSH binary found in the PATH
     */
    KSshProcess();

    /**
     * Initialize a SSH process using the specified SSH binary.
     * @param pathToSsh The fully qualified path name of the ssh binary
     *                  KSshProcess should use to setup a SSH connection.
     */
    KSshProcess(TQString pathToSsh);
    ~KSshProcess();

    /**
     * Set the ssh binary KSshProcess should use. This will only affect the
     * next ssh connection attempt using this instance.
     *
     * @param pathToSsh Full path to the ssh binary.
     *
     * @return True if the ssh binary is found and KSshProcess
     *         recognizes the version.
     *
     */
     bool setSshPath(TQString pathToSsh);

    /**
     * Get the ssh version.
     *
     * @return  The ssh version or -1 if KSshProcess does not recognize
     *          the ssh version. The returned value corresponds to the
     *          member of the SshVersion enum.
     */
    SshVersion version();

    /**
     * Get a string describing the ssh version
     *
     * @return A string describing the ssh version recognized by KSshProcess
     */
    //TQString versionStr();

    /**
     * Get the last error encountered by KSshProcess.
     *
     * @param msg Set to the error message, if any, outputted by ssh when it is run.
     *
     * @return The error number. See SshError for descriptions.
     */
    int error(TQString& msg);

    /**
     * Get the last error encountered by KSshProcess.
     * @return The error number. See SshError for descriptions.
     */
    int error() { return mError; }

    TQString errorMsg() { return mErrorMsg; }

    /**
     * Send a signal to the ssh process. Do not use this to end the
     * ssh connection as it will not correctly reset the internal
     * state of the KSshProcess object.  Use KSshProcess::disconnect()
     * instead.
     *
     * @param signal The signal to send to the ssh process. See 'kill -l'
     *               for a list of possible signals.
     *               The default signal is SIGKILL which kills ssh.
     *
     */
    void kill(int signal = SIGKILL);

    /**
     * The pid of the ssh process started by this instance of KSshProcess.
     * Only valid if KSshProcess::running() returns true;
     * 
     * @return The pid of the running ssh process.
     */
    int pid() { return ssh.pid(); }
    
    /**
     * Whether a ssh connection has been  established with a
     * remote host.  A establish connection means ssh has successfully
     * authenticated with the remote host and user data can be transfered
     * between the local and remote host.  This cannot return
     * true unless the most recent call to KSshProccess::connect() returned true.
     *
     * @return True if a ssh connection has been established with a remote
     *         host. False otherwise.
     */
    bool connected() { return mConnected; }

    /**
     * Whether a ssh process is currently running.  This  only indicates
     * if a ssh process has been started and is still running.  It does not
     * tell if authentication has been successful.  This may return true
     * even if the most recent call to KSshProcess::connect() returned false.
     *
     * @return True if a ssh process started by this instance of KSshProcess
     *         is running. False otherwise.
     */
    bool running() { return mRunning; }
    
    /**
     * Print the command line arguments ssh is run with using kdDebug.
     */
    void printArgs();

    /**
     * Set the SSH options.
     * This must be called before connect().  See SshOptType for a list of
     * supported ssh options.  The required options are SSH_USERNAME 
     * and SSH_HOST.
     *
     * To reset the saved options, just recall setOptions() again with
     * a different options list.
     *
     * @param opts A list of SshOpt objects specifying the ssh options.
     *
     * @return True if all options are valid. False if unrecognized options
     *         or a required option is missing. Call error()
     *         for details.
     *
     */
    bool setOptions(const SshOptList& opts);

    /**
     * Create a ssh connection based on the options provided by setOptions().
     * Sets one of the following error codes on failure:
     * <ul>
     * <li>ERR_NO_OPTIONS</li>
     * <li>ERR_CANNOT_LAUNCH</li>
     * <li>ERR_INVALID_STATE</li>
     * <li>ERR_NEED_PASSWD</li>
     * <li>ERR_AUTH_FAILED</li>
     * <li>ERR_NEW_HOST_KEY</li>
     * <li>ERR_KEY_ACCEPTED</li>
     * <li>ERR_DIFF_HOST_KEY</li>
     * <li>ERR_INTERNAL</li>
     * <li>ERR_INTERACT</li>
     * </ul>
     *
     * @param acceptHostKey When true KSshProcess will automatically accept
     *                      unrecognized or changed host keys.
     *
     * @return True if the ssh connection is successful. False if the connection
     *         fails.  Call error() to get the reason for the failure.
     */
    bool connect();


    /**
     * Disconnect ssh from the host.  This kills the ssh process and
     * resets the internal state of this KSshProcess object. After a 
     * disconnect, the same KSshProcess can be used to connect to a
     * host.
     */
    void disconnect();
    
    /**
     * Call to respond to a ERR_NEW_HOST_KEY or ERR_DIFF_HOST_KEY error.
     * 
     * @param accept True to accept the host key, false to not accept the
     *               host key and kill ssh.
     * 
     */
    void acceptHostKey(bool accept);

    /**
     * Call to respond to a ERR_NEED_PASSWD or ERR_NEED_PASSPHRASE error.
     *
     * @param password The user password to give ssh.
     */
    void setPassword(TQString password);
     
    /**
     * Access to standard in and out of the ssh process.
     *
     * @return The file description for stdin and stdout of the ssh process.
     */
    int stdioFd() { return ssh.stdioFd(); }

    /**
     * Access to standard error of the ssh process.
     *
     * @return The file descriptior for stderr of the ssh process.
     */
    int stderrFd() { return ssh.stderrFd(); }

    /**
     * Access the pty to which the ssh process is attached.
     *
     * @return The file descriptor of pty to which ssh is attached.
     */
    int pty() { return ssh.fd(); }
private:
    /**
     * Path the the ssh binary.
     */
    TQString mSshPath;
    
    /**
     * SSH version.  This is an index into the supported SSH 
     * versions array, and the various messages arrays.
     */
    SshVersion mVersion;

    /**
     * User's password.  Zero this out when it is no longer needed.
     */
    TQString mPassword;
    
    /**
     * User's username.
     */
    TQString mUsername;
    
    /**
     * Name of host we are connecting to.
     */
    TQString mHost;

    /**
     * Accept new or changed host keys if true.
     */
    bool mAcceptHostKey;
    
    /**
     * Flag to tell use if we have an open, authenticated ssh
     * session going.
     */
    bool mConnected;
    
    /**
     * Flag to tell us if we have started a ssh process, we use this
     * to make sure we kill ssh before going away.
     */
    bool mRunning;

    /**
     * Save any key fingerprint msg from ssh so we can present
     * it to the caller.
     */
    TQString mKeyFingerprint;

    /**
     * The location of the known host key file. We grab this from
     * any error messages ssh prints out.
     */
    TQString mKnownHostsFile;

    /**
     * The state of our connect state machine.
     */
    int mConnectState;
    
    /**
     * Port on on which the target ssh server is listening.
     */
    int mPort;

    /**
     * The last error number encountered. This is only valid for the
     * last error.
     */
    SshError mError;

    /**
     * An error message that corresponds to the error number set in
     * mError.  Optional.
     */
    TQString mErrorMsg;
    
    /**
     * Interface to the SSH process we ceate.  Handles communication
     * to and from the SSH process using stdin, stdout, stderr, and
     * pty.
     */
    MyPtyProcess ssh;

    /**
     * List of arguments we start SSH with.
     */
    QCStringList mArgs;
    void init();

    /**
      * Handler to clean up when ssh process terminates.
      */
    static void SIGCHLD_handler(int signo);
    void installSignalHandlers();
    void removeSignalHandlers();

    TQString getLine();
    
    static TQRegExp versionStrs[];
    static const char * const passwordPrompt[];
    static const char * const passphrasePrompt[];
    static const char * const authSuccessMsg[];
    static const char * const authFailedMsg[];
    static TQRegExp hostKeyMissingMsg[];
    static const char * const hostKeyChangedMsg[];
    static const char * const continuePrompt[];
    static const char * const hostKeyAcceptedMsg[];
    static const char * const tryAgainMsg[];
    static TQRegExp hostKeyVerifyFailedMsg[];
    static const char * const connectionClosedMsg[];
    static const char * const changeHostKeyOnDiskPrompt[];
    static TQRegExp keyFingerprintMsg[];
    static TQRegExp knownHostsFileMsg[];
};
#endif