#include "lisans_uzak.hpp" #include #include #include #include #include #include #include #include #include "ayar_kalici.hpp" // ayarOku/ayarYaz (URL ayarı) #include "fiko/core/gunluk.hpp" #include "imgui.h" #include "lisans_canli.hpp" // uzakCanliTazeKontrol, uzakCanliAyarBolumu #include "lisans_yukle.hpp" // lisansAcikAnahtarHex, lisansYukleVeUygula, lisansSistemi #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include namespace fs = std::filesystem; namespace fiko { namespace { // ── Ayarlar ────────────────────────────────────────────────────────────────── // URL derleme zamanında buraya GÖMÜLÜR; çalışma zamanında "lisans_uzak_url" // ayarı geçersiz kılabilir. Adresi değiştirmek saldırgana bir şey kazandırmaz — // politika yalnız gömülü açık anahtarla doğrulanan imzayla kabul edilir. constexpr const char* UZAK_URL_VARSAYILAN = ""; constexpr const char* UZAK_URL_AYAR = "lisans_uzak_url"; constexpr i64 DENETIM_ARALIGI = 6 * 3600; // saniye: sessiz periyodik yeniden denetim std::mutex g_mtx; std::shared_ptr g_politika; // nullptr = henüz politika yok i64 g_sonDenetleme = 0; // son deneme ZAMANI (başarısız da olsa — spam koruması) i64 g_sonBasari = 0; // son BAŞARILI indirme (0 = hiç) std::string g_kaynak; // "ağ" / "önbellek" / "" std::string g_hata; // son denetim hatası std::atomic g_denetimSuruyor{false}; std::atomic g_yenidenDegerlendir{false}; // pompa UI iş parçacığında uygular fs::path onbellekYolu() { // %ProgramData% makine geneli (tüm kullanıcılar/süreçler aynı kararı görür); // yazılamazsa exe yanı — kayıt hiçbir hâlde tamamen kaybolmasın. if (const char* pd = std::getenv("ProgramData"); pd && *pd) { std::error_code ec; const fs::path p = fs::path(pd) / "FikoCAD" / "lisans_uzak.pol"; fs::create_directories(p.parent_path(), ec); return p; } return fs::path(exeYaniYol("lisans_uzak.pol")); } std::string dosyaOku(const fs::path& p) { std::ifstream f(p, std::ios::binary); if (!f) return ""; std::ostringstream ss; ss << f.rdbuf(); return ss.str(); } // Atomik yazma: önce .tmp sonra rename → yarım dosya önbelleği bozmasın. bool dosyaYazAtomik(const fs::path& p, const std::string& icerik) { std::error_code ec; const fs::path tmp = p.string() + ".tmp"; { std::ofstream f(tmp, std::ios::binary); if (!f) return false; f << icerik; f.close(); if (!f.good()) return false; } fs::rename(tmp, p, ec); if (ec) { fs::remove(p, ec); // Windows rename var üstüne yazmaz — eskisini silip dene ec.clear(); fs::rename(tmp, p, ec); if (ec) return false; } return true; } std::string tarihMetni(i64 unix) { if (unix == 0) return "hiç"; const std::time_t t = (std::time_t)unix; std::tm tm{}; char buf[32] = "?"; if (localtime_s(&tm, &t) == 0) std::strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M", &tm); return buf; } // ── URL çözümleme: [https://]host[:port]/yol ───────────────────────────────── struct UrlParca { bool https = true; u16 port = 443; std::string host, yol; }; bool urlCoz(const std::string& url, UrlParca& u) { std::string kalan = url; for (char& c : kalan) if (c == '\\') c = '/'; if (kalan.rfind("https://", 0) == 0) { u.https = true; kalan.erase(0, 8); } else if (kalan.rfind("http://", 0) == 0) { u.https = false; kalan.erase(0, 7); } if (kalan.empty()) return false; std::string yetki = kalan, yol = "/"; if (const std::size_t egik = kalan.find('/'); egik != std::string::npos) { yetki = kalan.substr(0, egik); yol = kalan.substr(egik); } if (yetki.empty()) return false; std::string host = yetki; if (!yetki.empty() && yetki.front() == '[') { // [IPv6]:port const std::size_t kapa = yetki.find(']'); if (kapa == std::string::npos) return false; host = yetki.substr(1, kapa - 1); if (kapa + 1 < yetki.size() && yetki[kapa + 1] == ':') u.port = (u16)std::atoi(yetki.c_str() + kapa + 2); } else if (const std::size_t iki = yetki.rfind(':'); iki != std::string::npos) { host = yetki.substr(0, iki); u.port = (u16)std::atoi(yetki.c_str() + iki + 1); } if (host.empty() || u.port == 0) return false; u.host = host; u.yol = yol; return true; } // WinHTTP GET → gövde metni (guncelleme_kontrol.cpp ile aynı desen). bool httpGetMetin(const UrlParca& u, std::string& out, std::string& hata) { out.clear(); const std::wstring whost(u.host.begin(), u.host.end()); const std::wstring wyol(u.yol.begin(), u.yol.end()); HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansPolitika/1.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { hata = "WinHttpOpen başarısız"; return false; } WinHttpSetTimeouts(oturum, 8000, 8000, 12000, 12000); HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), u.port, 0); if (!baglanti) { hata = "sunucuya bağlanılamadı"; WinHttpCloseHandle(oturum); return false; } HINTERNET istek = WinHttpOpenRequest(baglanti, L"GET", wyol.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (!istek) { hata = "istek oluşturulamadı"; WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return false; } bool ok = false; if (WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(istek, nullptr)) { DWORD kod = 0, boyut = sizeof(kod); WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX); if (kod == 200) { DWORD var = 0; do { var = 0; if (!WinHttpQueryDataAvailable(istek, &var)) break; if (var == 0) break; if (out.size() + var > 4u * 1024 * 1024) { hata = "politika çok büyük"; break; } std::string parca(var, '\0'); DWORD okunan = 0; if (!WinHttpReadData(istek, parca.data(), var, &okunan)) break; parca.resize(okunan); out += parca; ok = true; } while (var > 0); if (out.empty()) hata = "boş yanıt"; } else { hata = "HTTP durum kodu " + std::to_string(kod); } } else { hata = "istek gönderilemedi (ağ erişilemiyor)"; } WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return ok && !out.empty(); } void denetleIc(); // Arka plan iş parçacığı: indir → doğrula → geri sarma kontrolü → önbelleğe yaz → kabul. void denetimIsParcacigi(std::string url) { UrlParca u; std::string hata; do { if (!urlCoz(url, u)) { hata = "geçersiz URL: " + url; break; } std::string metin; if (!httpGetMetin(u, metin, hata)) break; UzakPolitika yeni; std::string cozHata; const UzakPolitikaDurum d = uzakPolitikaCoz(metin, lisansAcikAnahtarHex(), yeni, cozHata); if (d != UzakPolitikaDurum::Gecerli) { hata = cozHata; break; } // GERİ SARMA KORUMASI: eski sürümlü politika kabul edilmez (saldırgan sunucuyu // ele geçirse bile engellenmiş bir seriyi eski "açık" politikayla diriltemez). { std::lock_guard lk(g_mtx); if (g_politika && yeni.surum < g_politika->surum) { hata = "sürüm geri sarıldı (" + std::to_string(yeni.surum) + " < " + std::to_string(g_politika->surum) + ") — reddedildi"; break; } } if (!dosyaYazAtomik(onbellekYolu(), metin)) gunluk::uyari("Uzak politika önbelleği yazılamadı: {}", onbellekYolu().string()); { std::lock_guard lk(g_mtx); g_politika = std::make_shared(std::move(yeni)); g_kaynak = "ağ"; g_hata.clear(); g_sonBasari = (i64)std::time(nullptr); } gunluk::bilgi("Uzak lisans politikası güncellendi (ağ)"); g_yenidenDegerlendir = true; // pompa UI iş parçacığında lisansı yeniden yükler hata.clear(); } while (false); if (!hata.empty()) { std::lock_guard lk(g_mtx); g_hata = hata; gunluk::uyari("Uzak politika denetimi başarısız: {}", hata); } g_denetimSuruyor = false; } void denetleIc() { std::string url = ayarOku(UZAK_URL_AYAR, UZAK_URL_VARSAYILAN); // Boşlukları kırp (elle yapıştırınca gelen satır sonu vs.) std::string kirpilmis; for (char c : url) if (!std::isspace((unsigned char)c)) kirpilmis.push_back(c); { std::lock_guard lk(g_mtx); g_sonDenetleme = (i64)std::time(nullptr); // başlangıçta damgala: spam koruması if (kirpilmis.empty()) { g_hata.clear(); // devre dışı — hata DEĞİL return; } } if (g_denetimSuruyor.exchange(true)) return; // zaten sürüyor std::thread(denetimIsParcacigi, kirpilmis).detach(); } } // namespace void uzakPolitikaBaslat() { static const bool birKez = [] { // 1) Önbelleği SENKRON yükle: ilk lisans yüklemesi ENGELLEME kararını görsün. const std::string metin = dosyaOku(onbellekYolu()); if (!metin.empty()) { UzakPolitika p; std::string hata; if (uzakPolitikaCoz(metin, lisansAcikAnahtarHex(), p, hata) == UzakPolitikaDurum::Gecerli) { std::lock_guard lk(g_mtx); g_politika = std::make_shared(std::move(p)); g_kaynak = "önbellek"; gunluk::bilgi("Uzak lisans politikası önbellekten yüklendi"); } else { gunluk::uyari("Uzak politika önbelleği geçersiz ({}): {}", hata, onbellekYolu().string()); } } // 2) Arka plan tazeleme (URL boşsa no-op). denetleIc(); return true; }(); (void)birKez; } void uzakPolitikaPompa() { uzakPolitikaBaslat(); // statik init: sonraki çağrılar no-op // Yeni politika VEYA canlı WS kararı geldi → lisansı UI iş parçacığında yeniden // değerlendir (LisansSistemi tek iş parçacığı sözleşmelidir; ağ iş parçacığı DOKUNMAZ). const bool politikaTaze = g_yenidenDegerlendir.exchange(false); const bool canliTaze = uzakCanliTazeKontrol(); // "Bağlantı ZORUNLU" kilidi: kanal bağlı/kopuk durumu değişmese bile eşik // süresi her an dolabilir — bu yüzden HER pompa turunda kontrol edilir (durum // FLIP olunca bir kez lisansYukleVeUygula çağrılır, spam olmasın diye). static bool oncekiZorunluIhlal = false; const bool zorunluIhlal = uzakCanliZorunluIhlalMi(); const bool zorunluDegisti = zorunluIhlal != oncekiZorunluIhlal; oncekiZorunluIhlal = zorunluIhlal; if (politikaTaze || canliTaze || zorunluDegisti) { gunluk::bilgi( "Uzak karar değişti (politika={} canlı={} zorunluIhlal={}) — lisans yeniden " "değerlendiriliyor", politikaTaze, canliTaze, zorunluIhlal); lisansYukleVeUygula(lisansSistemi()); } // Periyodik sessiz yeniden denetim. i64 son = 0; { std::lock_guard lk(g_mtx); son = g_sonDenetleme; } std::string url = ayarOku(UZAK_URL_AYAR, UZAK_URL_VARSAYILAN); bool bos = true; for (char c : url) if (!std::isspace((unsigned char)c)) { bos = false; break; } if (!bos && (i64)std::time(nullptr) - son > DENETIM_ARALIGI) denetleIc(); } std::shared_ptr uzakPolitikaAktif() { std::lock_guard lk(g_mtx); return g_politika; } bool uzakSeriEngelliMi(const std::string& seri) { std::lock_guard lk(g_mtx); return uzakPolitikaKarar(g_politika.get(), seri) == UzakKarar::Engelli; } bool uzakSeriOnayliMi(const std::string& seri) { std::lock_guard lk(g_mtx); return uzakPolitikaKarar(g_politika.get(), seri) == UzakKarar::Izinli; } std::string uzakPolitikaOzet() { std::lock_guard lk(g_mtx); if (!g_politika) { if (!g_hata.empty()) return "Uzak politika: yok (denetim hatası: " + g_hata + ")"; return "Uzak politika: yok (henüz denetlenmedi)"; } std::string s = "Uzak politika: v" + std::to_string(g_politika->surum) + ", "; s += g_politika->mod == UzakMod::EngelliListesi ? "engelli listesi" : "izin listesi"; s += " (" + std::to_string(g_politika->engelliler.size() + g_politika->izinliler.size()) + " kayıt)"; if (!g_hata.empty()) s += " — son hata: " + g_hata; return s; } void uzakPolitikaAyarBolumu() { ImGui::SeparatorText("Uzaktan lisans kontrolü"); std::shared_ptr p = uzakPolitikaAktif(); i64 sonDenetleme = 0, sonBasari = 0; std::string kaynak, hata; { std::lock_guard lk(g_mtx); sonDenetleme = g_sonDenetleme; sonBasari = g_sonBasari; kaynak = g_kaynak; hata = g_hata; } if (p) { ImGui::TextColored(ImVec4(0.35f, 0.85f, 0.45f, 1), "Aktif: v%llu (%s)", (unsigned long long)p->surum, p->mod == UzakMod::EngelliListesi ? "engelli listesi" : "izin listesi"); ImGui::TextDisabled("Kaynak: %s · Son başarılı: %s", kaynak.c_str(), tarihMetni(sonBasari).c_str()); if (!p->engelliler.empty() || !p->izinliler.empty()) { if (ImGui::TreeNode("uzakListe", "Liste (%d kayıt)", (int)(p->engelliler.size() + p->izinliler.size()))) { for (const std::string& s : p->engelliler) ImGui::BulletText("engelli: %s", s.c_str()); for (const std::string& s : p->izinliler) ImGui::BulletText("izinli: %s", s.c_str()); if (!p->mesaj.empty()) ImGui::TextWrapped("Mesaj: %s", p->mesaj.c_str()); ImGui::TreePop(); } } } else { ImGui::TextDisabled("Aktif politika yok%s", hata.empty() ? "" : (std::string(" — hata: ") + hata).c_str()); } ImGui::TextDisabled("Son denetleme: %s", tarihMetni(sonDenetleme).c_str()); // URL ayarı (satıcı/dağıtıcı değiştirir; müşteri değiştirse bile imza olmadan // sahte politika kabul edilmez). static char buf[512] = ""; static bool bufDolu = false; if (!bufDolu) { std::string u = ayarOku(UZAK_URL_AYAR, UZAK_URL_VARSAYILAN); snprintf(buf, sizeof(buf), "%s", u.c_str()); bufDolu = true; } ImGui::InputText("##uzakurl", buf, sizeof(buf)); if (ImGui::IsItemHovered()) ImGui::SetTooltip("İmzalı politika dosyasının adresi.\nÖrn: https://ornek.com/fiko/politika.txt\nBoş = uzaktan kontrol kapalı."); ImGui::SameLine(); if (ImGui::Button("URL'yi uygula")) { ayarYaz(UZAK_URL_AYAR, buf); denetleIc(); } ImGui::SameLine(); if (ImGui::Button("Şimdi denetle")) { ayarYaz(UZAK_URL_AYAR, buf); denetleIc(); } // Canlı WebSocket kanalı: bağlantı durumu + sunucu URL'si + elle bağlanma. ImGui::Spacing(); uzakCanliAyarBolumu(); ImGui::TextDisabled("Denetim açılışta ve 6 saatte bir arka planda yapılır;\n" "son bilinen karar çevrimdışıyken de geçerlidir."); } void uzakPolitikaDenetle() { denetleIc(); } } // namespace fiko