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