controlsocket.cpp

Go to the documentation of this file.
00001 /****************************************************************
00002  *  Vidalia is distributed under the following license:
00003  *
00004  *  Copyright (C) 2006,  Matt Edman, Justin Hipple
00005  *
00006  *  This program is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU General Public License
00008  *  as published by the Free Software Foundation; either version 2
00009  *  of the License, or (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, 
00019  *  Boston, MA  02110-1301, USA.
00020  ****************************************************************/
00021 
00022 /** 
00023  * \file controlsocket.cpp
00024  * \version $Id: $
00025  * \brief Socket used to connect to Tor's control interface
00026  */
00027 
00028 #include <QHostAddress>
00029 #include <util/string.h>
00030 
00031 #include "controlsocket.h"
00032 
00033 /** Give up after waiting five seconds for the control socket to connect to
00034 * Tor. This timeout used to be shorter (three seconds), but some Agnitum
00035 * OutPost users yelled at us wanting a longer timeout, for some reason. */
00036 #define CONN_TIMEOUT  5000
00037 
00038 /** Timeout reads in 250ms. We can set this to a short value because if there
00039 * isn't any data to read, we want to return anyway. */
00040 #define READ_TIMEOUT  250
00041 
00042 
00043 /** Default constructor. */
00044 ControlSocket::ControlSocket()
00045 {
00046 }
00047 
00048 /** Connects to Tor's control socket on the specified host and port. If the
00049  * connection is successful, true is returned. If the connection fails, then
00050  * this function returns false and sets <b>errmsg</b> appropriately, if not
00051  * null. */
00052 bool
00053 ControlSocket::connect(QHostAddress addr, quint16 port, QString *errmsg)
00054 {
00055   ProtocolVersion version;
00056 
00057   /* Connect the control socket. */
00058   connectToHost(addr, port);
00059   if (!waitForConnected(CONN_TIMEOUT)) {
00060     return err(errmsg, tr("Error connecting to %1:%2 [%3]")
00061                                             .arg(addr.toString())
00062                                             .arg(port)
00063                                             .arg(errorString()));
00064   }
00065   
00066   /* Verify that Tor is speaking a protocol version we understand. */
00067   blockSignals(true);
00068   version = protocolVersion();
00069   blockSignals(false);
00070   if (version != Version1) {
00071     disconnect();
00072     return err(errmsg, tr("Vidalia only supports Version 1 of Tor's Control Protocol "
00073                           "(Version %1 detected).\n"
00074                           "Upgrade to a newer version of Tor.").arg(version));
00075   }
00076 
00077   /* Ok, now we're really connected */
00078   return true;
00079 }
00080 
00081 /** Disconnects from Tor's control socket */
00082 bool
00083 ControlSocket::disconnect(QString *errmsg)
00084 {
00085   disconnectFromHost();
00086   if (isConnected()) {
00087     if (!waitForDisconnected(CONN_TIMEOUT)) {
00088       return err(errmsg, tr("Error disconnecting socket. [%1]")
00089                                             .arg(errorString()));
00090     }
00091   }
00092   return true;
00093 }
00094 
00095 /** Returns true if the control socket is connected and ready to send or
00096  * receive. */
00097 bool
00098 ControlSocket::isConnected()
00099 {
00100   return (isValid() && state() == QAbstractSocket::ConnectedState);
00101 }
00102 
00103 /** Determines which version of Tor's control protocol is being spoken. */
00104 ControlSocket::ProtocolVersion
00105 ControlSocket::protocolVersion()
00106 {
00107   QByteArray versionData;
00108 
00109   /* Send a special little bit of data and wait for the response */
00110   if (!write("\0\0\r\n", 4)) {
00111     return VersionUnknown;
00112   }
00113   while (bytesAvailable() < 4) {
00114     if (!waitForReadyRead(-1)) {
00115       return VersionUnknown;
00116     }
00117   }
00118 
00119   /* If the response starts with a "\0\0", that means it is V0 of the control
00120    * protocol. Otherwise, it is V1. */
00121   versionData = readAll();
00122   return (!qstrlen(versionData.data()) ? Version0 : Version1);
00123 }
00124 
00125 /** Send a control command to Tor on the control socket, conforming to Tor's
00126  * Control Protocol V1:
00127  *
00128  *   Command = Keyword Arguments CRLF / "+" Keyword Arguments CRLF Data
00129  *   Keyword = 1*ALPHA
00130  *   Arguments = *(SP / VCHAR)
00131  */
00132 bool
00133 ControlSocket::sendCommand(ControlCommand cmd, QString *errmsg)
00134 {
00135   if (!isConnected()) {
00136     return err(errmsg, tr("Control socket is not connected."));
00137   }
00138   
00139   /* Format the control command */
00140   QString strCmd = cmd.toString();
00141 
00142   /* Attempt to send the command to Tor */
00143   if (write(strCmd.toAscii()) != strCmd.length()) {
00144     return err(errmsg, tr("Error sending control command. [%1]")
00145                                             .arg(errorString()));
00146   }
00147   flush();
00148   return true;
00149 }
00150 
00151 /** Reads line data, one chunk at a time, until a newline character is
00152  * encountered. */
00153 bool
00154 ControlSocket::readLineData(QString &line, QString *errmsg)
00155 {
00156   char buffer[1024];  /* Read in 1024 byte chunks at a time */
00157   int bytesRecv = QAbstractSocket::readLine(buffer, 1024);
00158   while (bytesRecv != -1) {
00159     line.append(buffer);
00160     if (buffer[bytesRecv-1] == '\n') {
00161       break;
00162     }
00163     bytesRecv = QAbstractSocket::readLine(buffer, 1024);
00164   }
00165   if (bytesRecv == -1) {
00166     return err(errmsg, errorString());
00167   }
00168   return true;
00169 }
00170 
00171 /** Reads a line of data from the socket and returns true if successful or
00172  * false if an error occurred while waiting for a line of data to become
00173  * available. */
00174 bool
00175 ControlSocket::readLine(QString &line, QString *errmsg)
00176 {
00177   /* Make sure we have data to read before attempting anything. Note that this
00178    * essentially makes our socket a blocking socket */
00179   while (!canReadLine()) {
00180     if (!isConnected()) {
00181       return err(errmsg, tr("Socket disconnected while attempting "
00182                             "to read a line of data."));
00183     }
00184     waitForReadyRead(READ_TIMEOUT);
00185   }
00186   line.clear();
00187   return readLineData(line, errmsg);
00188 }
00189 
00190 /** Read a complete reply from the control socket. Replies take the following
00191  * form, based on Tor's Control Protocol v1:
00192  *
00193  *    Reply = *(MidReplyLine / DataReplyLine) EndReplyLine
00194  *
00195  *    MidReplyLine = "-" ReplyLine
00196  *    DataReplyLine = "+" ReplyLine Data
00197  *    EndReplyLine = SP ReplyLine
00198  *    ReplyLine = StatusCode [ SP ReplyText ]  CRLF
00199  *    ReplyText = XXXX
00200  *    StatusCode = XXiX
00201  */
00202 bool
00203 ControlSocket::readReply(ControlReply &reply, QString *errmsg)
00204 {
00205   QChar c;
00206   QString line;
00207 
00208   if (!isConnected()) {
00209     return false;
00210   }
00211 
00212   /* The implementation below is (loosely) based on the Java control library from Tor */
00213   do {
00214     /* Read a line of the response */
00215     if (!readLine(line, errmsg)) {
00216       return false;
00217     }
00218     
00219     if (line.length() < 4) {
00220       return err(errmsg, tr("Invalid control reply. [%1]").arg(line));
00221     }
00222 
00223     /* Parse the status and message */
00224     ReplyLine replyLine(line.mid(0, 3), line.mid(4));
00225     c = line.at(3);
00226 
00227     /* If the reply line contains data, then parse out the data up until the
00228      * trailing CRLF "." CRLF */
00229     if (c == QChar('+')) {
00230       while (true) {
00231         if (!readLine(line, errmsg)) {
00232           return false;
00233         }
00234         if (line.trimmed() == ".") {
00235           break;
00236         }
00237         replyLine.appendData(line);
00238       }
00239     }
00240     reply.appendLine(replyLine);
00241   } while (c != QChar(' '));
00242   return true;
00243 }
00244 

Generated on Mon Oct 23 20:08:15 2006 for Vidalia by  doxygen 1.5.0