Subversion Repositories oidplus

Rev

Rev 1114 | Rev 1196 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1114 Rev 1117
Line 551... Line 551...
551
     *
551
     *
552
     * @see self::__construct()
552
     * @see self::__construct()
553
     * @var array
553
     * @var array
554
     * @access private
554
     * @access private
555
     */
555
     */
556
    private $message_numbers = [];
556
    private static $message_numbers = [];
557
 
557
 
558
    /**
558
    /**
559
     * Disconnection Message 'reason codes' defined in RFC4253
559
     * Disconnection Message 'reason codes' defined in RFC4253
560
     *
560
     *
561
     * @see self::__construct()
561
     * @see self::__construct()
562
     * @var array
562
     * @var array
563
     * @access private
563
     * @access private
564
     */
564
     */
565
    private $disconnect_reasons = [];
565
    private static $disconnect_reasons = [];
566
 
566
 
567
    /**
567
    /**
568
     * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
568
     * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
569
     *
569
     *
570
     * @see self::__construct()
570
     * @see self::__construct()
571
     * @var array
571
     * @var array
572
     * @access private
572
     * @access private
573
     */
573
     */
574
    private $channel_open_failure_reasons = [];
574
    private static $channel_open_failure_reasons = [];
575
 
575
 
576
    /**
576
    /**
577
     * Terminal Modes
577
     * Terminal Modes
578
     *
578
     *
579
     * @link http://tools.ietf.org/html/rfc4254#section-8
579
     * @link http://tools.ietf.org/html/rfc4254#section-8
580
     * @see self::__construct()
580
     * @see self::__construct()
581
     * @var array
581
     * @var array
582
     * @access private
582
     * @access private
583
     */
583
     */
584
    private $terminal_modes = [];
584
    private static $terminal_modes = [];
585
 
585
 
586
    /**
586
    /**
587
     * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
587
     * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
588
     *
588
     *
589
     * @link http://tools.ietf.org/html/rfc4254#section-5.2
589
     * @link http://tools.ietf.org/html/rfc4254#section-5.2
590
     * @see self::__construct()
590
     * @see self::__construct()
591
     * @var array
591
     * @var array
592
     * @access private
592
     * @access private
593
     */
593
     */
594
    private $channel_extended_data_type_codes = [];
594
    private static $channel_extended_data_type_codes = [];
595
 
595
 
596
    /**
596
    /**
597
     * Send Sequence Number
597
     * Send Sequence Number
598
     *
598
     *
599
     * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
599
     * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
Line 645... Line 645...
645
     * @var array
645
     * @var array
646
     */
646
     */
647
    protected $channel_status = [];
647
    protected $channel_status = [];
648
 
648
 
649
    /**
649
    /**
-
 
650
     * The identifier of the interactive channel which was opened most recently
-
 
651
     *
-
 
652
     * @see self::getInteractiveChannelId()
-
 
653
     * @var int
-
 
654
     */
-
 
655
    private $channel_id_last_interactive = 0;
-
 
656
 
-
 
657
    /**
650
     * Packet Size
658
     * Packet Size
651
     *
659
     *
652
     * Maximum packet size indexed by channel
660
     * Maximum packet size indexed by channel
653
     *
661
     *
654
     * @see self::send_channel_packet()
662
     * @see self::send_channel_packet()
Line 836... Line 844...
836
     * @see self::enablePTY()
844
     * @see self::enablePTY()
837
     */
845
     */
838
    private $request_pty = false;
846
    private $request_pty = false;
839
 
847
 
840
    /**
848
    /**
841
     * Flag set while exec() is running when using enablePTY()
-
 
842
     *
-
 
843
     * @var bool
-
 
844
     */
-
 
845
    private $in_request_pty_exec = false;
-
 
846
 
-
 
847
    /**
-
 
848
     * Flag set after startSubsystem() is called
-
 
849
     *
-
 
850
     * @var bool
-
 
851
     */
-
 
852
    private $in_subsystem;
-
 
853
 
-
 
