bool CZNC::WriteConfig() {
    if (GetConfigFile().empty()) {
        DEBUG("Config file name is empty?!");
        return false;
    }

    // We first write to a temporary file and then move it to the right place
    CFile* pFile = new CFile(GetConfigFile() + "~");

    if (!pFile->Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
        DEBUG("Could not write config to " + GetConfigFile() + "~: " +
              CString(strerror(errno)));
        delete pFile;
        return false;
    }

    // We have to "transfer" our lock on the config to the new file.
    // The old file (= inode) is going away and thus a lock on it would be
    // useless. These lock should always succeed (races, anyone?).
    if (!pFile->TryExLock()) {
        DEBUG("Error while locking the new config file, errno says: " +
              CString(strerror(errno)));
        pFile->Delete();
        delete pFile;
        return false;
    }

    pFile->Write(MakeConfigHeader() + "\n");

    CConfig config;
    config.AddKeyValuePair("AnonIPLimit", CString(m_uiAnonIPLimit));
    config.AddKeyValuePair("MaxBufferSize", CString(m_uiMaxBufferSize));
    config.AddKeyValuePair("SSLCertFile", CString(GetPemLocation()));
    config.AddKeyValuePair("SSLKeyFile", CString(GetKeyLocation()));
    config.AddKeyValuePair("SSLDHParamFile", CString(GetDHParamLocation()));
    config.AddKeyValuePair("ProtectWebSessions",
                           CString(m_bProtectWebSessions));
    config.AddKeyValuePair("HideVersion", CString(m_bHideVersion));
    config.AddKeyValuePair("AuthOnlyViaModule", CString(m_bAuthOnlyViaModule));
    config.AddKeyValuePair("Version", CString(VERSION_STR));
    config.AddKeyValuePair("ConfigWriteDelay", CString(m_uiConfigWriteDelay));

    unsigned int l = 0;
    for (CListener* pListener : m_vpListeners) {
        CConfig listenerConfig;

        listenerConfig.AddKeyValuePair("Host", pListener->GetBindHost());
        listenerConfig.AddKeyValuePair("URIPrefix",
                                       pListener->GetURIPrefix() + "/");
        listenerConfig.AddKeyValuePair("Port", CString(pListener->GetPort()));

        listenerConfig.AddKeyValuePair(
            "IPv4", CString(pListener->GetAddrType() != ADDR_IPV6ONLY));
        listenerConfig.AddKeyValuePair(
            "IPv6", CString(pListener->GetAddrType() != ADDR_IPV4ONLY));

        listenerConfig.AddKeyValuePair("SSL", CString(pListener->IsSSL()));

        listenerConfig.AddKeyValuePair(
            "AllowIRC",
            CString(pListener->GetAcceptType() != CListener::ACCEPT_HTTP));
        listenerConfig.AddKeyValuePair(
            "AllowWeb",
            CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC));

        config.AddSubConfig("Listener", "listener" + CString(l++),
                            listenerConfig);
    }

    config.AddKeyValuePair("ConnectDelay", CString(m_uiConnectDelay));
    config.AddKeyValuePair("ServerThrottle",
                           CString(m_sConnectThrottle.GetTTL() / 1000));

    if (!m_sPidFile.empty()) {
        config.AddKeyValuePair("PidFile", m_sPidFile.FirstLine());
    }

    if (!m_sSkinName.empty()) {
        config.AddKeyValuePair("Skin", m_sSkinName.FirstLine());
    }

    if (!m_sStatusPrefix.empty()) {
        config.AddKeyValuePair("StatusPrefix", m_sStatusPrefix.FirstLine());
    }

    if (!m_sSSLCiphers.empty()) {
        config.AddKeyValuePair("SSLCiphers", CString(m_sSSLCiphers));
    }

    if (!m_sSSLProtocols.empty()) {
        config.AddKeyValuePair("SSLProtocols", m_sSSLProtocols);
    }

    for (const CString& sLine : m_vsMotd) {
        config.AddKeyValuePair("Motd", sLine.FirstLine());
    }

    for (const CString& sProxy : m_vsTrustedProxies) {
        config.AddKeyValuePair("TrustedProxy", sProxy.FirstLine());
    }

    CModules& Mods = GetModules();

    for (const CModule* pMod : Mods) {
        CString sName = pMod->GetModName();
        CString sArgs = pMod->GetArgs();

        if (!sArgs.empty()) {
            sArgs = " " + sArgs.FirstLine();
        }

        config.AddKeyValuePair("LoadModule", sName.FirstLine() + sArgs);
    }

    for (const auto& it : m_msUsers) {
        CString sErr;

        if (!it.second->IsValid(sErr)) {
            DEBUG("** Error writing config for user [" << it.first << "] ["
                                                       << sErr << "]");
            continue;
        }

        config.AddSubConfig("User", it.second->GetUserName(),
                            it.second->ToConfig());
    }

    config.Write(*pFile);

    // If Sync() fails... well, let's hope nothing important breaks..
    pFile->Sync();

    if (pFile->HadError()) {
        DEBUG("Error while writing the config, errno says: " +
              CString(strerror(errno)));
        pFile->Delete();
        delete pFile;
        return false;
    }

    // We wrote to a temporary name, move it to the right place
    if (!pFile->Move(GetConfigFile(), true)) {
        DEBUG(
            "Error while replacing the config file with a new version, errno "
            "says "
            << strerror(errno));
        pFile->Delete();
        delete pFile;
        return false;
    }

    // Everything went fine, just need to update the saved path.
    pFile->SetFileName(GetConfigFile());

    // Make sure the lock is kept alive as long as we need it.
    delete m_pLockFile;
    m_pLockFile = pFile;

    return true;
}