1 /*
2 MIT License
3
4 Copyright (c) 2020 Mikhail Milovidov
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 #include "Precompiled.h"
25 #include "Ssh2Channel.h"
26
27 #include "Ssh2Client.h"
28 #include "Ssh2Debug.h"
29
30 using namespace daggyssh2;
31
Ssh2Channel(Ssh2Client * ssh2_client)32 Ssh2Channel::Ssh2Channel(Ssh2Client* ssh2_client)
33 : QIODevice(ssh2_client)
34 , ssh2_channel_state_(ChannelStates::NotOpen)
35 , ssh2_channel_(nullptr)
36 , exit_status_(-1)
37 , exit_signal_("none")
38 , last_error_(ssh2_success)
39 {
40 }
41
~Ssh2Channel()42 Ssh2Channel::~Ssh2Channel()
43 {
44 destroyChannel();
45 }
46
writeData(const char * data,qint64 len)47 qint64 Ssh2Channel::writeData(const char* data, qint64 len)
48 {
49 if (ssh2_channel_ == nullptr)
50 return -1;
51 ssize_t result = libssh2_channel_write_ex(ssh2_channel_, 0, data, len);
52 if (result < 0 && result != LIBSSH2_ERROR_EAGAIN) {
53 switch (result) {
54 case LIBSSH2_ERROR_CHANNEL_CLOSED:
55 destroyChannel();
56 break;
57 default:
58 setLastError(Ssh2Error::ChannelWriteError);
59 }
60 result = -1;
61 }
62 return result;
63 }
64
readData(char * data,qint64 maxlen)65 qint64 Ssh2Channel::readData(char* data, qint64 maxlen)
66 {
67 if (ssh2_channel_ == nullptr)
68 return -1;
69
70 ssize_t result = libssh2_channel_read_ex(ssh2_channel_, currentReadChannel(), data, maxlen);
71 if (result < 0 && result != LIBSSH2_ERROR_EAGAIN) {
72 switch (result) {
73 case LIBSSH2_ERROR_CHANNEL_CLOSED:
74 destroyChannel();
75 break;
76 default:
77 setLastError(Ssh2Error::ChannelReadError);
78 }
79
80 result = -1;
81 }
82 return result;
83 }
84
isSequential() const85 bool Ssh2Channel::isSequential() const
86 {
87 return true;
88 }
89
open(QIODevice::OpenMode)90 bool Ssh2Channel::open(QIODevice::OpenMode)
91 {
92 if (ssh2_channel_)
93 return true;
94 if (ssh2Client()->sessionState() != Ssh2Client::Established)
95 return false;
96
97 std::error_code error_code = openSession();
98 setLastError(error_code);
99 return checkSsh2Error(error_code);
100 }
101
close()102 void Ssh2Channel::close()
103 {
104 if (ssh2_channel_ == nullptr)
105 return;
106 if (ssh2_channel_state_ == Opened) {
107 emit aboutToClose();
108 //libssh2_channel_send_eof(ssh2_channel_);
109 std::error_code error_code = closeSession();
110 setLastError(error_code);
111 } else
112 destroyChannel();
113 }
114
ssh2Client() const115 Ssh2Client* Ssh2Channel::ssh2Client() const
116 {
117 return qobject_cast<Ssh2Client*>(parent());
118 }
119
ssh2Channel() const120 LIBSSH2_CHANNEL* Ssh2Channel::ssh2Channel() const
121 {
122 return ssh2_channel_;
123 }
124
checkChannelData()125 void Ssh2Channel::checkChannelData()
126 {
127 checkChannelData(Out);
128 checkChannelData(Err);
129 }
130
checkIncomingData()131 void Ssh2Channel::checkIncomingData()
132 {
133 std::error_code error_code = ssh2_success;
134 switch (ssh2_channel_state_) {
135 case ChannelStates::Opening:
136 error_code = openSession();
137 break;
138 case ChannelStates::Opened:
139 checkChannelData();
140 if (libssh2_channel_eof(ssh2_channel_) == 1) {
141 close();
142 }
143 break;
144 case ChannelStates::Closing:
145 checkChannelData();
146 error_code = closeSession();
147 break;
148 default:;
149 }
150
151 setLastError(error_code);
152 }
153
exitStatus() const154 int Ssh2Channel::exitStatus() const
155 {
156 return exit_status_;
157 }
158
channelState() const159 Ssh2Channel::ChannelStates Ssh2Channel::channelState() const
160 {
161 return ssh2_channel_state_;
162 }
163
setSsh2ChannelState(const ChannelStates & state)164 void Ssh2Channel::setSsh2ChannelState(const ChannelStates& state) {
165 if (ssh2_channel_state_ != state) {
166 ssh2_channel_state_ = state;
167 emit channelStateChanged(ssh2_channel_state_);
168 }
169 }
170
openSession()171 std::error_code Ssh2Channel::openSession()
172 {
173 std::error_code error_code = ssh2_success;
174 int ssh2_method_result = 0;
175
176 LIBSSH2_SESSION* const ssh2_session = ssh2Client()->ssh2Session();
177 ssh2_channel_ = libssh2_channel_open_session(ssh2_session);
178 if (ssh2_channel_ == nullptr)
179 ssh2_method_result = libssh2_session_last_error(ssh2Client()->ssh2Session(),
180 nullptr,
181 nullptr,
182 0);
183 switch (ssh2_method_result) {
184 case LIBSSH2_ERROR_EAGAIN:
185 setSsh2ChannelState(Opening);
186 error_code = Ssh2Error::TryAgain;
187 break;
188 case 0:
189 QIODevice::open(QIODevice::ReadWrite | QIODevice::Unbuffered);
190 setSsh2ChannelState(Opened);
191 error_code = ssh2_success;
192 break;
193 default: {
194 debugSsh2Error(ssh2_method_result);
195 error_code = Ssh2Error::FailedToOpenChannel;
196 setSsh2ChannelState(FailedToOpen);
197 }
198 }
199 return error_code;
200 }
201
closeSession()202 std::error_code Ssh2Channel::closeSession()
203 {
204 std::error_code error_code = ssh2_success;
205 libssh2_channel_flush_ex(ssh2_channel_, 0);
206 libssh2_channel_flush_ex(ssh2_channel_, 1);
207 const int ssh2_method_result = libssh2_channel_send_eof(ssh2_channel_);
208 switch (ssh2_method_result) {
209 case LIBSSH2_ERROR_EAGAIN:
210 setSsh2ChannelState(ChannelStates::Closing);
211 error_code = Ssh2Error::TryAgain;
212 break;
213 case 0: {
214 exit_status_ = libssh2_channel_get_exit_status(ssh2_channel_);
215 char* exit_signal = (char*)"none";
216 const int result = libssh2_channel_get_exit_signal(ssh2_channel_,
217 &exit_signal,
218 nullptr,
219 nullptr,
220 nullptr,
221 nullptr,
222 nullptr);
223 if (result == 0)
224 exit_signal_ = QString(exit_signal);
225 destroyChannel();
226 }
227 break;
228 default: {
229 debugSsh2Error(ssh2_method_result);
230 destroyChannel();
231 }
232 }
233 return error_code;
234 }
235
destroyChannel()236 void Ssh2Channel::destroyChannel()
237 {
238 if (ssh2_channel_ == nullptr)
239 return;
240 setOpenMode(QIODevice::NotOpen);
241 if (ssh2_channel_state_ != ChannelStates::FailedToOpen)
242 setSsh2ChannelState(ChannelStates::Closed);
243 libssh2_channel_free(ssh2_channel_);
244 ssh2_channel_ = nullptr;
245 }
246
checkChannelData(const Ssh2Channel::ChannelStream & stream_id)247 void Ssh2Channel::checkChannelData(const Ssh2Channel::ChannelStream& stream_id)
248 {
249 switch (stream_id) {
250 case Out:
251 setCurrentReadChannel(0);
252 break;
253 case Err:
254 setCurrentReadChannel(1);
255 break;
256 }
257 const QByteArray data = readAll();
258 if (data.size())
259 emit newChannelData(data, stream_id);
260 }
261
exitSignal() const262 QString Ssh2Channel::exitSignal() const
263 {
264 return exit_signal_;
265 }
266
setLastError(const std::error_code & error_code)267 std::error_code Ssh2Channel::setLastError(const std::error_code& error_code)
268 {
269 if (last_error_ != error_code && error_code != Ssh2Error::TryAgain) {
270 last_error_ = error_code;
271 emit ssh2Error(last_error_);
272 }
273 return error_code;
274 }
275