854
    /**
-
 
855
     * Contents of stdError
849
     * Contents of stdError
856
     *
850
     *
857
     * @var string
851
     * @var string
858
     */
852
     */
859
    private $stdErrorLog;
853
    private $stdErrorLog;
Line 1103... Line 1097...
1103
     * @param int $timeout
1097
     * @param int $timeout
1104
     * @see self::login()
1098
     * @see self::login()
1105
     */
1099
     */
1106
    public function __construct($host, $port = 22, $timeout = 10)
1100
    public function __construct($host, $port = 22, $timeout = 10)
1107
    {
1101
    {
-
 
1102
        if (empty(self::$message_numbers)) {
1108
        $this->message_numbers = [
1103
            self::$message_numbers = [
1109
            1 => 'NET_SSH2_MSG_DISCONNECT',
1104
                1 => 'NET_SSH2_MSG_DISCONNECT',
1110
            2 => 'NET_SSH2_MSG_IGNORE',
1105
                2 => 'NET_SSH2_MSG_IGNORE',
1111
            3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
1106
                3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
1112
            4 => 'NET_SSH2_MSG_DEBUG',
1107
                4 => 'NET_SSH2_MSG_DEBUG',
1113
            5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
1108
                5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
Line 1134... Line 1129...
1134
            97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
1129
                97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
1135
            98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
1130
                98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
1136
            99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
1131
                99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
1137
            100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
1132
                100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
1138
        ];
1133
            ];
1139
        $this->disconnect_reasons = [
1134
            self::$disconnect_reasons = [
1140
            1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
1135
                1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
1141
            2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
1136
                2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
1142
            3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
1137
                3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
1143
            4 => 'NET_SSH2_DISCONNECT_RESERVED',
1138
                4 => 'NET_SSH2_DISCONNECT_RESERVED',
1144
            5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
1139
                5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
Line 1151... Line 1146...
1151
            12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
1146
                12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
1152
            13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
1147
                13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
1153
            14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
1148
                14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
1154
            15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
1149
                15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
1155
        ];
1150
            ];
1156
        $this->channel_open_failure_reasons = [
1151
            self::$channel_open_failure_reasons = [
1157
            1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
1152
                1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
1158
        ];
1153
            ];
1159
        $this->terminal_modes = [
1154
            self::$terminal_modes = [
1160
            0 => 'NET_SSH2_TTY_OP_END'
1155
                0 => 'NET_SSH2_TTY_OP_END'
1161
        ];
1156
            ];
1162
        $this->channel_extended_data_type_codes = [
1157
            self::$channel_extended_data_type_codes = [
1163
            1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
1158
                1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
1164
        ];
1159
            ];
1165
 
1160
 
1166
        $this->define_array(
1161
            self::define_array(
1167
            $this->message_numbers,
1162
                self::$message_numbers,
1168
            $this->disconnect_reasons,
1163
                self::$disconnect_reasons,
1169
            $this->channel_open_failure_reasons,
1164
                self::$channel_open_failure_reasons,
1170
            $this->terminal_modes,
1165
                self::$terminal_modes,
1171
            $this->channel_extended_data_type_codes,
1166
                self::$channel_extended_data_type_codes,
1172
            [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
1167
                [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
1173
            [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
1168
                [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
1174
            [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
1169
                [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
1175
                  61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
1170
                      61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
1176
            // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
1171
                // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
Line 1181... Line 1176...
1181
                  34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
1176
                      34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
1182
            // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
1177
                // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
1183
            [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
1178
                [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
1184
                  31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
1179
                      31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
1185
        );
1180
            );
-
 
1181
        }
1186
 
1182
 
1187
        /**
1183
        /**
1188
         * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508
1184
         * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508
1189
         * @var \WeakReference<SSH2>|SSH2
1185
         * @var \WeakReference<SSH2>|SSH2
1190
         */
1186
         */
Line 2727... Line 2723...
2727
 
2723
 
2728
        if (!$this->isAuthenticated()) {
2724
        if (!$this->isAuthenticated()) {
2729
            return false;
2725
            return false;
2730
        }
2726
        }
2731
 
2727
 
2732
        if ($this->in_request_pty_exec) {
2728
        if ($this->isPTYOpen()) {
2733
            throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
2729
            throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
2734
        }
2730
        }
2735
 
2731
 
2736
        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2732
        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2737
        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
2733
        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
Line 2777... Line 2773...
2777
            $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2773
            $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2778
            if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2774
            if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2779
                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
2775
                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
2780
                throw new \RuntimeException('Unable to request pseudo-terminal');
2776
                throw new \RuntimeException('Unable to request pseudo-terminal');
2781
            }
2777
            }
2782
 
-
 
2783
            $this->in_request_pty_exec = true;
-
 
2784
        }
2778
        }
2785
 
2779
 
2786
        // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2780
        // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2787
        // down.  the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
2781
        // down.  the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
2788
        // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2782
        // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
Line 2808... Line 2802...
2808
            return false;
2802
            return false;
2809
        }
2803
        }
2810
 
2804
 
2811
        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2805
        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2812
 
2806
 
2813
        if ($this->in_request_pty_exec) {
2807
        if ($this->request_pty === true) {
-
 
2808
            $this->channel_id_last_interactive = self::CHANNEL_EXEC;
2814
            return true;
2809
            return true;
2815
        }
2810
        }
2816
 
2811
 
2817
        $output = '';
2812
        $output = '';
2818
        while (true) {
2813
        while (true) {
Line 2836... Line 2831...
2836
    }
2831
    }
2837
 
2832
 
2838
    /**
2833
    /**
2839
     * Creates an interactive shell
2834
     * Creates an interactive shell
2840
     *
2835
     *
-
 
2836
     * Returns bool(true) if the shell was opened.
-
 
2837
     * Returns bool(false) if the shell was already open.
-
 
2838
     *
-
 
2839
     * @see self::isShellOpen()
2841
     * @see self::read()
2840
     * @see self::read()
2842
     * @see self::write()
2841
     * @see self::write()
2843
     * @return bool
2842
     * @return bool
-
 
2843
     * @throws InsufficientSetupException if not authenticated
2844
     * @throws \UnexpectedValueException on receipt of unexpected packets
2844
     * @throws \UnexpectedValueException on receipt of unexpected packets
2845
     * @throws \RuntimeException on other errors
2845
     * @throws \RuntimeException on other errors
2846
     */
2846
     */
2847
    private function initShell()
2847
    public function openShell()
2848
    {
2848
    {
2849
        if ($this->in_request_pty_exec === true) {
2849
        if ($this->isShellOpen()) {
2850
            return true;
2850
            return false;
-
 
2851
        }
-
 
2852
 
-
 
2853
        if (!$this->isAuthenticated()) {
-
 
2854
            throw new InsufficientSetupException('Operation disallowed prior to login()');
2851
        }
2855
        }
2852
 
2856
 
2853
        $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
2857
        $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
2854
        $packet_size = 0x4000;
2858
        $packet_size = 0x4000;
2855
 
2859
 
Line 2905... Line 2909...
2905
            throw new \RuntimeException('Unable to request shell');
2909
            throw new \RuntimeException('Unable to request shell');
2906
        }
2910
        }
2907
 
2911
 
2908
        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
2912
        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
2909
 
2913
 
-
 
2914
        $this->channel_id_last_interactive = self::CHANNEL_SHELL;
-
 
2915
 
2910
        $this->bitmap |= self::MASK_SHELL;
2916
        $this->bitmap |= self::MASK_SHELL;
2911
 
2917
 
2912
        return true;
2918
        return true;
2913
    }
2919
    }
2914
 
2920
 
2915
    /**
2921
    /**
2916
     * Return the channel to be used with read() / write()
2922
     * Return the channel to be used with read(), write(), and reset(), if none were specified
-
 
2923
     * @deprecated for lack of transparency in intended channel target, to be potentially replaced
-
 
2924
     *             with method which guarantees open-ness of all yielded channels and throws
2917
     *
2925
     *             error for multiple open channels
2918
     * @see self::read()
2926
     * @see self::read()
2919
     * @see self::write()
2927
     * @see self::write()
2920
     * @return int
2928
     * @return int
2921
     */
2929
     */
2922
    private function get_interactive_channel()
2930
    private function get_interactive_channel()
2923
    {
2931
    {
2924
        switch (true) {
2932
        switch (true) {
2925
            case $this->in_subsystem:
2933
            case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
2926
                return self::CHANNEL_SUBSYSTEM;
2934
                return self::CHANNEL_SUBSYSTEM;
2927
            case $this->in_request_pty_exec:
2935
            case $this->is_channel_status_data(self::CHANNEL_EXEC):
2928
                return self::CHANNEL_EXEC;
2936
                return self::CHANNEL_EXEC;
2929
            default:
2937
            default:
2930
                return self::CHANNEL_SHELL;
2938
                return self::CHANNEL_SHELL;
2931
        }
2939
        }
2932
    }
2940
    }
2933
 
2941
 
2934
    /**
2942
    /**
-
 
2943
     * Indicates the DATA status on the given channel
-
 
2944
     *
-
 
2945
     * @param int $channel The channel number to evaluate
-
 
2946
     * @return bool
-
 
2947
     */
-
 
2948
    private function is_channel_status_data($channel)
-
 
2949
    {
-
 
2950
        return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
-
 
2951
    }
-
 
2952
 
-
 
2953
    /**
2935
     * Return an available open channel
2954
     * Return an available open channel
2936
     *
2955
     *
2937
     * @return int
2956
     * @return int
2938
     */
2957
     */
2939
    private function get_open_channel()
2958
    private function get_open_channel()
Line 2985... Line 3004...
2985
     * Returns the output of an interactive shell
3004
     * Returns the output of an interactive shell
2986
     *
3005
     *
2987
     * Returns when there's a match for $expect, which can take the form of a string literal or,
3006
     * Returns when there's a match for $expect, which can take the form of a string literal or,
2988
     * if $mode == self::READ_REGEX, a regular expression.
3007
     * if $mode == self::READ_REGEX, a regular expression.
2989
     *
3008
     *
-
 
3009
     * If not specifying a channel, an open interactive channel will be selected, or, if there are
-
 
3010
     * no open channels, an interactive shell will be created. If there are multiple open
-
 
3011
     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
-
 
3012
     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
-
 
3013
     * channels, callers are discouraged from relying on this legacy behavior and should specify
-
 
3014
     * the intended channel.
-
 
3015
     *
2990
     * @see self::write()
3016
     * @see self::write()
2991
     * @param string $expect
3017
     * @param string $expect
2992
     * @param int $mode
3018
     * @param int $mode One of the self::READ_* constants
-
 
3019
     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
2993
     * @return string|bool|null
3020
     * @return string|bool|null
2994
     * @throws \RuntimeException on connection error
3021
     * @throws \RuntimeException on connection error
-
 
3022
     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
2995
     */
3023
     */
2996
    public function read($expect = '', $mode = self::READ_SIMPLE)
3024
    public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
2997
    {
3025
    {
2998
        $this->curTimeout = $this->timeout;
3026
        $this->curTimeout = $this->timeout;
2999
        $this->is_timeout = false;
3027
        $this->is_timeout = false;
3000
 
3028
 
3001
        if (!$this->isAuthenticated()) {
3029
        if ($channel === null) {
3002
            throw new InsufficientSetupException('Operation disallowed prior to login()');
3030
            $channel = $this->get_interactive_channel();
3003
        }
3031
        }
3004
 
3032
 
3005
        if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
3033
        if (!$this->isInteractiveChannelOpen($channel)) {
-
 
3034
            if ($channel != self::CHANNEL_SHELL) {
-
 
3035
                throw new InsufficientSetupException('Data is not available on channel');
-
 
3036
            } elseif (!$this->openShell()) {
3006
            throw new \RuntimeException('Unable to initiate an interactive shell session');
3037
                throw new \RuntimeException('Unable to initiate an interactive shell session');
3007
        }
3038
            }
3008
 
-
 
3009
        $channel = $this->get_interactive_channel();
3039
        }
3010
 
3040
 
3011
        if ($mode == self::READ_NEXT) {
3041
        if ($mode == self::READ_NEXT) {
3012
            return $this->get_channel_packet($channel);
3042
            return $this->get_channel_packet($channel);
3013
        }
3043
        }
3014
 
3044
 
Line 3022... Line 3052...
3022
            if ($pos !== false) {
3052
            if ($pos !== false) {
3023
                return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
3053
                return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
3024
            }
3054
            }
3025
            $response = $this->get_channel_packet($channel);
3055
            $response = $this->get_channel_packet($channel);
3026
            if ($response === true) {
3056
            if ($response === true) {
3027
                $this->in_request_pty_exec = false;
-
 
3028
                return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
3057
                return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
3029
            }
3058
            }
3030
 
3059
 
3031
            $this->interactiveBuffer .= $response;
3060
            $this->interactiveBuffer .= $response;
3032
        }
3061
        }
3033
    }
