Vidalia 0.3.1
ControlSocket.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If
4** you did not receive the LICENSE file with this file, you may obtain it
5** from the Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11/*
12** \file ControlSocket.cpp
13** \brief Socket used to connect to Tor's control interface
14*/
15
16#include "ControlSocket.h"
17#include "SendCommandEvent.h"
18#include "tcglobal.h"
19
20#include "stringutil.h"
21
22/** Timeout reads in 250ms. We can set this to a short value because if there
23* isn't any data to read, we want to return anyway. */
24#define READ_TIMEOUT 250
25
26
27/** Default constructor. */
29{
30 _tcpSocket = new QTcpSocket();
31 _localSocket = new QLocalSocket();
32 _method = method;
33 switch(_method) {
36 break;
37
40 break;
41 }
42
43 QObject::connect(_socket, SIGNAL(readyRead()), this, SIGNAL(readyRead()));
44 QObject::connect(_socket, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
45 QObject::connect(_socket, SIGNAL(connected()), this, SIGNAL(connected()));
46 QObject::connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)),
47 this, SIGNAL(error(QAbstractSocket::SocketError)));
48}
49
50/** Returns true if the control socket is connected and ready to send or
51 * receive. */
52bool
54{
55 switch(_method) {
57 return (_tcpSocket->isValid() && _tcpSocket->state() == QAbstractSocket::ConnectedState);
58 break;
59
60 default:
62 return (_localSocket->isValid() && _localSocket->state() == QLocalSocket::ConnectedState);
63 break;
64 }
65}
66
67/** Connects to address:port */
68void
69ControlSocket::connectToHost(const QHostAddress &address, quint16 port)
70{
71 _tcpSocket->connectToHost(address, port);
72}
73
74/** Disconnects from host */
75void
77{
78 _tcpSocket->disconnectFromHost();
79}
80
81/** Connects to a unix socket file */
82void
84{
85 _localSocket->connectToServer(name);
86}
87
88/** Disconnects from the socket */
89void
91{
92 _localSocket->disconnectFromServer();
93}
94
95/** Interface to QTcpSocket::canReadLine */
96bool
98{
99 return _socket->canReadLine();
100}
101
102/** Returns the string description of <b>error</b>. */
103QString
104ControlSocket::toString(const QAbstractSocket::SocketError error)
105{
106 QString str;
107 switch (error) {
108 case QAbstractSocket::ConnectionRefusedError:
109 str = "Connection refused by peer."; break;
110 case QAbstractSocket::RemoteHostClosedError:
111 str = "Remote host closed the connection."; break;
112 case QAbstractSocket::HostNotFoundError:
113 str = "Host address not found."; break;
114 case QAbstractSocket::SocketAccessError:
115 str = "Insufficient access privileges."; break;
116 case QAbstractSocket::SocketResourceError:
117 str = "Insufficient resources."; break;
118 case QAbstractSocket::SocketTimeoutError:
119 str = "Socket operation timed out."; break;
120 case QAbstractSocket::DatagramTooLargeError:
121 str = "Datagram size exceeded the operating system limit."; break;
122 case QAbstractSocket::NetworkError:
123 str = "Network error occurred."; break;
124 case QAbstractSocket::AddressInUseError:
125 str = "Specified address already in use."; break;
126 case QAbstractSocket::SocketAddressNotAvailableError:
127 str = "Specified address does not belong to the host."; break;
128 case QAbstractSocket::UnsupportedSocketOperationError:
129 str = "The requested operation is not supported."; break;
130 default:
131 str = "An unidentified error occurred."; break;
132 }
133 return str;
134}
135
136/** Processes custom events sent to this object (e.g. SendCommandEvents) from
137 * other threads. */
138void
140{
141 if (event->type() == QEvent::User) {
142 SendCommandEvent *sce = dynamic_cast<SendCommandEvent *>(event);
143 if (! sce)
144 return;
145
146 QString errmsg;
147 bool result = sendCommand(sce->command(), &errmsg);
148 if (sce->waiter())
149 sce->waiter()->setResult(result, errmsg);
150 sce->accept();
151 }
152}
153
154/** Send a control command to Tor on the control socket, conforming to Tor's
155 * Control Protocol V1:
156 *
157 * Command = Keyword Arguments CRLF / "+" Keyword Arguments CRLF Data
158 * Keyword = 1*ALPHA
159 * Arguments = *(SP / VCHAR)
160 */
161bool
163{
164 if (!isConnected()) {
165 return err(errmsg, tr("Control socket is not connected."));
166 }
167
168 /* Format the control command */
169 QString strCmd = cmd.toString();
170 tc::debug("Control Command: %1").arg(strCmd.trimmed());
171
172 /* Attempt to send the command to Tor */
173 if (_socket->write(strCmd.toLocal8Bit()) != strCmd.length()) {
174 return err(errmsg, tr("Error sending control command. [%1]")
175 .arg(_socket->errorString()));
176 }
177 switch(_method) {
179 _tcpSocket->flush();
180 break;
181
183 _localSocket->flush();
184 break;
185 }
186 return true;
187}
188
189
190/** Read a complete reply from the control socket. Replies take the following
191 * form, based on Tor's Control Protocol v1:
192 *
193 * Reply = *(MidReplyLine / DataReplyLine) EndReplyLine
194 *
195 * MidReplyLine = "-" ReplyLine
196 * DataReplyLine = "+" ReplyLine Data
197 * EndReplyLine = SP ReplyLine
198 * ReplyLine = StatusCode [ SP ReplyText ] CRLF
199 * ReplyText = XXXX
200 * StatusCode = XXiX
201 */
202bool
204{
205 QChar c;
206 QString line;
207
208 if (!isConnected()) {
209 return false;
210 }
211
212 /* The implementation below is (loosely) based on the Java control library
213 * from Tor */
214 do {
215 /* Read a line of the response */
216 if (!readLine(line, errmsg)) {
217 return false;
218 }
219
220 if (line.length() < 4) {
221 return err(errmsg, tr("Invalid control reply. [%1]").arg(line));
222 }
223
224 /* Parse the status and message */
225 ReplyLine replyLine(line.mid(0, 3), line.mid(4));
226 c = line.at(3);
227
228 /* If the reply line contains data, then parse out the data up until the
229 * trailing CRLF "." CRLF */
230 if (c == QChar('+') &&
231 !line.startsWith("250+PROTOCOLINFO")) {
232 /* XXX The second condition above is a hack to deal with Tor
233 * 0.2.0.5-alpha that gives a malformed PROTOCOLINFO reply. This
234 * should be removed once that version of Tor is sufficiently dead. */
235 while (true) {
236 if (!readLine(line, errmsg)) {
237 return false;
238 }
239 if (line.trimmed() == ".") {
240 break;
241 }
242 replyLine.appendData(line);
243 }
244 }
245 reply.appendLine(replyLine);
246 } while (c != QChar(' '));
247 return true;
248}
249
250/** Reads line data, one chunk at a time, until a newline character is
251 * encountered. */
252bool
253ControlSocket::readLineData(QString &line, QString *errmsg)
254{
255 char buffer[1024]; /* Read in 1024 byte chunks at a time */
256 int bytesRecv = _socket->readLine(buffer, 1024);
257 while (bytesRecv != -1) {
258 line.append(QString::fromLocal8Bit(buffer, bytesRecv));
259 if (buffer[bytesRecv-1] == '\n') {
260 break;
261 }
262 bytesRecv = _socket->readLine(buffer, 1024);
263 }
264 if (bytesRecv == -1) {
265 return err(errmsg, _socket->errorString());
266 }
267 return true;
268}
269
270/** Reads a line of data from the socket and returns true if successful or
271 * false if an error occurred while waiting for a line of data to become
272 * available. */
273bool
274ControlSocket::readLine(QString &line, QString *errmsg)
275{
276 /* Make sure we have data to read before attempting anything. Note that this
277 * essentially makes our socket a blocking socket */
278 while (!_socket->canReadLine()) {
279 if (!isConnected()) {
280 return err(errmsg, tr("Socket disconnected while attempting "
281 "to read a line of data."));
282 }
283 _socket->waitForReadyRead(READ_TIMEOUT);
284 }
285 line.clear();
286 return readLineData(line, errmsg);
287}
#define READ_TIMEOUT
stop errmsg connect(const QHostAddress &address, quint16 port)
QString toString() const
void appendLine(ReplyLine line)
bool readLine(QString &line, QString *errmsg=0)
void customEvent(QEvent *event)
static QString toString(const QAbstractSocket::SocketError error)
bool readLineData(QString &line, QString *errmsg=0)
void disconnectFromHost()
void connected()
void connectToHost(const QHostAddress &address, quint16 port)
QIODevice * _socket
Definition: ControlSocket.h:78
bool readReply(ControlReply &reply, QString *errmsg=0)
void readyRead()
void disconnectFromServer()
QLocalSocket * _localSocket
Definition: ControlSocket.h:77
ControlMethod::Method _method
Definition: ControlSocket.h:79
void disconnected()
QTcpSocket * _tcpSocket
Definition: ControlSocket.h:76
bool sendCommand(ControlCommand cmd, QString *errmsg=0)
void error(QAbstractSocket::SocketError)
void connectToServer(const QString &name)
ControlSocket(ControlMethod::Method method=ControlMethod::Port)
void appendData(const QString &data)
Definition: ReplyLine.cpp:71
void setResult(bool success, const QString &errmsg=QString())
ControlCommand command()
SendWaiter * waiter()
DebugMessage arg(const QString &a)
Definition: tcglobal.h:48
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
DebugMessage debug(const QString &fmt)
Definition: tcglobal.cpp:24
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37