Vidalia  0.3.1
TorEvents.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 TorEvents.cpp
13 ** \brief Parses and dispatches events from Tor
14 */
15 
16 
17 #include "TorEvents.h"
18 #include "ControlReply.h"
19 #include "ReplyLine.h"
20 #include "Circuit.h"
21 #include "Stream.h"
22 #include "BootstrapStatus.h"
23 
24 #include "stringutil.h"
25 
26 #include <QHostAddress>
27 #include <QMetaType>
28 
29 /** Format of expiry times in address map events. */
30 #define DATE_FMT "\"yyyy-MM-dd HH:mm:ss\""
31 
32 
33 /** Default constructor */
34 TorEvents::TorEvents(QObject *parent)
35  : QObject(parent)
36 {
37  qRegisterMetaType<tc::Severity>();
38  qRegisterMetaType<tc::SocksError>();
39  qRegisterMetaType<tc::TorVersionStatus>();
40 
41  qRegisterMetaType<BootstrapStatus>("BootstrapStatus");
42  qRegisterMetaType<Circuit>("Circuit");
43  qRegisterMetaType<Stream>("Stream");
44 
45  qRegisterMetaType<QHostAddress>("QHostAddress");
46  qRegisterMetaType<QDateTime>("QDateTime");
47 }
48 
49 /** Converts an event type to a string Tor understands */
50 QString
52 {
53  QString event;
54  switch (e) {
55  case Bandwidth: event = "BW"; break;
56  case LogDebug: event = "DEBUG"; break;
57  case LogInfo: event = "INFO"; break;
58  case LogNotice: event = "NOTICE"; break;
59  case LogWarn: event = "WARN"; break;
60  case LogError: event = "ERR"; break;
61  case CircuitStatus: event = "CIRC"; break;
62  case StreamStatus: event = "STREAM"; break;
63  case NewDescriptor: event = "NEWDESC"; break;
64  case AddressMap: event = "ADDRMAP"; break;
65  case GeneralStatus: event = "STATUS_GENERAL"; break;
66  case ClientStatus: event = "STATUS_CLIENT"; break;
67  case ServerStatus: event = "STATUS_SERVER"; break;
68  default: event = "UNKNOWN"; break;
69  }
70  return event;
71 }
72 
73 /** Converts an event in the string form sent by Tor to its enum value */
75 TorEvents::toTorEvent(const QString &event)
76 {
77  Event e;
78  if (event == "BW") {
79  e = Bandwidth;
80  } else if (event == "CIRC") {
81  e = CircuitStatus;
82  } else if (event == "STREAM") {
83  e = StreamStatus;
84  } else if (event == "DEBUG") {
85  e = LogDebug;
86  } else if (event == "INFO") {
87  e = LogInfo;
88  } else if (event == "NOTICE") {
89  e = LogNotice;
90  } else if (event == "WARN") {
91  e = LogWarn;
92  } else if (event == "ERR") {
93  e = LogError;
94  } else if (event == "NEWDESC") {
95  e = NewDescriptor;
96  } else if (event == "ADDRMAP") {
97  e = AddressMap;
98  } else if (event == "STATUS_GENERAL") {
99  e = GeneralStatus;
100  } else if (event == "STATUS_CLIENT") {
101  e = ClientStatus;
102  } else if (event == "STATUS_SERVER") {
103  e = ServerStatus;
104  } else {
105  e = Unknown;
106  }
107  return e;
108 }
109 
110 /** Parse the event type out of a message line and return the corresponding
111  * Event enum value */
114 {
115  QString msg = line.getMessage();
116  int i = msg.indexOf(" ");
117  return toTorEvent(msg.mid(0, i));
118 }
119 
120 /** Handles an event message from Tor. An event message can potentially have
121  * more than one line, so we will iterate through them all and dispatch the
122  * necessary events. */
123 void
125 {
126  foreach(ReplyLine line, reply.getLines()) {
127  switch (parseEventType(line)) {
128  case Bandwidth: handleBandwidthUpdate(line); break;
129  case CircuitStatus: handleCircuitStatus(line); break;
130  case StreamStatus: handleStreamStatus(line); break;
131  case NewDescriptor: handleNewDescriptor(line); break;
132  case AddressMap: handleAddressMap(line); break;
133 
134  case GeneralStatus:
135  handleStatusEvent(GeneralStatus, line); break;
136  case ClientStatus:
137  handleStatusEvent(ClientStatus, line); break;
138  case ServerStatus:
139  handleStatusEvent(ServerStatus, line); break;
140 
141  case LogDebug:
142  case LogInfo:
143  case LogNotice:
144  case LogWarn:
145  case LogError:
146  handleLogMessage(line); break;
147  default: break;
148  }
149  }
150 }
151 
152 /** Handle a bandwidth update event, to inform the controller of the bandwidth
153  * used in the last second. The format of this message is:
154  *
155  * "650" SP "BW" SP BytesRead SP BytesWritten
156  * BytesRead = 1*DIGIT
157  * BytesWritten = 1*DIGIT
158  */
159 void
161 {
162  QStringList msg = line.getMessage().split(" ");
163  if (msg.size() >= 3) {
164  quint64 bytesIn = (quint64)msg.at(1).toULongLong();
165  quint64 bytesOut = (quint64)msg.at(2).toULongLong();
166 
167  /* Post the event to each of the interested targets */
168  emit bandwidthUpdate(bytesIn, bytesOut);
169  }
170 }
171 
172 /** Handle a circuit status event. The format of this message is:
173  *
174  * "650" SP "CIRC" SP CircuitID SP CircStatus SP Path
175  * CircStatus =
176  * "LAUNCHED" / ; circuit ID assigned to new circuit
177  * "BUILT" / ; all hops finished, can now accept streams
178  * "EXTENDED" / ; one more hop has been completed
179  * "FAILED" / ; circuit closed (was not built)
180  * "CLOSED" ; circuit closed (was built)
181  * Path = ServerID *("," ServerID)
182  */
183 void
185 {
186  QString msg = line.getMessage().trimmed();
187  int i = msg.indexOf(" ") + 1;
188  if (i > 0) {
189  /* Post the event to each of the interested targets */
190  Circuit circ(msg.mid(i));
191  if (circ.isValid())
192  emit circuitStatusChanged(circ);
193  }
194 }
195 
196 /** Handle a stream status event. The format of this message is:
197  *
198  * "650" SP "STREAM" SP StreamID SP StreamStatus SP CircID SP Target SP
199  * StreamStatus =
200  * "NEW" / ; New request to connect
201  * "NEWRESOLVE" / ; New request to resolve an address
202  * "SENTCONNECT" / ; Sent a connect cell along a circuit
203  * "SENTRESOLVE" / ; Sent a resolve cell along a circuit
204  * "SUCCEEDED" / ; Received a reply; stream established
205  * "FAILED" / ; Stream failed and not retriable.
206  * "CLOSED" / ; Stream closed
207  * "DETACHED" ; Detached from circuit; still retriable.
208  * Target = Address ":" Port
209  *
210  * If the circuit ID is 0, then the stream is unattached.
211  */
212 void
214 {
215  QString msg = line.getMessage().trimmed();
216  int i = msg.indexOf(" ") + 1;
217  if (i > 0) {
218  Stream stream = Stream::fromString(msg.mid(i));
219  if (stream.isValid())
220  emit streamStatusChanged(stream);
221  }
222 }
223 
224 /** Handle a log message event. The format of this message is:
225  * The syntax is:
226  *
227  * "650" SP Severity SP ReplyText
228  * or
229  * "650+" Severity CRLF Data
230  * Severity = "DEBUG" / "INFO" / "NOTICE" / "WARN"/ "ERR"
231  */
232 void
234 {
235  QString msg = line.getMessage();
236  int i = msg.indexOf(" ");
237  tc::Severity severity = tc::severityFromString(msg.mid(0, i));
238  QString logLine = (line.getData().size() > 0 ? line.getData().join("\n") :
239  msg.mid(i+1));
240 
241  emit logMessage(severity, logLine);
242 }
243 
244 /** Handles a new descriptor event. The format for event messages of this type
245  * is:
246  *
247  * "650" SP "NEWDESC" 1*(SP ServerID)
248  */
249 void
251 {
252  QString descs = line.getMessage();
253  QStringList descList = descs.mid(descs.indexOf(" ")+1).split(" ");
254  emit newDescriptors(descList);
255 }
256 
257 /** Handles a new or updated address mapping event. The format for event
258  * messages of this type is:
259  *
260  * "650" SP "ADDRMAP" SP Address SP Address SP Expiry
261  * Expiry = DQUOTE ISOTime DQUOTE / "NEVER"
262  *
263  * Expiry is expressed as the local time (rather than GMT).
264  */
265 void
267 {
268  QStringList msg = line.getMessage().split(" ");
269  if (msg.size() >= 4) {
270  QDateTime expires;
271  if (msg.size() >= 5 && msg.at(3) != "NEVER")
272  expires = QDateTime::fromString(msg.at(3) + " " + msg.at(4), DATE_FMT);
273  emit addressMapped(msg.at(1), msg.at(2), expires);
274  }
275 }
276 
277 /** Handles a Tor status event. The format for event messages of this type is:
278  *
279  * "650" SP StatusType SP StatusSeverity SP StatusAction
280  * [SP StatusArguments] CRLF
281  *
282  * StatusType = "STATUS_GENERAL" / "STATUS_CLIENT" / "STATUS_SERVER"
283  * StatusSeverity = "NOTICE" / "WARN" / "ERR"
284  * StatusAction = 1*ALPHA
285  * StatusArguments = StatusArgument *(SP StatusArgument)
286  * StatusArgument = StatusKeyword '=' StatusValue
287  * StatusKeyword = 1*(ALNUM / "_")
288  * StatusValue = 1*(ALNUM / '_') / QuotedString
289  */
290 void
292 {
293  QString status;
294  tc::Severity severity;
295  QHash<QString,QString> args;
296  QString msg = line.getMessage();
297 
298  severity = tc::severityFromString(msg.section(' ', 1, 1));
299  status = msg.section(' ', 2, 2);
300  args = string_parse_keyvals(msg.section(' ', 3));
301  switch (e) {
302  case ClientStatus:
303  handleClientStatusEvent(severity, status, args);
304  break;
305 
306  case ServerStatus:
307  handleServerStatusEvent(severity, status, args);
308  break;
309 
310  case GeneralStatus:
311  handleGeneralStatusEvent(severity, status, args);
312  break;
313 
314  default:
315  break;
316  }
317 }
318 
319 /** Parses and emits a general Tor status event. */
320 void
322  const QString &action,
323  const QHash<QString,QString> &args)
324 {
325  if (! action.compare("DANGEROUS_TOR_VERSION", Qt::CaseInsensitive)) {
326  QString reason = args.value("REASON");
327  QString current = args.value("CURRENT");
328  QStringList recommended = args.value("RECOMMENDED")
329  .split(",", QString::SkipEmptyParts);
330  if (! reason.compare("NEW", Qt::CaseInsensitive))
331  emit dangerousTorVersion(tc::NewTorVersion, current, recommended);
332  else if (! reason.compare("UNRECOMMENDED", Qt::CaseInsensitive))
333  emit dangerousTorVersion(tc::UnrecommendedTorVersion, current, recommended);
334  else if (! reason.compare("OBSOLETE", Qt::CaseInsensitive)
335  || ! reason.compare("OLD", Qt::CaseInsensitive))
336  emit dangerousTorVersion(tc::ObsoleteTorVersion, current, recommended);
337  } else if (! action.compare("CLOCK_SKEW", Qt::CaseInsensitive)) {
338  int skew;
339  bool ok = false;
340  if (args.contains("SKEW"))
341  skew = args.value("SKEW").toInt(&ok);
342  else if (args.contains("MIN_SKEW"))
343  skew = args.value("MIN_SKEW").toInt(&ok);
344  if (ok)
345  emit clockSkewed(skew, args.value("SOURCE"));
346  } else if (! action.compare("BUG", Qt::CaseInsensitive)) {
347  emit bug(args.value("REASON"));
348  }
349 }
350 
351 /** Parses and emits a Tor client status event. */
352 void
354  const QString &action,
355  const QHash<QString,QString> &args)
356 {
357  if (! action.compare("CIRCUIT_ESTABLISHED", Qt::CaseInsensitive)) {
358  emit circuitEstablished();
359  } else if (! action.compare("DANGEROUS_PORT", Qt::CaseInsensitive)) {
360  bool reject = ! args.value("RESULT").compare("REJECT", Qt::CaseInsensitive);
361  emit dangerousPort(args.value("PORT").toUInt(), reject);
362  } else if (! action.compare("DANGEROUS_SOCKS", Qt::CaseInsensitive)) {
363  emit socksError(tc::DangerousSocksTypeError, args.value("ADDRESS"));
364  } else if (! action.compare("SOCKS_UNKNOWN_PROTOCOL", Qt::CaseInsensitive)) {
365  emit socksError(tc::UnknownSocksProtocolError, QString());
366  } else if (! action.compare("SOCKS_BAD_HOSTNAME", Qt::CaseInsensitive)) {
367  emit socksError(tc::BadSocksHostnameError, args.value("HOSTNAME"));
368  } else if (! action.compare("BOOTSTRAP", Qt::CaseInsensitive)) {
369  BootstrapStatus status
370  = BootstrapStatus(severity,
371  BootstrapStatus::statusFromString(args.value("TAG")),
372  args.value("PROGRESS").toInt(),
373  args.value("SUMMARY"),
374  args.value("WARNING"),
375  tc::connectionStatusReasonFromString(args.value("REASON")),
377  args.value("RECOMMENDATION")));
378  emit bootstrapStatusChanged(status);
379  }
380 }
381 
382 /** Parses and emits a Tor server status event. */
383 void
385  const QString &action,
386  const QHash<QString,QString> &args)
387 {
388  if (! action.compare("EXTERNAL_ADDRESS", Qt::CaseInsensitive)) {
389  emit externalAddressChanged(QHostAddress(args.value("ADDRESS")),
390  args.value("HOSTNAME"));
391  } else if (! action.compare("CHECKING_REACHABILITY", Qt::CaseInsensitive)) {
392  if (args.contains("ORADDRESS")) {
393  QPair<QHostAddress,quint16> pair = splitAddress(args.value("ORADDRESS"));
394  if (! pair.first.isNull())
395  emit checkingOrPortReachability(pair.first, pair.second);
396  } else if (args.contains("DIRADDRESS")) {
397  QPair<QHostAddress,quint16> pair = splitAddress(args.value("DIRADDRESS"));
398  if (! pair.first.isNull())
399  emit checkingDirPortReachability(pair.first, pair.second);
400  }
401  } else if (! action.compare("REACHABILITY_SUCCEEDED", Qt::CaseInsensitive)) {
402  if (args.contains("ORADDRESS")) {
403  QPair<QHostAddress,quint16> pair = splitAddress(args.value("ORADDRESS"));
404  if (! pair.first.isNull())
405  emit orPortReachabilityFinished(pair.first, pair.second, true);
406  } else if (args.contains("DIRADDRESS")) {
407  QPair<QHostAddress,quint16> pair = splitAddress(args.value("DIRADDRESS"));
408  if (! pair.first.isNull())
409  emit dirPortReachabilityFinished(pair.first, pair.second, true);
410  }
411  } else if (! action.compare("REACHABILITY_FAILED", Qt::CaseInsensitive)) {
412  if (args.contains("ORADDRESS")) {
413  QPair<QHostAddress,quint16> pair = splitAddress(args.value("ORADDRESS"));
414  if (! pair.first.isNull())
415  emit orPortReachabilityFinished(pair.first, pair.second, false);
416  } else if (args.contains("DIRADDRESS")) {
417  QPair<QHostAddress,quint16> pair = splitAddress(args.value("DIRADDRESS"));
418  if (! pair.first.isNull())
419  emit dirPortReachabilityFinished(pair.first, pair.second, false);
420  }
421  } else if (! action.compare("GOOD_SERVER_DESCRIPTOR", Qt::CaseInsensitive)) {
423  } else if (! action.compare("ACCEPTED_SERVER_DESCRIPTOR", Qt::CaseInsensitive)) {
424  QPair<QHostAddress,quint16> pair = splitAddress(args.value("DIRAUTH"));
425  if (! pair.first.isNull())
426  emit serverDescriptorAccepted(pair.first, pair.second);
427  } else if (! action.compare("BAD_SERVER_DESCRIPTOR", Qt::CaseInsensitive)) {
428  QPair<QHostAddress,quint16> pair = splitAddress(args.value("DIRAUTH"));
429  if (! pair.first.isNull())
430  emit serverDescriptorRejected(pair.first, pair.second,
431  args.value("REASON"));
432  } else if (! action.compare("DNS_HIJACKED", Qt::CaseInsensitive)) {
433  emit dnsHijacked();
434  } else if (! action.compare("DNS_USELESS", Qt::CaseInsensitive)) {
435  emit dnsUseless();
436  }
437 }
438 
439 /** Splits a string in the form "IP:PORT" into a QHostAddress and quint16
440  * pair. If either portion is invalid, a default-constructed QPair() is
441  * returned. */
442 QPair<QHostAddress,quint16>
443 TorEvents::splitAddress(const QString &address)
444 {
445  bool ok;
446  int idx = address.indexOf(":");
447  if (idx <= 0 || idx >= address.length()-1)
448  return QPair<QHostAddress,quint16>();
449 
450  QHostAddress ip = QHostAddress(address.mid(0, idx));
451  quint16 port = static_cast<quint16>(address.mid(idx+1).toUInt(&ok));
452  if (ip.isNull() || ! ok)
453  return QPair<QHostAddress,quint16>();
454  return QPair<QHostAddress,quint16>(ip, port);
455 }
456 
457 
void handleNewDescriptor(const ReplyLine &line)
Definition: TorEvents.cpp:250
void handleClientStatusEvent(tc::Severity severity, const QString &action, const QHash< QString, QString > &args)
Definition: TorEvents.cpp:353
void handleLogMessage(const ReplyLine &line)
Definition: TorEvents.cpp:233
ConnectionStatusReason connectionStatusReasonFromString(const QString &str)
Definition: tcglobal.cpp:55
void addressMapped(const QString &from, const QString &to, const QDateTime &expires)
void serverDescriptorRejected(const QHostAddress &ip, quint16 port, const QString &reason)
void circuitStatusChanged(const Circuit &circuit)
Definition: Stream.h:31
void serverDescriptorAccepted()
void checkingOrPortReachability(const QHostAddress &ip, quint16 port)
QHash< QString, QString > string_parse_keyvals(const QString &str, bool *ok)
Definition: stringutil.cpp:244
static Status statusFromString(const QString &tag)
static Recommendation actionFromString(const QString &str)
void bootstrapStatusChanged(const BootstrapStatus &status)
#define DATE_FMT
Definition: TorEvents.cpp:30
QStringList getData() const
Definition: ReplyLine.cpp:78
void dnsHijacked()
void clockSkewed(int skew, const QString &source)
void handleCircuitStatus(const ReplyLine &line)
Definition: TorEvents.cpp:184
void dnsUseless()
void socksError(tc::SocksError error, const QString &destination)
void streamStatusChanged(const Stream &stream)
void dangerousTorVersion(tc::TorVersionStatus reason, const QString &version, const QStringList &recommended)
QString getMessage() const
Definition: ReplyLine.cpp:64
void handleEvent(const ControlReply &reply)
Definition: TorEvents.cpp:124
static Stream fromString(const QString &stream)
Definition: Stream.cpp:63
void bug(const QString &reason)
TorEvents(QObject *parent=0)
Definition: TorEvents.cpp:34
static QPair< QHostAddress, quint16 > splitAddress(const QString &address)
Definition: TorEvents.cpp:443
void logMessage(tc::Severity level, const QString &msg)
QString i(QString str)
Definition: html.cpp:32
void externalAddressChanged(const QHostAddress &ip, const QString &hostname)
void handleStreamStatus(const ReplyLine &line)
Definition: TorEvents.cpp:213
QList< ReplyLine > getLines() const
void handleServerStatusEvent(tc::Severity severity, const QString &action, const QHash< QString, QString > &args)
Definition: TorEvents.cpp:384
void handleAddressMap(const ReplyLine &line)
Definition: TorEvents.cpp:266
void handleGeneralStatusEvent(tc::Severity severity, const QString &action, const QHash< QString, QString > &args)
Definition: TorEvents.cpp:321
void dangerousPort(quint16 port, bool rejected)
void dirPortReachabilityFinished(const QHostAddress &ip, quint16 port, bool reachable)
void orPortReachabilityFinished(const QHostAddress &ip, quint16 port, bool reachable)
Severity
Definition: tcglobal.h:69
static QString toString(TorEvents::Event e)
Definition: TorEvents.cpp:51
void checkingDirPortReachability(const QHostAddress &ip, quint16 port)
void circuitEstablished()
bool isValid() const
Definition: Stream.cpp:146
static Event parseEventType(const ReplyLine &line)
Definition: TorEvents.cpp:113
Severity severityFromString(const QString &str)
Definition: tcglobal.cpp:82
void handleBandwidthUpdate(const ReplyLine &line)
Definition: TorEvents.cpp:160
void newDescriptors(const QStringList &ids)
void handleStatusEvent(Event type, const ReplyLine &line)
Definition: TorEvents.cpp:291
static Event toTorEvent(const QString &event)
Definition: TorEvents.cpp:75
void bandwidthUpdate(quint64 bytesReceived, quint64 bytesSent)