3062
    }
3034
 
3063
 
3035
    /**
3064
    /**
3036
     * Inputs a command into an interactive shell.
3065
     * Inputs a command into an interactive shell.
3037
     *
3066
     *
-
 
3067
     * If not specifying a channel, an open interactive channel will be selected, or, if there are
-
 
3068
     * no open channels, an interactive shell will be created. If there are multiple open
-
 
3069
     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
-
 
3070
     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
-
 
3071
     * channels, callers are discouraged from relying on this legacy behavior and should specify
-
 
3072
     * the intended channel.
-
 
3073
     *
3038
     * @see SSH2::read()
3074
     * @see SSH2::read()
3039
     * @param string $cmd
3075
     * @param string $cmd
-
 
3076
     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3040
     * @return void
3077
     * @return void
3041
     * @throws \RuntimeException on connection error
3078
     * @throws \RuntimeException on connection error
-
 
3079
     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
3042
     */
3080
     */
3043
    public function write($cmd)
3081
    public function write($cmd, $channel = null)
3044
    {
3082
    {
3045
        if (!$this->isAuthenticated()) {
3083
        if ($channel === null) {
3046
            throw new InsufficientSetupException('Operation disallowed prior to login()');
3084
            $channel = $this->get_interactive_channel();
3047
        }
3085
        }
3048
 
3086
 
3049
        if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
3087
        if (!$this->isInteractiveChannelOpen($channel)) {
-
 
3088
            if ($channel != self::CHANNEL_SHELL) {
-
 
3089
                throw new InsufficientSetupException('Data is not available on channel');
-
 
3090
            } elseif (!$this->openShell()) {
3050
            throw new \RuntimeException('Unable to initiate an interactive shell session');
3091
                throw new \RuntimeException('Unable to initiate an interactive shell session');
3051
        }
3092
            }
-
 
3093
        }
3052
 
3094
 
3053
        $this->send_channel_packet($this->get_interactive_channel(), $cmd);
3095
        $this->send_channel_packet($channel, $cmd);
3054
    }
