summaryrefslogtreecommitdiff
path: root/src/protocol/internal/fqterm_ssh_channel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol/internal/fqterm_ssh_channel.cpp')
-rw-r--r--src/protocol/internal/fqterm_ssh_channel.cpp439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/protocol/internal/fqterm_ssh_channel.cpp b/src/protocol/internal/fqterm_ssh_channel.cpp
new file mode 100644
index 0000000..38a755a
--- /dev/null
+++ b/src/protocol/internal/fqterm_ssh_channel.cpp
@@ -0,0 +1,439 @@
+/***************************************************************************
+ * fqterm, a terminal emulator for both BBS and *nix. *
+ * Copyright (C) 2008 fqterm development group. *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. *
+ ***************************************************************************/
+
+#include "fqterm_ssh_channel.h"
+#include "fqterm_ssh_const.h"
+#include "fqterm_ssh_packet.h"
+#include "fqterm_trace.h"
+#include <QString>
+namespace FQTerm {
+
+//==============================================================================
+//FQTermSSH1Channel
+//==============================================================================
+
+FQTermSSH1Channel::FQTermSSH1Channel()
+ : FQTermSSHChannel() {
+ service_state_ = FQTermSSH1Channel::BEGIN_SERVICE;
+}
+
+void FQTermSSH1Channel::initChannel(FQTermSSHPacketReceiver *packet,
+ FQTermSSHPacketSender *output,
+ int col, int row, const QString& termtype) {
+ packet_receiver_ = packet;
+ packet_sender_ = output;
+ packet_receiver_->disconnect(this);
+ FQ_VERIFY(connect(packet_receiver_, SIGNAL(packetAvaliable(int)), this, SLOT(handlePacket(int))));
+ packet_sender_->startPacket(SSH1_CMSG_REQUEST_PTY);
+ // pty request is of no use in BBS, but we do this
+ packet_sender_->putString(termtype.toLatin1());
+ packet_sender_->putInt(row); // FIXME: hardcoded term size.
+ packet_sender_->putInt(col);
+ packet_sender_->putInt(0);
+ packet_sender_->putInt(0);
+ packet_sender_->putByte(0);
+ packet_sender_->write();
+ service_state_ = FQTermSSH1Channel::REQPTY_SENT;
+
+ is_closed_ = false;
+}
+
+void FQTermSSH1Channel::changeTermSize(int col, int row) {
+ packet_sender_->startPacket(SSH1_CMSG_WINDOW_SIZE);
+ packet_sender_->putInt(row);
+ packet_sender_->putInt(col);
+ packet_sender_->putInt(0);
+ packet_sender_->putInt(0);
+ packet_sender_->write();
+}
+
+void FQTermSSH1Channel::sendData(const char *data, int len) {
+ packet_sender_->startPacket(SSH1_CMSG_STDIN_DATA);
+ packet_sender_->putInt(len);
+ packet_sender_->putRawData(data, len);
+ packet_sender_->write();
+}
+
+void FQTermSSH1Channel::closeConnection(const char *reason) {
+ packet_sender_->startPacket(SSH1_MSG_DISCONNECT);
+ packet_sender_->putString(reason);
+ packet_sender_->write();
+ is_closed_ = true;
+}
+
+void FQTermSSH1Channel::handlePacket(int type) {
+ switch (service_state_) {
+ case BEGIN_SERVICE:
+ FQ_TRACE("sshchannel", 0) << "Channel: We should not be here";
+ break;
+ case REQPTY_SENT:
+ if (type != SSH1_SMSG_SUCCESS) {
+ emit channelError(tr("Server refused pty allocation!"));
+ }
+ packet_sender_->startPacket(SSH1_CMSG_EXEC_SHELL);
+ packet_sender_->write();
+ emit channelOK();
+ service_state_ = FQTermSSH1Channel::SERVICE_OK;
+ //emit msg to tell window we could process input.
+ break;
+ case SERVICE_OK:
+ switch (type) {
+ case SSH1_SMSG_STDOUT_DATA:
+ case SSH1_SMSG_STDERR_DATA:
+ {
+ const char *data = (const char *)packet_receiver_->buffer_->data() + 4;
+ int len = packet_receiver_->packetDataLen() - 4;
+ emit channelReadyRead(data, len);
+ }
+ break;
+ case SSH1_SMSG_X11_OPEN:
+ case SSH1_SMSG_AGENT_OPEN:
+ case SSH1_MSG_PORT_OPEN:
+ {
+ int i = packet_receiver_->getInt();
+ packet_sender_->startPacket(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ packet_sender_->putInt(i);
+ packet_sender_->write();
+ }
+ break;
+ case SSH1_SMSG_EXIT_STATUS:
+ packet_sender_->startPacket(SSH1_CMSG_EXIT_CONFIRMATION);
+ packet_sender_->write();
+ closeConnection("end");
+ is_closed_ = true;
+ break;
+ case SSH1_SMSG_SUCCESS:
+ case SSH1_SMSG_FAILURE:
+ break;
+ default:
+ FQ_TRACE("sshchannel", 0) << "Unimplemented message: "
+ << service_state_;
+ }
+ }
+}
+
+
+//==============================================================================
+//FQTermSSH2Channel
+//==============================================================================
+u_int32_t FQTermSSH2Channel::generateChannelID() {
+ static u_int32_t id = 0;
+ return id++;
+}
+
+FQTermSSH2Channel::FQTermSSH2Channel()
+ : FQTermSSHChannel(),
+ col_(80),
+ row_(24),
+ termtype_("vt100") {
+ channel_id_ = generateChannelID();
+ server_channel_id_ = 0xCCCCCCCC;
+ local_window_size_ = 0;
+ channel_state_ = FQTermSSH2Channel::BEGIN_CHANNEL;
+}
+
+void FQTermSSH2Channel::initChannel(FQTermSSHPacketReceiver *packet,
+ FQTermSSHPacketSender *output,
+ int col, int row, const QString& termtype) {
+ FQ_FUNC_TRACE("ssh2channel", 5);
+ col_ = col;
+ row_ = row;
+ termtype_ = termtype;
+ packet_receiver_ = packet;
+ packet_sender_ = output;
+ packet_receiver_->disconnect(this);
+ FQ_VERIFY(connect(packet_receiver_, SIGNAL(packetAvaliable(int)), this, SLOT(handlePacket(int))));
+
+ // byte SSH_MSG_CHANNEL_OPEN
+ // string channel type in US-ASCII only
+ // uint32 sender channel
+ // uint32 initial window size
+ // uint32 maximum packet size
+ // .... channel type specific data follows
+
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_OPEN);
+ packet_sender_->putString("session");
+ packet_sender_->putInt(channel_id_);
+ packet_sender_->putInt(MAX_LOCAL_WINDOW_SIZE); // TODO: what's the best window size?
+ packet_sender_->putInt(MAX_LOCAL_PACKET_SIZE); // TODO: what's the best maximum packet size?
+ packet_sender_->write();
+
+ server_window_size_ = 0;
+ server_max_packet_size_ = 0;
+ local_window_size_ = MAX_LOCAL_WINDOW_SIZE;
+
+ is_closed_ = false;
+}
+
+void FQTermSSH2Channel::changeTermSize(int col, int row) {
+ // byte SSH_MSG_CHANNEL_REQUEST
+ // uint32 recipient channel
+ // string "window-change"
+ // boolean FALSE
+ // uint32 terminal width, columns
+ // uint32 terminal height, rows
+ // uint32 terminal width, pixels
+ // uint32 terminal height, pixels
+
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_REQUEST);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->putString("window-change");
+ packet_sender_->putByte(false);
+ packet_sender_->putInt(col);
+ packet_sender_->putInt(row);
+ packet_sender_->putInt(640); // FIXME: hard-coded screen pixels.
+ packet_sender_->putInt(480);
+ packet_sender_->write();
+}
+
+void FQTermSSH2Channel::sendData(const char *data, int len) {
+ if (len > (int)server_window_size_ || len > (int)server_max_packet_size_) {
+ FQ_TRACE("ssh2channel", 3) << "Data length is greater than server's capacity: "
+ << "data length: " << len << ", "
+ << "server window: " << server_window_size_ << ", "
+ << "server packet max size: " << server_max_packet_size_;
+ return;
+ }
+
+ // byte SSH_MSG_CHANNEL_DATA
+ // uint32 recipient channel
+ // string data
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_DATA);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->putString(data, len);
+ packet_sender_->write();
+
+ server_window_size_ -= len;;
+
+ FQ_TRACE("ssh2channel", 5) << len
+ << " bytes data sent, server window size left: "
+ << server_window_size_;;
+}
+
+void FQTermSSH2Channel::closeConnection(const char *reason) {
+ packet_sender_->startPacket(SSH1_MSG_DISCONNECT);
+ packet_sender_->putString(reason);
+ packet_sender_->write();
+ is_closed_ = true;
+}
+
+void FQTermSSH2Channel::requestPty() {
+ if (packet_receiver_->packetType() == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+ // TODO: Here the error reason in the packet is ignored.
+ emit channelError(tr("Server refuces to open a channel."));
+ return;
+ }
+
+ if (packet_receiver_->packetType() != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ emit channelError(tr("Server error when opening a channel."));
+ return;
+ }
+
+ FQ_TRACE("ssh2channel", 5) << "Channel open. Try to request a pty.";
+
+ // byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION
+ // uint32 recipient channel
+ // uint32 sender channel
+ // uint32 initial window size
+ // uint32 maximum packet size
+ // .... channel type specific data follows
+ packet_receiver_->consume(4);
+ server_channel_id_ = packet_receiver_->getInt();
+ server_window_size_ = packet_receiver_->getInt();
+ server_max_packet_size_ = packet_receiver_->getInt();
+
+ // byte SSH_MSG_CHANNEL_REQUEST
+ // uint32 recipient channel
+ // string "pty-req"
+ // boolean want_reply
+ // string TERM environment variable value (e.g., vt100)
+ // uint32 terminal width, characters (e.g., 80)
+ // uint32 terminal height, rows (e.g., 24)
+ // uint32 terminal width, pixels (e.g., 640)
+ // uint32 terminal height, pixels (e.g., 480)
+ // string encoded terminal modes
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_REQUEST);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->putString("pty-req");
+ packet_sender_->putByte(true);
+ packet_sender_->putString(termtype_.toLatin1()); // TODO: hardcoded term type.
+ packet_sender_->putInt(col_); // FIXME: hardcoded screen parameters.
+ packet_sender_->putInt(row_);
+ packet_sender_->putInt(640);
+ packet_sender_->putInt(480);
+ packet_sender_->putString(""); // TODO: no modes sent.
+ packet_sender_->write();
+}
+
+
+void FQTermSSH2Channel::requestShell() {
+ if (packet_receiver_->packetType() != SSH2_MSG_CHANNEL_SUCCESS) {
+ emit channelError(tr("Server refused pty allocation!"));
+ }
+ FQ_TRACE("ssh2channel", 5) << "Pty allocated. Now try to request a shell.";
+ // byte SSH_MSG_CHANNEL_REQUEST
+ // uint32 recipient channel
+ // string "shell"
+ // boolean want reply
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_REQUEST);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->putString("shell");
+ packet_sender_->putByte(true);
+ packet_sender_->write();
+}
+
+void FQTermSSH2Channel::checkLocalWindowSize() {
+ if (local_window_size_ < MAX_LOCAL_WINDOW_SIZE/2) {
+ // byte SSH_MSG_CHANNEL_WINDOW_ADJUST
+ // uint32 recipient channel
+ // uint32 bytes to add
+ int inc = MAX_LOCAL_WINDOW_SIZE/2;
+ local_window_size_ += inc;
+
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->putInt(inc);
+ packet_sender_->write();
+ }
+}
+
+void FQTermSSH2Channel::processChannelPacket() {
+ switch (packet_receiver_->packetType()) {
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ {
+ // byte SSH_MSG_CHANNEL_WINDOW_ADJUST
+ // uint32 recipient channel
+ // uint32 bytes to add
+ packet_receiver_->consume(4); // channel id is already checked.
+ int inc = packet_receiver_->getInt();
+ server_window_size_ += inc;
+ FQ_TRACE("ssh2channel", 5) << "Server window size increased from "
+ << server_window_size_ - inc
+ << " to " << server_window_size_;
+ }
+ break;
+ case SSH2_MSG_CHANNEL_DATA:
+ {
+ // byte SSH_MSG_CHANNEL_DATA
+ // uint32 recipient channel
+ // string data
+ packet_receiver_->consume(4);
+ int len = packet_receiver_->getInt();
+ const char *data = (const char *)packet_receiver_->buffer_->data();
+ local_window_size_ -= len;
+ checkLocalWindowSize();
+ emit channelReadyRead(data, len);
+ }
+ break;
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ {
+ // byte SSH_MSG_CHANNEL_DATA
+ // uint32 recipient channel
+ // string data
+ packet_receiver_->consume(4);
+ int len = packet_receiver_->getInt();
+ const char *data = (const char *)packet_receiver_->buffer_->data();
+ local_window_size_ -= len;
+ checkLocalWindowSize();
+ emit channelReadyRead(data, len);
+ }
+ break;
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ // byte SSH_MSG_CHANNEL_EOF
+ // uint32 recipient channel
+ // FIXME: this error would cause the connection closed, while only the channel need be closed in ssh2.
+ emit channelError(tr("Channel closed by the server."));
+ break;
+ case SSH2_MSG_CHANNEL_REQUEST:
+ // byte SSH_MSG_CHANNEL_REQUEST
+ // uint32 recipient channel
+ // string "xon-xoff"
+ // boolean FALSE
+ // boolean client can do
+
+ // TODO: just ignore this message currently.
+ break;
+ }
+}
+
+void FQTermSSH2Channel::handlePacket(int type) {
+ // first check the channel id.
+ u_int32_t channel_id = ntohu32(packet_receiver_->buffer_->data());
+ if (channel_id != channel_id_) {
+ return;
+ }
+
+ switch (channel_state_) {
+ case FQTermSSH2Channel::BEGIN_CHANNEL:
+ requestPty();
+ channel_state_ = FQTermSSH2Channel::REQUEST_PTY_SENT;
+ break;
+ case FQTermSSH2Channel::REQUEST_PTY_SENT:
+ requestShell();
+ channel_state_ = FQTermSSH2Channel::REQUEST_SHELL_SENT;
+ break;
+ case FQTermSSH2Channel::REQUEST_SHELL_SENT:
+ switch (type) {
+ case SSH2_MSG_CHANNEL_REQUEST:
+ {
+ FQ_TRACE("ssh2channel", 8) << "SSH2_MSG_CHANNEL_REQUEST isn't supported, just send back a packet with SSH2_MSG_CHANNEL_SUCCESS if reply is needed.";
+
+ packet_receiver_->consume(4);
+ u_int32_t len = ntohu32(packet_receiver_->buffer_->data());
+ packet_receiver_->consume(4 + len);
+ bool replyNeeded = packet_receiver_->getByte();
+
+ if (replyNeeded) {
+ packet_sender_->startPacket(SSH2_MSG_CHANNEL_SUCCESS);
+ packet_sender_->putInt(server_channel_id_);
+ packet_sender_->write();
+ }
+ }
+ break;
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ {
+ packet_receiver_->consume(4);
+ u_int32_t inc = packet_receiver_->getInt();
+ server_window_size_ += inc;
+ }
+ break;
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ emit channelOK();
+ channel_state_ = FQTermSSH2Channel::CHANNEL_OK;
+ //emit a msg to tell window we could process input.
+ break;
+ case SSH2_MSG_CHANNEL_FAILURE:
+ emit channelError(tr("Can't open a shell."));
+ break;
+ default:
+ emit channelError(tr("Unsupported packet."));
+ break;
+ }
+ break;
+ case FQTermSSH2Channel::CHANNEL_OK:
+ processChannelPacket();
+ break;
+ }
+}
+
+} // namespace FQTerm
+
+#include "fqterm_ssh_channel.moc"