ControlSocket.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If 
00004 **  you did not receive the LICENSE file with this file, you may obtain it
00005 **  from the Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to
00008 **  the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file ControlSocket.cpp
00013 ** \version $Id: ControlSocket.cpp 4054 2009-08-17 02:25:08Z edmanm $
00014 ** \brief Socket used to connect to Tor's control interface
00015 */
00016 
00017 #include "ControlSocket.h"
00018 #include "SendCommandEvent.h"
00019 #include "tcglobal.h"
00020 
00021 #include "stringutil.h"
00022 
00023 /** Timeout reads in 250ms. We can set this to a short value because if there
00024 * isn't any data to read, we want to return anyway. */
00025 #define READ_TIMEOUT  250
00026 
00027 
00028 /** Default constructor. */
00029 ControlSocket::ControlSocket()
00030 {
00031 }
00032 
00033 /** Returns true if the control socket is connected and ready to send or
00034  * receive. */
00035 bool
00036 ControlSocket::isConnected()
00037 {
00038   return (isValid() && state() == QAbstractSocket::ConnectedState);
00039 }
00040 
00041 /** Processes custom events sent to this object (e.g. SendCommandEvents) from
00042  * other threads. */
00043 void
00044 ControlSocket::customEvent(QEvent *event)
00045 {
00046   if (event->type() == QEvent::User) {
00047     SendCommandEvent *sce = dynamic_cast<SendCommandEvent *>(event);
00048     if (! sce)
00049       return;
00050 
00051     QString errmsg;
00052     bool result = sendCommand(sce->command(), &errmsg);
00053     if (sce->waiter())
00054       sce->waiter()->setResult(result, errmsg);
00055     sce->accept();
00056   }
00057 }
00058 
00059 /** Send a control command to Tor on the control socket, conforming to Tor's
00060  * Control Protocol V1:
00061  *
00062  *   Command = Keyword Arguments CRLF / "+" Keyword Arguments CRLF Data
00063  *   Keyword = 1*ALPHA
00064  *   Arguments = *(SP / VCHAR)
00065  */
00066 bool
00067 ControlSocket::sendCommand(ControlCommand cmd, QString *errmsg)
00068 {  
00069   if (!isConnected()) {
00070     return err(errmsg, tr("Control socket is not connected."));
00071   }
00072   
00073   /* Format the control command */
00074   QString strCmd = cmd.toString();
00075   tc::debug("Control Command: %1").arg(strCmd.trimmed());
00076 
00077   /* Attempt to send the command to Tor */
00078   if (write(strCmd.toAscii()) != strCmd.length()) {
00079     return err(errmsg, tr("Error sending control command. [%1]")
00080                                             .arg(errorString()));
00081   }
00082   flush();
00083   return true;
00084 }
00085 
00086 /** Reads line data, one chunk at a time, until a newline character is
00087  * encountered. */
00088 bool
00089 ControlSocket::readLineData(QString &line, QString *errmsg)
00090 {
00091   char buffer[1024];  /* Read in 1024 byte chunks at a time */
00092   int bytesRecv = QAbstractSocket::readLine(buffer, 1024);
00093   while (bytesRecv != -1) {
00094     line.append(buffer);
00095     if (buffer[bytesRecv-1] == '\n') {
00096       break;
00097     }
00098     bytesRecv = QAbstractSocket::readLine(buffer, 1024);
00099   }
00100   if (bytesRecv == -1) {
00101     return err(errmsg, errorString());
00102   }
00103   return true;
00104 }
00105 
00106 /** Reads a line of data from the socket and returns true if successful or
00107  * false if an error occurred while waiting for a line of data to become
00108  * available. */
00109 bool
00110 ControlSocket::readLine(QString &line, QString *errmsg)
00111 {
00112   /* Make sure we have data to read before attempting anything. Note that this
00113    * essentially makes our socket a blocking socket */
00114   while (!canReadLine()) {
00115     if (!isConnected()) {
00116       return err(errmsg, tr("Socket disconnected while attempting "
00117                             "to read a line of data."));
00118     }
00119     waitForReadyRead(READ_TIMEOUT);
00120   }
00121   line.clear();
00122   return readLineData(line, errmsg);
00123 }
00124 
00125 /** Read a complete reply from the control socket. Replies take the following
00126  * form, based on Tor's Control Protocol v1:
00127  *
00128  *    Reply = *(MidReplyLine / DataReplyLine) EndReplyLine
00129  *
00130  *    MidReplyLine = "-" ReplyLine
00131  *    DataReplyLine = "+" ReplyLine Data
00132  *    EndReplyLine = SP ReplyLine
00133  *    ReplyLine = StatusCode [ SP ReplyText ]  CRLF
00134  *    ReplyText = XXXX
00135  *    StatusCode = XXiX
00136  */
00137 bool
00138 ControlSocket::readReply(ControlReply &reply, QString *errmsg)
00139 {
00140   QChar c;
00141   QString line;
00142 
00143   if (!isConnected()) {
00144     return false;
00145   }
00146 
00147   /* The implementation below is (loosely) based on the Java control library
00148    * from Tor */
00149   do {
00150     /* Read a line of the response */
00151     if (!readLine(line, errmsg)) {
00152       return false;
00153     }
00154     
00155     if (line.length() < 4) {
00156       return err(errmsg, tr("Invalid control reply. [%1]").arg(line));
00157     }
00158 
00159     /* Parse the status and message */
00160     ReplyLine replyLine(line.mid(0, 3), line.mid(4));
00161     c = line.at(3);
00162 
00163     /* If the reply line contains data, then parse out the data up until the
00164      * trailing CRLF "." CRLF */
00165     if (c == QChar('+') &&
00166         !line.startsWith("250+PROTOCOLINFO")) {
00167         /* XXX The second condition above is a hack to deal with Tor
00168          * 0.2.0.5-alpha that gives a malformed PROTOCOLINFO reply. This
00169          * should be removed once that version of Tor is sufficiently dead. */
00170       while (true) {
00171         if (!readLine(line, errmsg)) {
00172           return false;
00173         }
00174         if (line.trimmed() == ".") {
00175           break;
00176         }
00177         replyLine.appendData(line);
00178       }
00179     }
00180     reply.appendLine(replyLine);
00181   } while (c != QChar(' '));
00182   return true;
00183 }
00184 
00185 /** Returns the string description of <b>error</b>. */
00186 QString
00187 ControlSocket::toString(const QAbstractSocket::SocketError error)
00188 {
00189   QString str;
00190   switch (error) {
00191     case ConnectionRefusedError:
00192       str = "Connection refused by peer."; break;
00193     case RemoteHostClosedError:
00194       str = "Remote host closed the connection."; break;
00195     case HostNotFoundError:
00196       str = "Host address not found."; break;
00197     case SocketAccessError:
00198       str = "Insufficient access privileges."; break;
00199     case SocketResourceError:
00200       str = "Insufficient resources."; break;
00201     case SocketTimeoutError:
00202       str = "Socket operation timed out."; break;
00203     case DatagramTooLargeError:
00204       str = "Datagram size exceeded the operating system limit."; break;
00205     case NetworkError:
00206       str = "Network error occurred."; break;
00207     case AddressInUseError:
00208       str = "Specified address already in use."; break;
00209     case SocketAddressNotAvailableError:
00210       str = "Specified address does not belong to the host."; break;
00211     case UnsupportedSocketOperationError:
00212       str = "The requested operation is not supported."; break;
00213     default:
00214       str = "An unidentified error occurred."; break;
00215   }
00216   return str;
00217 }
00218 
Generated on Mon Aug 30 23:09:48 2010 for Vidalia by  doxygen 1.6.3