3096
    }
3055
 
3097
 
3056
    /**
3098
    /**
3057
     * Start a subsystem.
3099
     * Start a subsystem.
3058
     *
3100
     *
Line 3101... Line 3143...
3101
            return false;
3143
            return false;
3102
        }
3144
        }
3103
 
3145
 
3104
        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
3146
        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
3105
 
3147
 
3106
        $this->bitmap |= self::MASK_SHELL;
3148
        $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
3107
        $this->in_subsystem = true;
-
 
3108
 
3149
 
3109
        return true;
3150
        return true;
3110
    }
3151
    }
3111
 
3152
 
3112
    /**
3153
    /**
Line 3115... Line 3156...
3115
     * @see self::startSubsystem()
3156
     * @see self::startSubsystem()
3116
     * @return bool
3157
     * @return bool
3117
     */
3158
     */
3118
    public function stopSubsystem()
3159
    public function stopSubsystem()
3119
    {
3160
    {
3120
        $this->in_subsystem = false;
3161
        if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
3121
        $this->close_channel(self::CHANNEL_SUBSYSTEM);
3162
            $this->close_channel(self::CHANNEL_SUBSYSTEM);
-
 
3163
        }
3122
        return true;
3164
        return true;
3123
    }
3165
    }
3124
 
3166
 
