summaryrefslogtreecommitdiff
path: root/src/protocol/fqterm_socket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol/fqterm_socket.cpp')
-rw-r--r--src/protocol/fqterm_socket.cpp672
1 files changed, 672 insertions, 0 deletions
diff --git a/src/protocol/fqterm_socket.cpp b/src/protocol/fqterm_socket.cpp
new file mode 100644
index 0000000..131044a
--- /dev/null
+++ b/src/protocol/fqterm_socket.cpp
@@ -0,0 +1,672 @@
+/***************************************************************************
+ * 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 <stdlib.h>
+#include <ctype.h>
+
+#include <QByteArray>
+#include <QTcpSocket>
+#include <QProcess>
+
+#include "fqterm.h"
+#include "fqterm_socket.h"
+#include "fqterm_trace.h"
+
+#if !defined(Q_OS_BSD4) && !defined(_OS_FREEBSD_) \
+ && !defined(Q_OS_MACX) && !defined(Q_OS_DARWIN)
+#include <malloc.h>
+#endif
+
+namespace FQTerm {
+
+/* hack from wget/http.c */
+#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))
+static void base64_encode(const char *s, char *store, int length);
+static char *basic_authentication_encode(const char *user, const char *passwd,
+ const char *header);
+static int parse_http_status_line(const char *line, const char
+ **reason_phrase_ptr);
+
+const char wingate_enter = 'J' &0x1f;
+
+//==============================================================================
+//FQTermSocketPrivate
+
+//==============================================================================
+FQTermSocketPrivate::FQTermSocketPrivate(QObject *parent)
+ : QObject(parent) {
+ m_socket = new QTcpSocket(this);
+
+ // proxy related
+ proxy_type = NOPROXY;
+ proxy_state = 0;
+ bauth = false;
+
+ FQ_VERIFY(connect(m_socket, SIGNAL(connected()), this, SLOT(socketConnected())));
+ FQ_VERIFY(connect(m_socket, SIGNAL(hostFound()), this, SIGNAL(hostFound())));
+ FQ_VERIFY(connect(m_socket, SIGNAL(disconnected()), this, SIGNAL(connectionClosed())));
+
+ FQ_VERIFY(connect(m_socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead())));
+ FQ_VERIFY(connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))));
+}
+
+FQTermSocketPrivate::~FQTermSocketPrivate() {
+ delete m_socket;
+}
+
+void FQTermSocketPrivate::setProxy(int nProxyType, bool bAuth, const QString &strProxyHost,
+ quint16 uProxyPort, const QString &strProxyUsr,
+ const QString &strProxyPwd) {
+ proxy_type = nProxyType;
+ if (proxy_type == NOPROXY) {
+ return ;
+ }
+ bauth = bAuth;
+ proxy_host = strProxyHost;
+ proxy_port = uProxyPort;
+ proxy_usr = strProxyUsr;
+ proxy_pwd = strProxyPwd;
+}
+
+void FQTermSocketPrivate::socketConnected() {
+ QByteArray strPort;
+ QByteArray command(9, 0);
+
+ char *proxyauth;
+ char *request;
+ int len = 0;
+
+ switch (proxy_type) {
+ case NOPROXY:
+ // no proxy
+ emit connected();
+ return ;
+ case WINGATE:
+ // Wingate Proxy
+ strPort.setNum(port);
+ writeBlock(host.toLocal8Bit());
+ writeBlock(" ");
+ writeBlock(strPort);
+ writeBlock(&wingate_enter); // CTRL+J
+ emit socketState(TSHOSTCONNECTED);
+ return ;
+ case SOCKS4:
+ // Socks4 Proxy
+ command.resize(9);
+ command[0] = '\x04';
+ command[1] = '\x01';
+ memcpy(command.data() + 2, &addr_host.sin_port, 2);
+ memcpy(command.data() + 4, &addr_host.sin_addr, 4);
+ writeBlock(command);
+ proxy_state = 1;
+ emit socketState(TSPROXYCONNECTED);
+ return ;
+ case SOCKS5:
+ // Socks5 Proxy
+ if (bauth) {
+ command.resize(4);
+ command[0] = '\x05';
+ command[1] = '\x02';
+ command[2] = '\x02';
+ command[3] = '\x00';
+ writeBlock(command);
+ }
+ else {
+ command.resize(3);
+ command[0] = '\x05';
+ command[1] = '\x01';
+ command[2] = '\x00';
+ writeBlock(command);
+ }
+ proxy_state = 1;
+ emit socketState(TSPROXYCONNECTED);
+ return ;
+ case HTTP:
+ proxyauth = NULL;
+ if (bauth) {
+ proxyauth = basic_authentication_encode(proxy_usr.toLocal8Bit(),
+ proxy_pwd.toLocal8Bit(), "Proxy-Authorization");
+ }
+
+ len = proxyauth != NULL ? strlen(proxyauth): 0;
+
+ request = new char[host.length() + len + 81];
+
+ sprintf(request, "CONNECT %s:%u HTTP/1.0\r\n""%s\r\n", host.toLatin1().constData(), port,
+ proxyauth != NULL ? proxyauth : "");
+
+
+ writeBlock(request);
+ delete [] request;
+ free(proxyauth);
+ proxy_state = 1;
+ emit socketState(TSPROXYCONNECTED);
+ return ;
+ default:
+ emit socketState(TSHOSTCONNECTED);
+ return ;
+ }
+}
+
+void FQTermSocketPrivate::socketReadyRead() {
+ if (proxy_type == NOPROXY || proxy_state == 0) {
+ emit readyRead();
+ }
+
+ int nbytes, nread;
+ QByteArray from_socket;
+ // get the data size
+ nbytes = bytesAvailable();
+ if (nbytes <= 0) {
+ return ;
+ }
+
+ //resize input buffer
+ from_socket.resize(0);
+
+ //read data from socket to from_socket
+ from_socket = readBlock(nbytes);
+ nread = from_socket.size();
+ //do some checks
+ if (nread <= 0) {
+ FQ_TRACE("socket", 0) << "Failed to read socket: " << nread << " <= 0";
+ return ;
+ }
+ if (nread > nbytes) {
+ FQ_TRACE("socket", 0) << "Overflow when reading socket: "
+ << " nread = " << nread
+ << " > nbytes = " << nbytes;
+ return ;
+ }
+
+ // Socks4 Proxy Reply
+ if (proxy_type == SOCKS4 && proxy_state == 1) {
+ if (nread != 8) {
+ emit socketState(TSPROXYERROR);
+ return ;
+ }
+
+ if (from_socket.at(1) == 90) {
+ emit connected();
+ } else {
+ emit socketState(TSPROXYFAIL);
+ }
+ proxy_state = 0;
+ return ;
+ }
+
+ // HTTP Proxy Reply
+ if (proxy_type == HTTP && proxy_state != 0) {
+ const char *error;
+ int statcode = parse_http_status_line(from_socket.data(), &error);
+ if (statcode >= 200 && statcode < 300) {
+ emit connected();
+ } else {
+ emit socketState(TSPROXYFAIL);
+ }
+ proxy_state = 0;
+ return ;
+ }
+
+ // Socks5 Proxy Reply
+ if (proxy_type == SOCKS5 && proxy_state != 0) {
+ socks5_reply(from_socket, nread);
+ return ;
+ }
+}
+
+void FQTermSocketPrivate::flush() {
+ m_socket->flush();
+}
+
+/////////////////////////////////////////////////////
+//originally version from qhostaddress.cpp, qt4.3.2, --dp
+static bool isIp4(const QString& address)
+{
+ QStringList ipv4 = address.split(QLatin1String("."));
+ if (ipv4.count() != 4)
+ return false;
+
+ for (int i = 0; i < 4; ++i) {
+ bool ok = false;
+ uint byteValue = ipv4.at(i).toUInt(&ok);
+ if (!ok || byteValue > 255)
+ return false;
+
+ }
+
+ return true;
+}
+/////////////////////////////////////////////////////////
+
+void FQTermSocketPrivate::connectToHost(const QString &hostname, quint16 portnumber) {
+ host = hostname;
+ port = portnumber;
+ addr_host.sin_port = htons(portnumber);
+
+ if (proxy_type == NOPROXY) {
+ m_socket->connectToHost(host, port);
+ } else {
+ //get ip addr from name
+ struct hostent *hostent;
+#if defined(_OS_WIN32_) || defined(Q_OS_WIN32)
+ WSADATA wsd;
+ if (WSAStartup(0x202, &wsd) != 0) {
+ emit socketState(TSEINIWINSOCK);
+ WSACleanup();
+ return ;
+ }
+#endif
+ // QHostAddress ha(host);
+ //ipv4 address check -- dp
+ if(isIp4(host)) {
+ hostent = gethostbyaddr( host.toLocal8Bit(), host.length(), AF_INET );
+ FQ_TRACE("ssh", 1) << "by addr";
+ } else {
+ hostent = gethostbyname(host.toLocal8Bit());
+ FQ_TRACE("ssh", 1) << "by name";
+ }
+
+ if (hostent == NULL) {
+ emit socketState(TSEGETHOSTBYNAME);
+ return ;
+ }
+ //now only ipv4 support
+ memcpy(&addr_host.sin_addr, hostent->h_addr, 4);
+ m_socket->connectToHost(proxy_host, proxy_port);
+ }
+
+}
+
+void FQTermSocketPrivate::close() {
+ m_socket->close();
+}
+
+QByteArray FQTermSocketPrivate::readBlock(unsigned long maxlen) {
+ return m_socket->read(maxlen);
+}
+
+long FQTermSocketPrivate::writeBlock(const QByteArray &data) {
+ return m_socket->write(data);
+}
+
+unsigned long FQTermSocketPrivate::bytesAvailable() {
+ return m_socket->bytesAvailable();
+}
+
+/*------------------------------------------------------------------------
+ * connect command for socks5
+ *------------------------------------------------------------------------
+ */
+void FQTermSocketPrivate::socks5_connect() {
+ QByteArray command(10, 0);
+ command[0] = '\x05';
+ command[1] = '\x01';
+ command[2] = '\x00';
+ command[3] = '\x01';
+ memcpy(command.data() + 4, &addr_host.sin_addr, 4);
+ memcpy(command.data() + 8, &addr_host.sin_port, 2);
+ writeBlock(command);
+}
+
+/*------------------------------------------------------------------------
+ * authentation command for socks5
+ *------------------------------------------------------------------------
+ */
+void FQTermSocketPrivate::socks5_auth() {
+ int ulen = proxy_usr.length();
+ int plen = proxy_pwd.length();
+ QByteArray command(3+ulen + plen, 0);
+ sprintf((char*)command.data(), " %s %s", proxy_usr.toLocal8Bit().data(),
+ proxy_pwd.toLocal8Bit().data());
+ command[0] = '\x01';
+ command[1] = ulen;
+ command[2+ulen] = plen;
+ writeBlock(command);
+}
+
+/*------------------------------------------------------------------------
+ * reply from socks5
+ *------------------------------------------------------------------------
+ */
+void FQTermSocketPrivate::socks5_reply(const QByteArray &from_socket, int nread) {
+ if (proxy_state == 1) { // Socks5 Proxy Replay 1
+ if (nread != 2) {
+ proxy_state = 0;
+ emit socketState(TSPROXYERROR);
+ return ;
+ }
+ switch (from_socket.at(1)) {
+ case '\x00':
+ // no authentation needed
+ socks5_connect();
+ proxy_state = 3;
+ emit socketState(TSCONNECTVIAPROXY);
+ return ;
+ case '\x02':
+ //need user/password
+ socks5_auth();
+ proxy_state = 2;
+ emit socketState(TSPROXYAUTH);
+ return ;
+ case '\xff':
+ proxy_state = 0;
+ emit socketState(TSPROXYFAIL);
+ return ;
+ default:
+ proxy_state = 0;
+ emit socketState(TSPROXYFAIL);
+ return ;
+ }
+ } else if (proxy_state == 2) { //Socks5 Proxy Replay 2
+ if (nread != 2) {
+ proxy_state = 0;
+ emit socketState(TSPROXYERROR);
+ return ;
+ }
+
+
+ if (from_socket.at(1) != '\00') {
+ proxy_state = 0;
+ emit socketState(TSPROXYFAIL);
+ return ;
+ }
+ socks5_connect();
+ proxy_state = 3;
+ emit socketState(TSCONNECTVIAPROXY);
+ return ;
+ } else if (proxy_state == 3) { //Socks5 Proxy Replay 3
+ proxy_state = 0;
+ if (nread != 10) {
+ emit socketState(TSPROXYERROR);
+ return ;
+ }
+ if (from_socket.at(1) != '\00') {
+ emit socketState(TSPROXYFAIL);
+ return ;
+ }
+ emit connected();
+ return ;
+ } else {
+ proxy_state = 0;
+ return ;
+ }
+}
+
+/* hack from wget/http.c */
+/* How many bytes it will take to store LEN bytes in base64. */
+#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))
+
+/* Encode the string S of length LENGTH to base64 format and place it
+ to STORE. STORE will be 0-terminated, and must point to a writable
+ buffer of at least 1+BASE64_LENGTH(length) bytes. */
+static void base64_encode(const char *s, char *store, int length) {
+ /* Conversion table. */
+ static char tbl[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+ 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+ };
+ int i;
+ unsigned char *p = (unsigned char*)store;
+
+ /* Transform the 3x8 bits to 4x6 bits, as required by base64. */
+ for (i = 0; i < length; i += 3) {
+ *p++ = tbl[s[0] >> 2];
+ *p++ = tbl[((s[0] &3) << 4) + (s[1] >> 4)];
+ *p++ = tbl[((s[1] &0xf) << 2) + (s[2] >> 6)];
+ *p++ = tbl[s[2] &0x3f];
+ s += 3;
+ }
+ /* Pad the result if necessary... */
+ if (i == length + 1) {
+ *(p - 1) = '=';
+ } else if (i == length + 2) {
+ *(p - 1) = *(p - 2) = '=';
+ }
+ /* ...and zero-terminate it. */
+ *p = '\0';
+}
+
+/* Create the authentication header contents for the `Basic' scheme.
+ This is done by encoding the string `USER:PASS' in base64 and
+ prepending `HEADER: Basic ' to it. */
+static char *basic_authentication_encode(const char *user, const char *passwd,
+ const char *header) {
+ char *t1, *t2, *res;
+ int len1 = strlen(user) + 1+strlen(passwd);
+ int len2 = BASE64_LENGTH(len1);
+
+ t1 = (char*)alloca(len1 + 1);
+ sprintf(t1, "%s:%s", user, passwd);
+ t2 = (char*)alloca(1+len2);
+ base64_encode(t1, t2, len1);
+ res = (char*)malloc(len2 + 11+strlen(header));
+ sprintf(res, "%s: Basic %s\r\n", header, t2);
+
+ return res;
+}
+
+/* Parse the HTTP status line, which is of format:
+
+ HTTP-Version SP Status-Code SP Reason-Phrase
+
+ The function returns the status-code, or -1 if the status line is
+ malformed. The pointer to reason-phrase is returned in RP. */
+static int parse_http_status_line(const char *line, const char
+ **reason_phrase_ptr) {
+ /* (the variables must not be named `major' and `minor', because
+ that breaks compilation with SunOS4 cc.) */
+ int mjr, mnr, statcode;
+ const char *p;
+
+ *reason_phrase_ptr = NULL;
+
+ /* The standard format of HTTP-Version is: `HTTP/X.Y', where X is
+ major version, and Y is minor version. */
+ if (strncmp(line, "HTTP/", 5) != 0) {
+ return -1;
+ }
+ line += 5;
+
+ /* Calculate major HTTP version. */
+ p = line;
+ for (mjr = 0; QChar(*line).isDigit(); line++) {
+ mjr = 10 * mjr + (*line - '0');
+ }
+ if (*line != '.' || p == line) {
+ return -1;
+ }
+ ++line;
+
+ /* Calculate minor HTTP version. */
+ p = line;
+ for (mnr = 0; QChar(*line).isDigit(); line++) {
+ mnr = 10 * mnr + (*line - '0');
+ }
+ if (*line != ' ' || p == line) {
+ return -1;
+ }
+ /* Wget will accept only 1.0 and higher HTTP-versions. The value of
+ minor version can be safely ignored. */
+ if (mjr < 1) {
+ return -1;
+ }
+ ++line;
+
+ /* Calculate status code. */
+ if (!(QChar(*line).isDigit() && QChar(line[1]).isDigit() && QChar(line[2])
+ .isDigit())) {
+ return -1;
+ }
+ statcode = 100 *(*line - '0') + 10 *(line[1] - '0') + (line[2] - '0');
+
+ /* Set up the reason phrase pointer. */
+ line += 3;
+ /* RFC2068 requires SPC here, but we allow the string to finish
+ here, in case no reason-phrase is present. */
+ if (*line != ' ') {
+ if (! *line) {
+ *reason_phrase_ptr = line;
+ } else {
+ return -1;
+ }
+ } else {
+ *reason_phrase_ptr = line + 1;
+ }
+
+ return statcode;
+}
+
+
+//==============================================================================
+//FQTermTelnetSocket
+//==============================================================================
+
+FQTermTelnetSocket::FQTermTelnetSocket()
+ : FQTermSocket() {
+ private_socket_ = new FQTermSocketPrivate();
+ FQ_VERIFY(connect(private_socket_, SIGNAL(connected()), this, SIGNAL(connected())));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(hostFound()), this, SIGNAL(hostFound())));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(connectionClosed()), this, SIGNAL(connectionClosed())));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(delayedCloseFinished()),
+ this, SIGNAL(delayedCloseFinished())));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(readyRead()), this, SIGNAL(readyRead())));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))));
+ FQ_VERIFY(connect(private_socket_, SIGNAL(socketState(int)), this, SIGNAL(socketState(int))));
+}
+
+FQTermTelnetSocket::~FQTermTelnetSocket() {
+ delete private_socket_;
+}
+
+void FQTermTelnetSocket::flush() {
+ private_socket_->flush();
+}
+
+void FQTermTelnetSocket::setProxy(int nProxyType, bool bAuth,
+ const QString &strProxyHost,
+ quint16 uProxyPort,
+ const QString &strProxyUsr,
+ const QString &strProxyPwd) {
+ private_socket_->setProxy(nProxyType, bAuth, strProxyHost, uProxyPort, strProxyUsr,
+ strProxyPwd);
+}
+
+void FQTermTelnetSocket::connectToHost(const QString &host, quint16 port) {
+ private_socket_->connectToHost(host, port);
+}
+
+void FQTermTelnetSocket::close() {
+ private_socket_->close();
+}
+
+QByteArray FQTermTelnetSocket::readBlock(unsigned long maxlen) {
+ return private_socket_->readBlock(maxlen);
+}
+
+long FQTermTelnetSocket::writeBlock(const QByteArray &data) {
+ return private_socket_->writeBlock(data);
+}
+
+unsigned long FQTermTelnetSocket::bytesAvailable() {
+ return private_socket_->bytesAvailable();
+}
+
+
+FQTermLocalSocket::FQTermLocalSocket( const QString& shell_bin ) : shell_bin_(shell_bin)
+{
+ shell_process_ = new QProcess();
+ shell_process_->setProcessChannelMode(QProcess::MergedChannels);
+ FQ_VERIFY(connect(shell_process_, SIGNAL(started()), this, SIGNAL(connected())));
+ FQ_VERIFY(connect(shell_process_, SIGNAL(stateChanged(QProcess::ProcessState)), this , SLOT(stateChanged(QProcess::ProcessState))));
+ FQ_VERIFY(connect(shell_process_, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))));
+ //FQ_VERIFY(connect(shell_process_, SIGNAL(delayedCloseFinished()), this, SIGNAL(delayedCloseFinished())));
+ FQ_VERIFY(connect(shell_process_, SIGNAL(readyRead()), this, SIGNAL(readyRead())));
+ //TODO: Error
+ //FQ_VERIFY(connect(shell_process_, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))));
+ //FQ_VERIFY(connect(shell_process_, SIGNAL(socketState(int)), this, SIGNAL(socketState(int))));
+}
+
+FQTermLocalSocket::~FQTermLocalSocket()
+{
+ delete shell_process_;
+}
+
+void FQTermLocalSocket::connectToHost( const QString &host, quint16 port )
+{
+ shell_process_->start("E:/yxl/test.exe"/*C:/Windows/System32/cmd.exeshell_bin*/, QIODevice::ReadWrite | QIODevice::Unbuffered);
+}
+
+void FQTermLocalSocket::close()
+{
+ shell_process_->close();
+}
+
+QByteArray FQTermLocalSocket::readBlock( unsigned long maxlen )
+{
+ return shell_process_->read(maxlen);
+}
+
+long FQTermLocalSocket::writeBlock( const QByteArray &data )
+{
+ int count = shell_process_->write(data);
+
+ //char c;
+// shell_process_->getChar(&c);
+// shell_process_->ungetChar(c);
+ if (bytesAvailable())
+ {
+ emit readyRead();
+ }
+ return count;
+}
+
+unsigned long FQTermLocalSocket::bytesAvailable()
+{
+ return shell_process_->bytesAvailable();
+}
+
+void FQTermLocalSocket::finished( int exitCode, QProcess::ExitStatus exitStatus )
+{
+ emit connectionClosed();
+}
+void FQTermLocalSocket::stateChanged(QProcess::ProcessState newState)
+{
+ switch(newState)
+ {
+ case QProcess::NotRunning:
+ break;
+ case QProcess::Starting:
+ emit hostFound();
+ shell_process_->write("dir\n");
+ break;
+ case QProcess::Running:
+ break;
+
+ }
+}
+
+
+} // namespace FQTerm
+
+#include "fqterm_socket.moc"