bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
    TOption<const CString&> StringOptions[] = {
        {"nick", &CUser::SetNick},
        {"quitmsg", &CUser::SetQuitMsg},
        {"altnick", &CUser::SetAltNick},
        {"ident", &CUser::SetIdent},
        {"realname", &CUser::SetRealName},
        {"chanmodes", &CUser::SetDefaultChanModes},
        {"bindhost", &CUser::SetBindHost},
        {"vhost", &CUser::SetBindHost},
        {"dccbindhost", &CUser::SetDCCBindHost},
        {"dccvhost", &CUser::SetDCCBindHost},
        {"timestampformat", &CUser::SetTimestampFormat},
        {"skin", &CUser::SetSkinName},
        {"clientencoding", &CUser::SetClientEncoding},
    };
    TOption<unsigned int> UIntOptions[] = {
        {"jointries", &CUser::SetJoinTries},
        {"maxnetworks", &CUser::SetMaxNetworks},
        {"maxquerybuffers", &CUser::SetMaxQueryBuffers},
        {"maxjoins", &CUser::SetMaxJoins},
        {"notraffictimeout", &CUser::SetNoTrafficTimeout},
    };
    TOption<bool> BoolOptions[] = {
        {"keepbuffer",
         &CUser::SetKeepBuffer},  // XXX compatibility crap from pre-0.207
        {"autoclearchanbuffer", &CUser::SetAutoClearChanBuffer},
        {"autoclearquerybuffer", &CUser::SetAutoClearQueryBuffer},
        {"multiclients", &CUser::SetMultiClients},
        {"denyloadmod", &CUser::SetDenyLoadMod},
        {"admin", &CUser::SetAdmin},
        {"denysetbindhost", &CUser::SetDenySetBindHost},
        {"denysetvhost", &CUser::SetDenySetBindHost},
        {"appendtimestamp", &CUser::SetTimestampAppend},
        {"prependtimestamp", &CUser::SetTimestampPrepend},
        {"authonlyviamodule", &CUser::SetAuthOnlyViaModule},
    };

    for (const auto& Option : StringOptions) {
        CString sValue;
        if (pConfig->FindStringEntry(Option.name, sValue))
            (this->*Option.pSetter)(sValue);
    }
    for (const auto& Option : UIntOptions) {
        CString sValue;
        if (pConfig->FindStringEntry(Option.name, sValue))
            (this->*Option.pSetter)(sValue.ToUInt());
    }
    for (const auto& Option : BoolOptions) {
        CString sValue;
        if (pConfig->FindStringEntry(Option.name, sValue))
            (this->*Option.pSetter)(sValue.ToBool());
    }

    VCString vsList;
    pConfig->FindStringVector("allow", vsList);
    for (const CString& sHost : vsList) {
        AddAllowedHost(sHost);
    }
    pConfig->FindStringVector("ctcpreply", vsList);
    for (const CString& sReply : vsList) {
        AddCTCPReply(sReply.Token(0), sReply.Token(1, true));
    }

    CString sValue;

    CString sDCCLookupValue;
    pConfig->FindStringEntry("dcclookupmethod", sDCCLookupValue);
    if (pConfig->FindStringEntry("bouncedccs", sValue)) {
        if (sValue.ToBool()) {
            CUtils::PrintAction("Loading Module [bouncedcc]");
            CString sModRet;
            bool bModRet = GetModules().LoadModule(
                "bouncedcc", "", CModInfo::UserModule, this, nullptr, sModRet);

            CUtils::PrintStatus(bModRet, sModRet);
            if (!bModRet) {
                sError = sModRet;
                return false;
            }

            if (sDCCLookupValue.Equals("Client")) {
                GetModules().FindModule("bouncedcc")->SetNV("UseClientIP", "1");
            }
        }
    }
    if (pConfig->FindStringEntry("buffer", sValue))
        SetBufferCount(sValue.ToUInt(), true);
    if (pConfig->FindStringEntry("chanbuffersize", sValue))
        SetChanBufferSize(sValue.ToUInt(), true);
    if (pConfig->FindStringEntry("querybuffersize", sValue))
        SetQueryBufferSize(sValue.ToUInt(), true);
    if (pConfig->FindStringEntry("awaysuffix", sValue)) {
        CUtils::PrintMessage(
            "WARNING: AwaySuffix has been deprecated, instead try -> "
            "LoadModule = awaynick %nick%_" +
            sValue);
    }
    if (pConfig->FindStringEntry("autocycle", sValue)) {
        if (sValue.Equals("true"))
            CUtils::PrintError(
                "WARNING: AutoCycle has been removed, instead try -> "
                "LoadModule = autocycle");
    }
    if (pConfig->FindStringEntry("keepnick", sValue)) {
        if (sValue.Equals("true"))
            CUtils::PrintError(
                "WARNING: KeepNick has been deprecated, instead try -> "
                "LoadModule = keepnick");
    }
    if (pConfig->FindStringEntry("statusprefix", sValue)) {
        if (!SetStatusPrefix(sValue)) {
            sError = "Invalid StatusPrefix [" + sValue +
                     "] Must be 1-5 chars, no spaces.";
            CUtils::PrintError(sError);
            return false;
        }
    }
    if (pConfig->FindStringEntry("timezone", sValue)) {
        SetTimezone(sValue);
    }
    if (pConfig->FindStringEntry("timezoneoffset", sValue)) {
        if (fabs(sValue.ToDouble()) > 0.1) {
            CUtils::PrintError(
                "WARNING: TimezoneOffset has been deprecated, now you can set "
                "your timezone by name");
        }
    }
    if (pConfig->FindStringEntry("timestamp", sValue)) {
        if (!sValue.Trim_n().Equals("true")) {
            if (sValue.Trim_n().Equals("append")) {
                SetTimestampAppend(true);
                SetTimestampPrepend(false);
            } else if (sValue.Trim_n().Equals("prepend")) {
                SetTimestampAppend(false);
                SetTimestampPrepend(true);
            } else if (sValue.Trim_n().Equals("false")) {
                SetTimestampAppend(false);
                SetTimestampPrepend(false);
            } else {
                SetTimestampFormat(sValue);
            }
        }
    }
    if (pConfig->FindStringEntry("language", sValue)) {
        SetLanguage(sValue);
    }
    pConfig->FindStringEntry("pass", sValue);
    // There are different formats for this available:
    // Pass = <plain text>
    // Pass = <md5 hash> -
    // Pass = plain#<plain text>
    // Pass = <hash name>#<hash>
    // Pass = <hash name>#<salted hash>#<salt>#
    // 'Salted hash' means hash of 'password' + 'salt'
    // Possible hashes are md5 and sha256
    if (sValue.TrimSuffix("-")) {
        SetPass(sValue.Trim_n(), CUser::HASH_MD5);
    } else {
        CString sMethod = sValue.Token(0, false, "#");
        CString sPass = sValue.Token(1, true, "#");
        if (sMethod == "md5" || sMethod == "sha256") {
            CUser::eHashType type = CUser::HASH_MD5;
            if (sMethod == "sha256") type = CUser::HASH_SHA256;

            CString sSalt = sPass.Token(1, false, "#");
            sPass = sPass.Token(0, false, "#");
            SetPass(sPass, type, sSalt);
        } else if (sMethod == "plain") {
            SetPass(sPass, CUser::HASH_NONE);
        } else {
            SetPass(sValue, CUser::HASH_NONE);
        }
    }
    CConfig::SubConfig subConf;
    CConfig::SubConfig::const_iterator subIt;
    pConfig->FindSubConfig("pass", subConf);
    if (!sValue.empty() && !subConf.empty()) {
        sError = "Password defined more than once";
        CUtils::PrintError(sError);
        return false;
    }
    subIt = subConf.begin();
    if (subIt != subConf.end()) {
        CConfig* pSubConf = subIt->second.m_pSubConfig;
        CString sHash;
        CString sMethod;
        CString sSalt;
        CUser::eHashType method;
        pSubConf->FindStringEntry("hash", sHash);
        pSubConf->FindStringEntry("method", sMethod);
        pSubConf->FindStringEntry("salt", sSalt);
        if (sMethod.empty() || sMethod.Equals("plain"))
            method = CUser::HASH_NONE;
        else if (sMethod.Equals("md5"))
            method = CUser::HASH_MD5;
        else if (sMethod.Equals("sha256"))
            method = CUser::HASH_SHA256;
        else {
            sError = "Invalid hash method";
            CUtils::PrintError(sError);
            return false;
        }

        SetPass(sHash, method, sSalt);
        if (!pSubConf->empty()) {
            sError = "Unhandled lines in config!";
            CUtils::PrintError(sError);

            CZNC::DumpConfig(pSubConf);
            return false;
        }
        ++subIt;
    }
    if (subIt != subConf.end()) {
        sError = "Password defined more than once";
        CUtils::PrintError(sError);
        return false;
    }

    pConfig->FindSubConfig("network", subConf);
    for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) {
        const CString& sNetworkName = subIt->first;

        CUtils::PrintMessage("Loading network [" + sNetworkName + "]");

        CIRCNetwork* pNetwork = FindNetwork(sNetworkName);

        if (!pNetwork) {
            pNetwork = new CIRCNetwork(this, sNetworkName);
        }

        if (!pNetwork->ParseConfig(subIt->second.m_pSubConfig, sError)) {
            return false;
        }
    }

    if (pConfig->FindStringVector("server", vsList, false) ||
        pConfig->FindStringVector("chan", vsList, false) ||
        pConfig->FindSubConfig("chan", subConf, false)) {
        CIRCNetwork* pNetwork = FindNetwork("default");
        if (!pNetwork) {
            CString sErrorDummy;
            pNetwork = AddNetwork("default", sErrorDummy);
        }

        if (pNetwork) {
            CUtils::PrintMessage(
                "NOTICE: Found deprecated config, upgrading to a network");

            if (!pNetwork->ParseConfig(pConfig, sError, true)) {
                return false;
            }
        }
    }

    pConfig->FindStringVector("loadmodule", vsList);
    for (const CString& sMod : vsList) {
        CString sModName = sMod.Token(0);
        CString sNotice = "Loading user module [" + sModName + "]";

        // XXX Legacy crap, added in ZNC 0.089
        if (sModName == "discon_kick") {
            sNotice =
                "NOTICE: [discon_kick] was renamed, loading [disconkick] "
                "instead";
            sModName = "disconkick";
        }

        // XXX Legacy crap, added in ZNC 0.099
        if (sModName == "fixfreenode") {
            sNotice =
                "NOTICE: [fixfreenode] doesn't do anything useful anymore, "
                "ignoring it";
            CUtils::PrintMessage(sNotice);
            continue;
        }

        // XXX Legacy crap, added in ZNC 0.207
        if (sModName == "admin") {
            sNotice =
                "NOTICE: [admin] module was renamed, loading [controlpanel] "
                "instead";
            sModName = "controlpanel";
        }

        // XXX Legacy crap, should have been added ZNC 0.207, but added only in
        // 1.1 :(
        if (sModName == "away") {
            sNotice = "NOTICE: [away] was renamed, loading [awaystore] instead";
            sModName = "awaystore";
        }

        // XXX Legacy crap, added in 1.1; fakeonline module was dropped in 1.0
        // and returned in 1.1
        if (sModName == "fakeonline") {
            sNotice =
                "NOTICE: [fakeonline] was renamed, loading [modules_online] "
                "instead";
            sModName = "modules_online";
        }

        // XXX Legacy crap, added in 1.3
        if (sModName == "charset") {
            CUtils::PrintAction(
                "NOTICE: Charset support was moved to core, importing old "
                "charset module settings");
            size_t uIndex = 1;
            if (sMod.Token(uIndex).Equals("-force")) {
                uIndex++;
            }
            VCString vsClient, vsServer;
            sMod.Token(uIndex).Split(",", vsClient);
            sMod.Token(uIndex + 1).Split(",", vsServer);
            if (vsClient.empty() || vsServer.empty()) {
                CUtils::PrintStatus(
                    false, "charset module was loaded with wrong parameters.");
                continue;
            }
            SetClientEncoding(vsClient[0]);
            for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
                pNetwork->SetEncoding(vsServer[0]);
            }
            CUtils::PrintStatus(true, "Using [" + vsClient[0] +
                                          "] for clients, and [" + vsServer[0] +
                                          "] for servers");
            continue;
        }

        CString sModRet;
        CString sArgs = sMod.Token(1, true);

        bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet);

        CUtils::PrintStatus(bModRet, sModRet);
        if (!bModRet) {
            // XXX The awaynick module was retired in 1.6 (still available as
            // external module)
            if (sModName == "awaynick") {
                // load simple_away instead, unless it's already on the list
                if (std::find(vsList.begin(), vsList.end(), "simple_away") ==
                    vsList.end()) {
                    sNotice = "Loading [simple_away] module instead";
                    sModName = "simple_away";
                    // not a fatal error if simple_away is not available
                    LoadModule(sModName, sArgs, sNotice, sModRet);
                }
            } else {
                sError = sModRet;
                return false;
            }
        }
        continue;
    }

    // Move ircconnectenabled to the networks
    if (pConfig->FindStringEntry("ircconnectenabled", sValue)) {
        for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
            pNetwork->SetIRCConnectEnabled(sValue.ToBool());
        }
    }

    return true;
}