3125
    /**
3167
    /**
3126
     * Closes a channel
3168
     * Closes a channel
3127
     *
3169
     *
3128
     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
3170
     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
3129
     *
3171
     *
-
 
3172
     * If not specifying a channel, an open interactive channel will be selected. If there are
-
 
3173
     * multiple open interactive channels, a legacy behavior will apply in which channel selection
-
 
3174
     * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
-
 
3175
     * interactive channels, callers are discouraged from relying on this legacy behavior and
-
 
3176
     * should specify the intended channel.
-
 
3177
     *
-
 
3178
     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
-
 
3179
     * @return void
3130
     */
3180
     */
3131
    public function reset()
3181
    public function reset($channel = null)
3132
    {
3182
    {
-
 
3183
        if ($channel === null) {
3133
        $this->close_channel($this->get_interactive_channel());
3184
            $channel = $this->get_interactive_channel();
-
 
3185
        }
-
 
3186
        if ($this->isInteractiveChannelOpen($channel)) {
-
 
3187
            $this->close_channel($channel);
-
 
3188
        }
3134
    }
3189
    }
3135
 
3190
 
3136
    /**
3191
    /**
3137
     * Is timeout?
3192
     * Is timeout?
3138
     *
3193
     *
Line 3188... Line 3243...
3188
    {
3243
    {
3189
        return (bool) ($this->bitmap & self::MASK_LOGIN);
3244
        return (bool) ($this->bitmap & self::MASK_LOGIN);
3190
    }
3245
    }
3191
 
3246
 
3192
    /**
3247
    /**
-
 
3248
     * Is the interactive shell active?
-
 
3249
     *
-
 
3250
     * @return bool
-
 
3251
     */
