Vidalia  0.2.21
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. */
52 bool
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 */
68 void
69 ControlSocket::connectToHost(const QHostAddress &address, quint16 port)
70 {
71  _tcpSocket->connectToHost(address, port);
72 }
73 
74 /** Disconnects from host */
75 void
77 {
78  _tcpSocket->disconnectFromHost();
79 }
80 
81 /** Connects to a unix socket file */
82 void
83 ControlSocket::connectToServer(const QString &name)
84 {
85  _localSocket->connectToServer(name);
86 }
87 
88 /** Disconnects from the socket */
89 void
91 {
92  _localSocket->disconnectFromServer();
93 }
94 
95 /** Interface to QTcpSocket::canReadLine */
96 bool
98 {
99  return _socket->canReadLine();
100 }
101 
102 /** Returns the string description of <b>error</b>. */
103 QString
104 ControlSocket::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. */
138 void
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  */
161 bool
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) {
178  case ControlMethod::Port:
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  */
202 bool
203 ControlSocket::readReply(ControlReply &reply, QString *errmsg)
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. */
252 bool
253 ControlSocket::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. */
273 bool
274 ControlSocket::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 }