// Copyright 2005-2021 The Mumble Developers. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file at the root of the // Mumble source tree or at . #include #if defined(Q_OS_WIN) # include "win.h" #endif #include "OSInfo.h" #include "Version.h" #include #include #include #include #if defined(Q_OS_WIN) # include #endif #if defined(Q_OS_MACOS) # include # include # include # include // Ignore deprecation warnings for Gestalt. // See mumble-voip/mumble#3290 for more information. # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(Q_OS_WIN) // regString converts a wchar_t string of size to a // QString. If the string contains a NUL value, that // NUL value will terminate the string. static QString regString(wchar_t *string, int size) { if (size <= 0) { return QString(); } // If string contains a NUL, adjust the size such // that the NUL is not included in the returned // string. const size_t adjustedSize = wcsnlen(string, static_cast< size_t >(size)); // The return value of wcsnlen is <= size which is // an int, so casting adjustedSize to int is safe. return QString::fromWCharArray(string, static_cast< int >(adjustedSize)); } /// Query for a Windows 10-style displayable version. /// /// This returns a string of the kind: /// /// Windows 10 Enterprise 2009 19042.804 /// /// which is: /// /// $ProductName $Version $Build.$Ubr /// /// Of note, $Version is formatted as YYMM. /// So, 2009 is a Windows 10 update released in September 2020. /// /// This function can be called on non-Windows 10 OSes. /// On those, this functions fails to query some of the /// registry keys used for building the version string /// to be returned. Because of that, it is safe to call /// this function, and if it returns an empty/null string, /// a legacy version string can be displayed. static QString win10DisplayableVersion() { HKEY key = 0; auto err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &key); if (err != ERROR_SUCCESS) { RegCloseKey(key); return QString(); } wchar_t buf[64]; DWORD len = sizeof(buf); err = RegQueryValueEx(key, L"ProductName", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len); if (err != ERROR_SUCCESS) { RegCloseKey(key); return QString(); } const auto productName = regString(buf, static_cast< int >(len / sizeof(buf[0]))); len = sizeof(buf); err = RegQueryValueEx(key, L"ReleaseId", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len); if (err != ERROR_SUCCESS) { RegCloseKey(key); return QString(); } const auto releaseId = regString(buf, static_cast< int >(len / sizeof(buf[0]))); len = sizeof(buf); err = RegQueryValueEx(key, L"CurrentBuild", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len); if (err != ERROR_SUCCESS) { RegCloseKey(key); return QString(); } const auto currentBuild = regString(buf, static_cast< int >(len / sizeof(buf[0]))); DWORD dw = 0; len = sizeof(dw); err = RegQueryValueEx(key, L"UBR", nullptr, nullptr, reinterpret_cast< LPBYTE >(&dw), &len); if (err != ERROR_SUCCESS) { RegCloseKey(key); return QString(); } const auto ubr = QString::number(static_cast< ulong >(dw), 10); RegCloseKey(key); return QString::fromLatin1("%1 %2 %3.%4").arg(productName, releaseId, currentBuild, ubr); } #endif QString OSInfo::getArchitecture(const bool build) { QString architecture = build ? QSysInfo::buildCpuArchitecture() : QSysInfo::currentCpuArchitecture(); if (architecture == QLatin1String("x86_64")) { architecture = QLatin1String("x64"); } else if (architecture == QLatin1String("i386")) { architecture = QLatin1String("x86"); } return architecture; } QString OSInfo::getMacHash(const QList< QHostAddress > &qlBind) { QString first, second, third; foreach (const QNetworkInterface &qni, QNetworkInterface::allInterfaces()) { if (!qni.isValid()) continue; if (qni.flags() & QNetworkInterface::IsLoopBack) continue; if (qni.hardwareAddress().isEmpty()) continue; QString hash = QString::fromUtf8( QCryptographicHash::hash(qni.hardwareAddress().toUtf8(), QCryptographicHash::Sha1).toHex()); if (third.isEmpty() || third > hash) third = hash; if (!(qni.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning))) continue; if (second.isEmpty() || second > hash) second = hash; foreach (const QNetworkAddressEntry &qnae, qni.addressEntries()) { const QHostAddress &qha = qnae.ip(); if (qlBind.isEmpty() || qlBind.contains(qha)) { if (first.isEmpty() || first > hash) first = hash; } } } if (!first.isEmpty()) return first; if (!second.isEmpty()) return second; if (!third.isEmpty()) return third; return QString(); } QString OSInfo::getOS() { #if defined(Q_OS_WIN) return QLatin1String("Windows"); #elif defined(Q_OS_LINUX) return QLatin1String("Linux"); #elif defined(Q_OS_MACOS) return QLatin1String("macOS"); #elif defined(Q_OS_FREEBSD) return QLatin1String("FreeBSD"); #elif defined(Q_OS_NETBSD) return QLatin1String("NetBSD"); #elif defined(Q_OS_OPENBSD) return QLatin1String("OpenBSD"); #elif defined(Q_OS_BSD4) return QLatin1String("BSD"); #elif defined(Q_OS_UNIX) return QLatin1String("UNIX"); #else return QLatin1String("Unknown"); #endif } QString OSInfo::getOSDisplayableVersion(const bool appendArch) { #if defined(Q_OS_WIN) QString os = win10DisplayableVersion(); if (os.isEmpty()) { OSVERSIONINFOEXW ovi; memset(&ovi, 0, sizeof(ovi)); ovi.dwOSVersionInfoSize = sizeof(ovi); if (!GetVersionEx(reinterpret_cast< OSVERSIONINFOW * >(&ovi))) { return QString(); } if (ovi.dwMajorVersion == 10) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows 10"); } else { os = QLatin1String("Windows 10 Server"); } } else if (ovi.dwMajorVersion == 6) { if (ovi.dwMinorVersion == 0) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows Vista"); } else { os = QLatin1String("Windows Server 2008"); } } else if (ovi.dwMinorVersion == 1) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows 7"); } else { os = QLatin1String("Windows Server 2008 R2"); } } else if (ovi.dwMinorVersion == 2) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows 8"); } else { os = QLatin1String("Windows Server 2012"); } } else if (ovi.dwMinorVersion == 3) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows 8.1"); } else { os = QLatin1String("Windows Server 2012 R2"); } } else if (ovi.dwMinorVersion == 4) { if (ovi.wProductType == VER_NT_WORKSTATION) { os = QLatin1String("Windows 10"); } else { os = QLatin1String("Windows 10 Server"); } } } typedef BOOL(WINAPI * PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD); PGPI pGetProductInfo = (PGPI) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo"); if (!pGetProductInfo) { return QString(); } DWORD dwType = 0; if (!pGetProductInfo(ovi.dwMajorVersion, ovi.dwMinorVersion, 0, 0, &dwType)) { return QString(); } switch (dwType) { case PRODUCT_ULTIMATE: os.append(QLatin1String(" Ultimate Edition")); break; case PRODUCT_PROFESSIONAL: os.append(QLatin1String(" Professional")); break; case PRODUCT_HOME_PREMIUM: os.append(QLatin1String(" Home Premium Edition")); break; case PRODUCT_HOME_BASIC: os.append(QLatin1String(" Home Basic Edition")); break; case PRODUCT_ENTERPRISE: os.append(QLatin1String(" Enterprise Edition")); break; case PRODUCT_BUSINESS: os.append(QLatin1String(" Business Edition")); break; case PRODUCT_STARTER: os.append(QLatin1String(" Starter Edition")); break; case PRODUCT_CLUSTER_SERVER: os.append(QLatin1String(" Cluster Server Edition")); break; case PRODUCT_DATACENTER_SERVER: os.append(QLatin1String(" Datacenter Edition")); break; case PRODUCT_DATACENTER_SERVER_CORE: os.append(QLatin1String(" Datacenter Edition (core installation)")); break; case PRODUCT_ENTERPRISE_SERVER: os.append(QLatin1String(" Enterprise Edition")); break; case PRODUCT_ENTERPRISE_SERVER_CORE: os.append(QLatin1String(" Enterprise Edition (core installation)")); break; case PRODUCT_ENTERPRISE_SERVER_IA64: os.append(QLatin1String(" Enterprise Edition for Itanium-based Systems")); break; case PRODUCT_SMALLBUSINESS_SERVER: os.append(QLatin1String(" Small Business Server")); break; case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: os.append(QLatin1String(" Small Business Server Premium Edition")); break; case PRODUCT_STANDARD_SERVER: os.append(QLatin1String(" Standard Edition")); break; case PRODUCT_STANDARD_SERVER_CORE: os.append(QLatin1String(" Standard Edition (core installation)")); break; case PRODUCT_WEB_SERVER: os.append(QLatin1String(" Web Server Edition")); break; } // Service Packs (may be empty) static_assert(sizeof(TCHAR) == sizeof(wchar_t), "Expected Unicode TCHAR."); auto tmp = QString::fromWCharArray(ovi.szCSDVersion); if (!tmp.isEmpty()) { os.append(QLatin1String(" ")); os.append(tmp); } tmp = QString::asprintf(" - %lu.%lu.%lu", static_cast< unsigned long >(ovi.dwMajorVersion), static_cast< unsigned long >(ovi.dwMinorVersion), static_cast< unsigned long >(ovi.dwBuildNumber)); os.append(tmp); return os; } #elif defined(Q_OS_MACOS) const QString os = QLatin1String("macOS ") + getOSVersion(); #else const QString os = QSysInfo::prettyProductName(); #endif if (!appendArch) { return os; } return os + QString(" [%1]").arg(getArchitecture(false)); } QString OSInfo::getOSVersion() { #if defined(Q_OS_WIN) OSVERSIONINFOEXW ovi; memset(&ovi, 0, sizeof(ovi)); ovi.dwOSVersionInfoSize = sizeof(ovi); if (!GetVersionEx(reinterpret_cast< OSVERSIONINFOW * >(&ovi))) { return QString(); } return QString::asprintf("%lu.%lu.%lu.%lu", static_cast< unsigned long >(ovi.dwMajorVersion), static_cast< unsigned long >(ovi.dwMinorVersion), static_cast< unsigned long >(ovi.dwBuildNumber), (ovi.wProductType == VER_NT_WORKSTATION) ? 1UL : 0UL); #elif defined(Q_OS_MACOS) SInt32 major, minor, bugfix; OSErr err = Gestalt(gestaltSystemVersionMajor, &major); if (err == noErr) err = Gestalt(gestaltSystemVersionMinor, &minor); if (err == noErr) err = Gestalt(gestaltSystemVersionBugFix, &bugfix); if (err != noErr) return QString::number(QSysInfo::MacintoshVersion, 16); char *buildno = nullptr; char buildno_buf[32]; size_t sz_buildno_buf = sizeof(buildno); int ret = sysctlbyname("kern.osversion", buildno_buf, &sz_buildno_buf, nullptr, 0); if (ret == 0) { buildno = &buildno_buf[0]; } return QString::asprintf("%lu.%lu.%lu %s", static_cast< unsigned long >(major), static_cast< unsigned long >(minor), static_cast< unsigned long >(bugfix), buildno ? buildno : "unknown"); #else return QSysInfo::productVersion(); #endif } void OSInfo::fillXml(QDomDocument &doc, QDomElement &root, const QList< QHostAddress > &qlBind) { QDomElement tag = doc.createElement(QLatin1String("machash")); root.appendChild(tag); QDomText t = doc.createTextNode(getMacHash(qlBind)); tag.appendChild(t); tag = doc.createElement(QLatin1String("arch")); root.appendChild(tag); t = doc.createTextNode(getArchitecture(true)); tag.appendChild(t); tag = doc.createElement(QLatin1String("version")); root.appendChild(tag); t = doc.createTextNode(QLatin1String(MUMTEXT(MUMBLE_VERSION))); tag.appendChild(t); tag = doc.createElement(QLatin1String("release")); root.appendChild(tag); t = doc.createTextNode(QLatin1String(MUMBLE_RELEASE)); tag.appendChild(t); tag = doc.createElement(QLatin1String("os")); root.appendChild(tag); t = doc.createTextNode(getOS()); tag.appendChild(t); tag = doc.createElement(QLatin1String("osarch")); root.appendChild(tag); t = doc.createTextNode(getArchitecture(false)); tag.appendChild(t); tag = doc.createElement(QLatin1String("osver")); root.appendChild(tag); t = doc.createTextNode(getOSVersion()); tag.appendChild(t); tag = doc.createElement(QLatin1String("osverbose")); root.appendChild(tag); t = doc.createTextNode(getOSDisplayableVersion(false)); tag.appendChild(t); tag = doc.createElement(QLatin1String("qt")); root.appendChild(tag); t = doc.createTextNode(QString::fromLatin1(qVersion())); tag.appendChild(t); QString cpu_id, cpu_extid; bool bSSE2 = false; #if defined(Q_OS_WIN) # define regstr(x) QString::fromLatin1(reinterpret_cast< const char * >(&x), 4) int chop; int cpuinfo[4]; __cpuid(cpuinfo, 1); bSSE2 = (cpuinfo[3] & 0x04000000); __cpuid(cpuinfo, 0); cpu_id = regstr(cpuinfo[1]) + regstr(cpuinfo[3]) + regstr(cpuinfo[2]); for (unsigned int j = 2; j <= 4; ++j) { __cpuid(cpuinfo, 0x80000000 + j); cpu_extid += regstr(cpuinfo[0]) + regstr(cpuinfo[1]) + regstr(cpuinfo[2]) + regstr(cpuinfo[3]); } cpu_id = cpu_id.trimmed(); chop = cpu_id.indexOf(QLatin1Char('\0')); if (chop != -1) cpu_id.truncate(chop); cpu_extid = cpu_extid.trimmed(); chop = cpu_extid.indexOf(QLatin1Char('\0')); if (chop != -1) cpu_extid.truncate(chop); #endif tag = doc.createElement(QLatin1String("cpu_id")); root.appendChild(tag); t = doc.createTextNode(cpu_id); tag.appendChild(t); tag = doc.createElement(QLatin1String("cpu_extid")); root.appendChild(tag); t = doc.createTextNode(cpu_extid); tag.appendChild(t); tag = doc.createElement(QLatin1String("cpu_sse2")); root.appendChild(tag); t = doc.createTextNode(QString::number(bSSE2 ? 1 : 0)); tag.appendChild(t); }