-
 
3252
    public function isShellOpen()
-
 
3253
    {
-
 
3254
        return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
-
 
3255
    }
-
 
3256
 
-
 
3257
    /**
-
 
3258
     * Is the exec pty active?
-
 
3259
     *
-
 
3260
     * @return bool
-
 
3261
     */
-
 
3262
    public function isPTYOpen()
-
 
3263
    {
-
 
3264
        return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
-
 
3265
    }
-
 
3266
 
-
 
3267
    /**
-
 
3268
     * Is the given interactive channel active?
-
 
3269
     *
-
 
3270
     * @param int $channel Channel id returned by self::getInteractiveChannelId()
-
 
3271
     * @return bool
-
 
3272
     */
-
 
3273
    public function isInteractiveChannelOpen($channel)
-
 
3274
    {
-
 
3275
        return $this->isAuthenticated() && $this->is_channel_status_data($channel);
-
 
3276
    }
-
 
3277
 
-
 
3278
    /**
-
 
3279
     * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
-
 
3280
     * Returns 0 if no interactive channel has been opened.
-
 
3281
     *
-
 
3282
     * @see self::isInteractiveChannelOpen()
-
 
3283
     * @return int
-
 
3284
     */
-
 
3285
    public function getInteractiveChannelId()
-
 
3286
    {
-
 
3287
        return $this->channel_id_last_interactive;
-
 
3288
    }
-
 
3289
 
-
 
