diff options
Diffstat (limited to 'src/protocol/fqterm_socket.cpp')
-rw-r--r-- | src/protocol/fqterm_socket.cpp | 672 |
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" |