core: connectToIrc: Prevent DNS leaks on connection when using proxy
[quassel.git] / src / core / corenetwork.cpp
index 8c03dc8..53960b6 100644 (file)
@@ -36,6 +36,7 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
     _userInputHandler(new CoreUserInputHandler(this)),
     _autoReconnectCount(0),
     _quitRequested(false),
+    _disconnectExpected(false),
 
     _previousConnectionAttemptFailed(false),
     _lastUsedServerIndex(0),
@@ -89,13 +90,39 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
 
 CoreNetwork::~CoreNetwork()
 {
-    if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
-        disconnectFromIrc(false);  // clean up, but this does not count as requested disconnect!
+    // Request a proper disconnect, but don't count as user-requested disconnect
+    if (socketConnected()) {
+        // Only try if the socket's fully connected (not initializing or disconnecting).
+        // Force an immediate disconnect, jumping the command queue.  Ensures the proper QUIT is
+        // shown even if other messages are queued.
+        disconnectFromIrc(false, QString(), false, true);
+        // Process the putCmd events that trigger the quit.  Without this, shutting down the core
+        // results in abrubtly closing the socket rather than sending the QUIT as expected.
+        QCoreApplication::processEvents();
+        // Wait briefly for each network to disconnect.  Sometimes it takes a little while to send.
+        if (!forceDisconnect()) {
+            qWarning() << "Timed out quitting network" << networkName() <<
+                          "(user ID " << userId() << ")";
+        }
+    }
     disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
     delete _userInputHandler;
 }
 
 
+bool CoreNetwork::forceDisconnect(int msecs)
+{
+    if (socket.state() == QAbstractSocket::UnconnectedState) {
+        // Socket already disconnected.
+        return true;
+    }
+    // Request a socket-level disconnect if not already happened
+    socket.disconnectFromHost();
+    // Return the result of waiting for disconnect; true if successful, otherwise false
+    return socket.waitForDisconnected(msecs);
+}
+
+
 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
 {
     if (!bufferName.isEmpty()) {
@@ -164,12 +191,16 @@ void CoreNetwork::connectToIrc(bool reconnecting)
     }
     else if (_previousConnectionAttemptFailed) {
         // cycle to next server if previous connection attempt failed
+        _previousConnectionAttemptFailed = false;
         displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
         if (++_lastUsedServerIndex >= serverList().size()) {
             _lastUsedServerIndex = 0;
         }
     }
-    _previousConnectionAttemptFailed = false;
+    else {
+        // Start out with the top server in the list
+        _lastUsedServerIndex = 0;
+    }
 
     Server server = usedServer();
     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
@@ -187,8 +218,9 @@ void CoreNetwork::connectToIrc(bool reconnecting)
 
     // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
     // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
-    QHostInfo::fromName(server.host);
-
+    if (! server.useProxy) {
+               QHostInfo::fromName(server.host);
+       }
 #ifdef HAVE_SSL
     if (server.useSsl) {
         CoreIdentity *identity = identityPtr();
@@ -210,6 +242,8 @@ void CoreNetwork::connectToIrc(bool reconnecting)
 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect,
                                     bool forceImmediate)
 {
+    // Disconnecting from the network, should expect a socket close or error
+    _disconnectExpected = true;
     _quitRequested = requested; // see socketDisconnected();
     if (!withReconnect) {
         _autoReconnectTimer.stop();
@@ -440,8 +474,10 @@ void CoreNetwork::socketHasData()
 
 void CoreNetwork::socketError(QAbstractSocket::SocketError error)
 {
-    if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
+    // Ignore socket closed errors if expected
+    if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
         return;
+    }
 
     _previousConnectionAttemptFailed = true;
     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
@@ -530,6 +566,8 @@ void CoreNetwork::socketDisconnected()
     setConnected(false);
     emit disconnected(networkId());
     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
+    // Reset disconnect expectations
+    _disconnectExpected = false;
     if (_quitRequested) {
         _quitRequested = false;
         setConnectionState(Network::Disconnected);
@@ -574,6 +612,7 @@ void CoreNetwork::networkInitialized()
 {
     setConnectionState(Network::Initialized);
     setConnected(true);
+    _disconnectExpected = false;
     _quitRequested = false;
 
     if (useAutoReconnect()) {