3290
    /**
3193
     * Pings a server connection, or tries to reconnect if the connection has gone down
3291
     * Pings a server connection, or tries to reconnect if the connection has gone down
3194
     *
3292
     *
3195
     * Inspired by http://php.net/manual/en/mysqli.ping.php
3293
     * Inspired by http://php.net/manual/en/mysqli.ping.php
3196
     *
3294
     *
3197
     * @return bool
3295
     * @return bool
Line 3502... Line 3600...
3502
 
3600
 
3503
        $this->get_seq_no++;
3601
        $this->get_seq_no++;
3504
 
3602
 
3505
        if (defined('NET_SSH2_LOGGING')) {
3603
        if (defined('NET_SSH2_LOGGING')) {
3506
            $current = microtime(true);
3604
            $current = microtime(true);
3507
            $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
3605
            $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
3508
            $message_number = '<- ' . $message_number .
3606
            $message_number = '<- ' . $message_number .
3509
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
3607
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
3510
            $this->append_log($message_number, $payload);
3608
            $this->append_log($message_number, $payload);
3511
            $this->last_packet = $current;
3609
            $this->last_packet = $current;
3512
        }
3610
        }
Line 3584... Line 3682...
3584
    {
3682
    {
3585
        switch (ord($payload[0])) {
3683
        switch (ord($payload[0])) {
3586
            case NET_SSH2_MSG_DISCONNECT:
3684
            case NET_SSH2_MSG_DISCONNECT:
3587
                Strings::shift($payload, 1);
3685
                Strings::shift($payload, 1);
3588
                list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
3686
                list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
3589
                $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message";
3687
                $this->errors[] = 'SSH_MSG_DISCONNECT: ' . static::$disconnect_reasons[$reason_code] . "\r\n$message";
3590
                $this->bitmap = 0;
3688
                $this->bitmap = 0;
3591
                return false;
3689
                return false;
3592
            case NET_SSH2_MSG_IGNORE:
3690
            case NET_SSH2_MSG_IGNORE:
3593
                $payload = $this->get_binary_packet($skip_channel_filter);
3691
                $payload = $this->get_binary_packet($skip_channel_filter);
3594
                break;
3692
                break;
Line 3771... Line 3869...
3771
     * Disable request-pty when using exec()
3869
     * Disable request-pty when using exec()
3772
     *
3870
     *
3773
     */
3871
     */
3774
    public function disablePTY()
3872
    public function disablePTY()
3775
    {
3873
    {
3776
        if ($this->in_request_pty_exec) {
3874
        if ($this->isPTYOpen()) {
3777
            $this->close_channel(self::CHANNEL_EXEC);
3875
            $this->close_channel(self::CHANNEL_EXEC);
3778
            $this->in_request_pty_exec = false;
-
 
3779
        }
3876
        }
3780
        $this->request_pty = false;
3877
        $this->request_pty = false;
3781
    }
3878
    }
3782
 
3879
 
