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); |