Rev 1249 | Rev 1324 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1249 | Rev 1284 | ||
---|---|---|---|
Line 1086... | Line 1086... | ||
1086 | * @var bool |
1086 | * @var bool |
1087 | */ |
1087 | */ |
1088 | private $smartMFA = true; |
1088 | private $smartMFA = true; |
1089 | 1089 | ||
1090 | /** |
1090 | /** |
- | 1091 | * How many channels are currently opened |
|
- | 1092 | * |
|
- | 1093 | * @var int |
|
- | 1094 | */ |
|
- | 1095 | private $channelCount = 0; |
|
- | 1096 | ||
- | 1097 | /** |
|
- | 1098 | * Does the server support multiple channels? If not then error out |
|
- | 1099 | * when multiple channels are attempted to be opened |
|
- | 1100 | * |
|
- | 1101 | * @var bool |
|
- | 1102 | */ |
|
- | 1103 | private $errorOnMultipleChannels; |
|
- | 1104 | ||
- | 1105 | /** |
|
1091 | * Default Constructor. |
1106 | * Default Constructor. |
1092 | * |
1107 | * |
1093 | * $host can either be a string, representing the host, or a stream resource. |
1108 | * $host can either be a string, representing the host, or a stream resource. |
1094 | * |
1109 | * |
1095 | * @param mixed $host |
1110 | * @param mixed $host |
Line 1382... | Line 1397... | ||
1382 | if (version_compare($matches[3], '1.99', '<')) { |
1397 | if (version_compare($matches[3], '1.99', '<')) { |
1383 | $this->bitmap = 0; |
1398 | $this->bitmap = 0; |
1384 | throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); |
1399 | throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); |
1385 | } |
1400 | } |
1386 | 1401 | ||
- | 1402 | // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see |
|
- | 1403 | // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info. |
|
- | 1404 | // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses |
|
- | 1405 | // when consolekit was incorporated. |
|
- | 1406 | // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the |
|
- | 1407 | // issues with how Ubuntu incorporated consolekit |
|
- | 1408 | $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#'; |
|
- | 1409 | $match = preg_match($pattern, $this->server_identifier, $matches); |
|
- | 1410 | $match = $match && version_compare('5.8', $matches[1], '<='); |
|
- | 1411 | $match = $match && version_compare('6.9', $matches[1], '>='); |
|
- | 1412 | $this->errorOnMultipleChannels = $match; |
|
- | 1413 | ||
1387 | if (!$this->send_id_string_first) { |
1414 | if (!$this->send_id_string_first) { |
1388 | fputs($this->fsock, $this->identifier . "\r\n"); |
1415 | fputs($this->fsock, $this->identifier . "\r\n"); |
1389 | } |
1416 | } |
1390 | 1417 | ||
1391 | if (!$this->send_kex_first) { |
1418 | if (!$this->send_kex_first) { |
Line 2723... | Line 2750... | ||
2723 | 2750 | ||
2724 | if (!$this->isAuthenticated()) { |
2751 | if (!$this->isAuthenticated()) { |
2725 | return false; |
2752 | return false; |
2726 | } |
2753 | } |
2727 | 2754 | ||
2728 | if ($this->isPTYOpen()) { |
2755 | //if ($this->isPTYOpen()) { |
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.'); |
2756 | // 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.'); |
2730 | } |
2757 | //} |
2731 | - | ||
2732 | // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to |
- | |
2733 | // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, |
- | |
2734 | // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. |
- | |
2735 | // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info |
- | |
2736 | $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size; |
- | |
2737 | // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy |
- | |
2738 | // uses 0x4000, that's what will be used here, as well. |
- | |
2739 | $packet_size = 0x4000; |
- | |
2740 | - | ||
2741 | $packet = Strings::packSSH2( |
- | |
2742 | 'CsN3', |
- | |
2743 | NET_SSH2_MSG_CHANNEL_OPEN, |
- | |
2744 | 'session', |
- | |
2745 | self::CHANNEL_EXEC, |
- | |
2746 | $this->window_size_server_to_client[self::CHANNEL_EXEC], |
- | |
2747 | $packet_size |
- | |
2748 | ); |
- | |
2749 | $this->send_binary_packet($packet); |
- | |
2750 | - | ||
2751 | $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN; |
- | |
2752 | 2758 | ||
2753 | $this->get_channel_packet(self::CHANNEL_EXEC); |
2759 | $this->openChannel(self::CHANNEL_EXEC); |
2754 | 2760 | ||
2755 | if ($this->request_pty === true) { |
2761 | if ($this->request_pty === true) { |
2756 | $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); |
2762 | $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); |
2757 | $packet = Strings::packSSH2( |
2763 | $packet = Strings::packSSH2( |
2758 | 'CNsCsN4s', |
2764 | 'CNsCsN4s', |
Line 2829... | Line 2835... | ||
2829 | } |
2835 | } |
2830 | } |
2836 | } |
2831 | } |
2837 | } |
2832 | 2838 | ||
2833 | /** |
2839 | /** |
2834 | * Creates an interactive shell |
2840 | * How many channels are currently open? |
2835 | * |
2841 | * |
- | 2842 | * @return int |
|
- | 2843 | */ |
|
2836 | * Returns bool(true) if the shell was opened. |
2844 | public function getOpenChannelCount() |
- | 2845 | { |
|
2837 | * Returns bool(false) if the shell was already open. |
2846 | return $this->channelCount; |
- | 2847 | } |
|
- | 2848 | ||
- | 2849 | /** |
|
- | 2850 | * Opens a channel |
|
2838 | * |
2851 | * |
2839 | * @see self::isShellOpen() |
- | |
2840 | * @see self::read() |
2852 | * @param string $channel |
2841 | * @see self::write() |
2853 | * @param bool $skip_extended |
2842 | * @return bool |
2854 | * @return bool |
2843 | * @throws InsufficientSetupException if not authenticated |
- | |
2844 | * @throws \UnexpectedValueException on receipt of unexpected packets |
- | |
2845 | * @throws \RuntimeException on other errors |
- | |
2846 | */ |
2855 | */ |
2847 | public function openShell() |
2856 | protected function openChannel($channel, $skip_extended = false) |
2848 | { |
2857 | { |
2849 | if ($this->isShellOpen()) { |
2858 | if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) { |
2850 | return false; |
2859 | throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again'); |
2851 | } |
2860 | } |
2852 | 2861 | ||
2853 | if (!$this->isAuthenticated()) { |
2862 | $this->channelCount++; |
- | 2863 | ||
- | 2864 | if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { |
|
2854 | throw new InsufficientSetupException('Operation disallowed prior to login()'); |
2865 | throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels"); |
2855 | } |
2866 | } |
2856 | 2867 | ||
- | 2868 | // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to |
|
- | 2869 | // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, |
|
- | 2870 | // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. |
|
- | 2871 | // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info |
|
2857 | $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; |
2872 | $this->window_size_server_to_client[$channel] = $this->window_size; |
- | 2873 | // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy |
|
- | 2874 | // uses 0x4000, that's what will be used here, as well. |
|
2858 | $packet_size = 0x4000; |
2875 | $packet_size = 0x4000; |
2859 | 2876 | ||
2860 | $packet = Strings::packSSH2( |
2877 | $packet = Strings::packSSH2( |
2861 | 'CsN3', |
2878 | 'CsN3', |
2862 | NET_SSH2_MSG_CHANNEL_OPEN, |
2879 | NET_SSH2_MSG_CHANNEL_OPEN, |
2863 | 'session', |
2880 | 'session', |
2864 | self::CHANNEL_SHELL, |
2881 | $channel, |
2865 | $this->window_size_server_to_client[self::CHANNEL_SHELL], |
2882 | $this->window_size_server_to_client[$channel], |
2866 | $packet_size |
2883 | $packet_size |
2867 | ); |
2884 | ); |
2868 | 2885 | ||
2869 | $this->send_binary_packet($packet); |
2886 | $this->send_binary_packet($packet); |
2870 | 2887 | ||
2871 | $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; |
2888 | $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN; |
2872 | 2889 | ||
- | 2890 | return $this->get_channel_packet($channel, $skip_extended); |
|
- | 2891 | } |
|
- | 2892 | ||
- | 2893 | /** |
|
- | 2894 | * Creates an interactive shell |
|
- | 2895 | * |
|
- | 2896 | * Returns bool(true) if the shell was opened. |
|
- | 2897 | * Returns bool(false) if the shell was already open. |
|
- | 2898 | * |
|
- | 2899 | * @see self::isShellOpen() |
|
- | 2900 | * @see self::read() |
|
- | 2901 | * @see self::write() |
|
- | 2902 | * @return bool |
|
- | 2903 | * @throws InsufficientSetupException if not authenticated |
|
- | 2904 | * @throws \UnexpectedValueException on receipt of unexpected packets |
|
- | 2905 | * @throws \RuntimeException on other errors |
|
- | 2906 | */ |
|
- | 2907 | public function openShell() |
|
- | 2908 | { |
|
- | 2909 | if (!$this->isAuthenticated()) { |
|
- | 2910 | throw new InsufficientSetupException('Operation disallowed prior to login()'); |
|
- | 2911 | } |
|
- | 2912 | ||
2873 | $this->get_channel_packet(self::CHANNEL_SHELL); |
2913 | $this->openChannel(self::CHANNEL_SHELL); |
2874 | 2914 | ||
2875 | $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); |
2915 | $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); |
2876 | $packet = Strings::packSSH2( |
2916 | $packet = Strings::packSSH2( |
2877 | 'CNsbsN4s', |
2917 | 'CNsbsN4s', |
2878 | NET_SSH2_MSG_CHANNEL_REQUEST, |
2918 | NET_SSH2_MSG_CHANNEL_REQUEST, |
Line 3021... | Line 3061... | ||
3021 | * @throws \RuntimeException on connection error |
3061 | * @throws \RuntimeException on connection error |
3022 | * @throws InsufficientSetupException on unexpected channel status, possibly due to closure |
3062 | * @throws InsufficientSetupException on unexpected channel status, possibly due to closure |
3023 | */ |
3063 | */ |
3024 | public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null) |
3064 | public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null) |
3025 | { |
3065 | { |
- | 3066 | if (!$this->isAuthenticated()) { |
|
- | 3067 | throw new InsufficientSetupException('Operation disallowed prior to login()'); |
|
- | 3068 | } |
|
- | 3069 | ||
3026 | $this->curTimeout = $this->timeout; |
3070 | $this->curTimeout = $this->timeout; |
3027 | $this->is_timeout = false; |
3071 | $this->is_timeout = false; |
3028 | 3072 | ||
3029 | if ($channel === null) { |
3073 | if ($channel === null) { |
3030 | $channel = $this->get_interactive_channel(); |
3074 | $channel = $this->get_interactive_channel(); |
3031 | } |
3075 | } |
3032 | 3076 | ||
3033 | if (!$this->isInteractiveChannelOpen($channel)) { |
3077 | if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) { |
3034 | if ($channel != self::CHANNEL_SHELL) { |
3078 | if ($channel != self::CHANNEL_SHELL) { |
3035 | throw new InsufficientSetupException('Data is not available on channel'); |
3079 | throw new InsufficientSetupException('Data is not available on channel'); |
3036 | } elseif (!$this->openShell()) { |
3080 | } elseif (!$this->openShell()) { |
3037 | throw new \RuntimeException('Unable to initiate an interactive shell session'); |
3081 | throw new \RuntimeException('Unable to initiate an interactive shell session'); |
3038 | } |
3082 | } |
Line 3078... | Line 3122... | ||
3078 | * @throws \RuntimeException on connection error |
3122 | * @throws \RuntimeException on connection error |
3079 | * @throws InsufficientSetupException on unexpected channel status, possibly due to closure |
3123 | * @throws InsufficientSetupException on unexpected channel status, possibly due to closure |
3080 | */ |
3124 | */ |
3081 | public function write($cmd, $channel = null) |
3125 | public function write($cmd, $channel = null) |
3082 | { |
3126 | { |
- | 3127 | if (!$this->isAuthenticated()) { |
|
- | 3128 | throw new InsufficientSetupException('Operation disallowed prior to login()'); |
|
- | 3129 | } |
|
- | 3130 | ||
3083 | if ($channel === null) { |
3131 | if ($channel === null) { |
3084 | $channel = $this->get_interactive_channel(); |
3132 | $channel = $this->get_interactive_channel(); |
3085 | } |
3133 | } |
3086 | 3134 | ||
3087 | if (!$this->isInteractiveChannelOpen($channel)) { |
3135 | if (!$this->is_channel_status_data($channel)) { |
3088 | if ($channel != self::CHANNEL_SHELL) { |
3136 | if ($channel != self::CHANNEL_SHELL) { |
3089 | throw new InsufficientSetupException('Data is not available on channel'); |
3137 | throw new InsufficientSetupException('Data is not available on channel'); |
3090 | } elseif (!$this->openShell()) { |
3138 | } elseif (!$this->openShell()) { |
3091 | throw new \RuntimeException('Unable to initiate an interactive shell session'); |
3139 | throw new \RuntimeException('Unable to initiate an interactive shell session'); |
3092 | } |
3140 | } |
Line 3108... | Line 3156... | ||
3108 | * @param string $subsystem |
3156 | * @param string $subsystem |
3109 | * @return bool |
3157 | * @return bool |
3110 | */ |
3158 | */ |
3111 | public function startSubsystem($subsystem) |
3159 | public function startSubsystem($subsystem) |
3112 | { |
3160 | { |
3113 | $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; |
- | |
3114 | - | ||
3115 | $packet = Strings::packSSH2( |
- | |
3116 | 'CsN3', |
- | |
3117 | NET_SSH2_MSG_CHANNEL_OPEN, |
- | |
3118 | 'session', |
- | |
3119 | self::CHANNEL_SUBSYSTEM, |
- | |
3120 | $this->window_size, |
- | |
3121 | 0x4000 |
- | |
3122 | ); |
- | |
3123 | - | ||
3124 | $this->send_binary_packet($packet); |
- | |
3125 | - | ||
3126 | $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN; |
- | |
3127 | - | ||
3128 | $this->get_channel_packet(self::CHANNEL_SUBSYSTEM); |
3161 | $this->openChannel(self::CHANNEL_SUBSYSTEM); |
3129 | 3162 | ||
3130 | $packet = Strings::packSSH2( |
3163 | $packet = Strings::packSSH2( |
3131 | 'CNsCs', |
3164 | 'CNsCs', |
3132 | NET_SSH2_MSG_CHANNEL_REQUEST, |
3165 | NET_SSH2_MSG_CHANNEL_REQUEST, |
3133 | $this->server_channels[self::CHANNEL_SUBSYSTEM], |
3166 | $this->server_channels[self::CHANNEL_SUBSYSTEM], |
Line 3301... | Line 3334... | ||
3301 | return $this->reconnect(); |
3334 | return $this->reconnect(); |
3302 | } |
3335 | } |
3303 | return false; |
3336 | return false; |
3304 | } |
3337 | } |
3305 | 3338 | ||
3306 | $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; |
- | |
3307 | $packet_size = 0x4000; |
- | |
3308 | $packet = Strings::packSSH2( |
- | |
3309 | 'CsN3', |
- | |
3310 | NET_SSH2_MSG_CHANNEL_OPEN, |
- | |
3311 | 'session', |
- | |
3312 | self::CHANNEL_KEEP_ALIVE, |
- | |
3313 | $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], |
- | |
3314 | $packet_size |
- | |
3315 | ); |
- | |
3316 | - | ||
3317 | try { |
3339 | try { |
3318 | $this->send_binary_packet($packet); |
- | |
3319 | - | ||
3320 | $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; |
- | |
3321 | - | ||
3322 | $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE); |
3340 | $this->openChannel(self::CHANNEL_KEEP_ALIVE); |
3323 | } catch (\RuntimeException $e) { |
3341 | } catch (\RuntimeException $e) { |
3324 | return $this->reconnect(); |
3342 | return $this->reconnect(); |
3325 | } |
3343 | } |
3326 | 3344 | ||
3327 | $this->close_channel(self::CHANNEL_KEEP_ALIVE); |
3345 | $this->close_channel(self::CHANNEL_KEEP_ALIVE); |
Line 4109... | Line 4127... | ||
4109 | if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { |
4127 | if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { |
4110 | $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); |
4128 | $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); |
4111 | } |
4129 | } |
4112 | 4130 | ||
4113 | $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
4131 | $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
- | 4132 | $this->channelCount--; |
|
- | 4133 | ||
4114 | if ($client_channel == $channel) { |
4134 | if ($client_channel == $channel) { |
4115 | return true; |
4135 | return true; |
4116 | } |
4136 | } |
4117 | // fall-through |
4137 | // fall-through |
4118 | case NET_SSH2_MSG_CHANNEL_EOF: |
4138 | case NET_SSH2_MSG_CHANNEL_EOF: |
Line 4443... | Line 4463... | ||
4443 | if (!$want_reply) { |
4463 | if (!$want_reply) { |
4444 | $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); |
4464 | $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); |
4445 | } |
4465 | } |
4446 | 4466 | ||
4447 | $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
4467 | $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
- | 4468 | $this->channelCount--; |
|
4448 | 4469 | ||
4449 | $this->curTimeout = 5; |
4470 | $this->curTimeout = 5; |
4450 | 4471 | ||
4451 | while (!is_bool($this->get_channel_packet($client_channel))) { |
4472 | while (!is_bool($this->get_channel_packet($client_channel))) { |
4452 | } |
4473 | } |
Line 4915... | Line 4936... | ||
4915 | ] |
4936 | ] |
4916 | ]; |
4937 | ]; |
4917 | } |
4938 | } |
4918 | 4939 | ||
4919 | /** |
4940 | /** |
- | 4941 | * Force multiple channels (even if phpseclib has decided to disable them) |
|
- | 4942 | */ |
|
- | 4943 | public function forceMultipleChannels() |
|
- | 4944 | { |
|
- | 4945 | $this->errorOnMultipleChannels = false; |
|
- | 4946 | } |
|
- | 4947 | ||
- | 4948 | /** |
|
4920 | * Allows you to set the terminal |
4949 | * Allows you to set the terminal |
4921 | * |
4950 | * |
4922 | * @param string $term |
4951 | * @param string $term |
4923 | */ |
4952 | */ |
4924 | public function setTerminal($term) |
4953 | public function setTerminal($term) |