3783
    /**
3880
    /**
Line 3799... Line 3896...
3799
     *
3896
     *
3800
     * - the server closes the channel
3897
     * - the server closes the channel
3801
     * - if the connection times out
3898
     * - if the connection times out
3802
     * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
3899
     * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
3803
     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
3900
     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
-
 
3901
     * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
3804
     *
3902
     *
3805
     * bool(false) is returned if:
3903
     * bool(false) is returned if:
3806
     *
3904
     *
3807
     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
3905
     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
3808
     *
3906
     *
Line 3966... Line 4064...
3966
                            default:
4064
                            default:
3967
                                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4065
                                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3968
                                throw new \RuntimeException('Unable to fulfill channel request');
4066
                                throw new \RuntimeException('Unable to fulfill channel request');
3969
                        }
4067
                        }
3970
                    case NET_SSH2_MSG_CHANNEL_CLOSE:
4068
                    case NET_SSH2_MSG_CHANNEL_CLOSE:
-
 
4069
                        if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
-
 
4070
                            return true;
-
 
4071
                        }
3971
                        return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
4072
                        return $this->get_channel_packet($client_channel, $skip_extended);
3972
                }
4073
                }
3973
            }
4074
            }
3974
 
4075
 
3975
            // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
4076
            // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
3976
 
4077
 
Line 4001... Line 4102...
4001
                    $this->channel_buffers[$channel][] = chr($type) . $data;
4102
                    $this->channel_buffers[$channel][] = chr($type) . $data;
4002
                    break;
4103
                    break;
4003
                case NET_SSH2_MSG_CHANNEL_CLOSE:
4104
                case NET_SSH2_MSG_CHANNEL_CLOSE:
4004
                    $this->curTimeout = 5;
4105
                    $this->curTimeout = 5;
4005
 
4106
 
4006
                    if ($this->bitmap & self::MASK_SHELL) {
-
 
4007
                        $this->bitmap &= ~self::MASK_SHELL;
4107
                    $this->close_channel_bitmap($channel);
4008
                    }
-
 
-
 
4108
 
4009
                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
4109
                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
4010
                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4110
                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4011
                    }
4111
                    }
4012
 
4112
 
4013
                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4113
                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
Line 4155... Line 4255...
4155
        $sent = @fputs($this->fsock, $packet);
4255
        $sent = @fputs($this->fsock, $packet);
4156
        $stop = microtime(true);
4256
        $stop = microtime(true);
4157
 
4257
 
4158
        if (defined('NET_SSH2_LOGGING')) {
4258
        if (defined('NET_SSH2_LOGGING')) {
4159
            $current = microtime(true);
4259
            $current = microtime(true);
4160
            $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
4260
            $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
4161
            $message_number = '-> ' . $message_number .
4261
            $message_number = '-> ' . $message_number .
4162
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
4262
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
4163
            $this->append_log($message_number, $logged);
4263
            $this->append_log($message_number, $logged);
4164
            $this->last_packet = $current;
4264
            $this->last_packet = $current;
4165
        }
4265
        }
Line 4346... Line 4446...
4346
        $this->curTimeout = 5;
4446
        $this->curTimeout = 5;
4347
 
4447
 
4348
        while (!is_bool($this->get_channel_packet($client_channel))) {
4448
        while (!is_bool($this->get_channel_packet($client_channel))) {
4349
        }
4449
        }
4350
 
4450
 
4351
        if ($this->is_timeout) {
-
 
4352
            $this->disconnect();
-
 
4353
        }
-
 
4354
 
-
 
4355
        if ($want_reply) {
4451
        if ($want_reply) {
4356
            $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4452
            $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4357
        }
4453
        }
4358
 
4454
 
-
 
4455
        $this->close_channel_bitmap($client_channel);
-
 
4456
    }
-
 
4457
 
-
 
4458
    /**
-
 
4459
     * Maintains execution state bitmap in response to channel closure
-
 
4460
     *
-
 
4461
     * @param int $client_channel The channel number to maintain closure status of
-
 
4462
     * @return void
-
 
4463
     */
-
 
4464
    private function close_channel_bitmap($client_channel)
-
 
4465
    {
-
 
4466
        switch ($client_channel) {
-
 
4467
            case self::CHANNEL_SHELL:
-
 
4468
                // Shell status has been maintained in the bitmap for backwards
-
 
4469
                //  compatibility sake, but can be removed going forward
4359
        if ($this->bitmap & self::MASK_SHELL) {
4470
                if ($this->bitmap & self::MASK_SHELL) {
4360
            $this->bitmap &= ~self::MASK_SHELL;
4471
                    $this->bitmap &= ~self::MASK_SHELL;
4361
        }
4472
                }
-
 
4473
                break;
-
 
4474
        }
4362
    }
4475
    }
4363
 
4476
 
4364
    /**
4477
    /**
4365
     * Disconnect
4478
     * Disconnect
4366
     *
4479
     *
Line 4393... Line 4506...
4393
     * If any of the constants that would be defined already exists, none of the constants will be defined.
4506
     * If any of the constants that would be defined already exists, none of the constants will be defined.
4394
     *
4507
     *
4395
     * @param mixed[] ...$args
4508
     * @param mixed[] ...$args
4396
     * @access protected
4509
     * @access protected
4397
     */
4510
     */
4398
    protected function define_array(...$args)
4511
    protected static function define_array(...$args)
4399
    {
4512
    {
4400
        foreach ($args as $arg) {
4513
        foreach ($args as $arg) {
4401
            foreach ($arg as $key => $value) {
4514
            foreach ($arg as $key => $value) {
4402
                if (!defined($value)) {
4515
                if (!defined($value)) {
4403
                    define($value, $key);
4516
                    define($value, $key);