// -- Uzak lisans BILETI cekme (kokDB .aggregate) ---------------------------------- // Sunucudan imzali bileti alir (periyodik), dogrular ve uygular. // Kopma suresi de biletten gelir - sunucu belirler. // // Bu, .fkl dosya modelinin YERINI alir: musteri makinesinde dosya yok, // yetkiler sunucudan gelir. Yan yol olarak .fkl de varsa birlestirilir // (LisansSistemi::birlestir semantigi: "genis kazanir"). #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #include #include #include #include "lisans_uzaktan.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/kripto.hpp" #include "fiko/cekirdek/lisans.hpp" #include "fiko/cekirdek/lisans_dosya.hpp" #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko/core/gunluk.hpp" #include "ayar_kalici.hpp" #include "lisans_yukle.hpp" namespace fiko { namespace { constexpr char TIRNAK = 34; constexpr char TERS = 92; constexpr char NL = 10; std::mutex g_mtx; // Sunucudan gelen kopma suresi (bilet uzerinden) int g_kopmaSuresi = 30; int g_uyari1 = 5; int g_uyari2 = 15; i64 g_sonBiletZamani = 0; std::string g_sonHata; std::string g_sunucu; std::string kacisla(const std::string& s) { std::string o; for (char c : s) { if (c == TIRNAK || c == TERS) { o += TERS; o += c; } else if (c == NL) o += "\\n"; else if ((unsigned char)c < 0x20) o += ' '; else o += c; } return o; } // JSON dizge alani ("anahtar":"deger") std::string alan(const std::string& j, const char* a) { const std::string pat = "\x22" + std::string(a) + "\x22"; size_t i = j.find(pat); if (i == std::string::npos) return ""; i += pat.size(); while (i < j.size() && (j[i] == ' ' || j[i] == ':')) ++i; if (i >= j.size() || j[i] != TIRNAK) return ""; ++i; std::string r; while (i < j.size() && j[i] != TIRNAK) { if (j[i] == TERS && i + 1 < j.size()) { ++i; if (j[i] == 'n') r += NL; else if (j[i] == 't') r += '\t'; else r += j[i]; } else r += j[i]; ++i; } return r; } long long alanSayi(const std::string& j, const char* a) { const std::string pat = "\x22" + std::string(a) + "\x22"; size_t i = j.find(pat); if (i == std::string::npos) return 0; i += pat.size(); while (i < j.size() && (j[i] == ' ' || j[i] == ':')) ++i; bool neg = false; if (i < j.size() && j[i] == '-') { neg = true; ++i; } long long v = 0; bool rakam = false; while (i < j.size() && j[i] >= '0' && j[i] <= '9') { v = v * 10 + (j[i] - '0'); ++i; rakam = true; } return rakam ? (neg ? -v : v) : 0; } struct UrlParca { bool https = true; int port = 443; std::string host, yol; }; bool urlCoz(const std::string& url, UrlParca& u) { std::string kalan = url; for (char& c : kalan) if (c == TERS) 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 = "/"; const size_t e = kalan.find('/'); if (e != std::string::npos) { yetki = kalan.substr(0, e); yol = kalan.substr(e); } std::string host = yetki; const size_t p = yetki.rfind(':'); if (p != std::string::npos) { host = yetki.substr(0, p); u.port = std::atoi(yetki.c_str() + p + 1); } if (host.empty()) return false; u.host = host; u.yol = yol; if (u.port <= 0) u.port = u.https ? 443 : 80; return true; } } // namespace // ── Sunucudan bilet cek (aggregate) ─────────────────────────────────────────── bool uzakLisansBiletiCekVeUygula(Cekirdek& cekirdek, std::string& hata) { (void)cekirdek; std::string url = ayarOku("lisans_bilet_url", ""); std::string temiz; for (char c : url) if (!std::isspace((unsigned char)c)) temiz += c; if (temiz.empty()) temiz = "https://fikoyt.com:9000"; g_sunucu = temiz; UrlParca u; if (!urlCoz(temiz, u)) { hata = "gecersiz sunucu adresi"; return false; } const std::string cihaz = cihazKimlikUret(); if (cihaz.empty()) { hata = "cihaz kimligi uretilemedi"; return false; } HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansBilet/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { hata = "WinHttpOpen basarisiz"; return false; } WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE); // ⚠️ std::wstring(...) geçici nesnesi yerine sabit tampon: geçici nesnenin // yaşam süresi belirsiz olduğu için 0xC0000409 fast-fail üretiyordu. wchar_t wbuf[256] = {0}; { const size_t n = u.host.size() < 255 ? u.host.size() : 255; for (size_t i = 0; i < n; ++i) wbuf[i] = (wchar_t)(unsigned char)u.host[i]; wbuf[n] = 0; } HINTERNET baglanti = WinHttpConnect(oturum, wbuf, (INTERNET_PORT)u.port, 0); if (!baglanti) { WinHttpCloseHandle(oturum); hata = "baglanti hatasi"; return false; } // $match: kendi cihazim + aktif bilet const std::string govde = "{\"aggregate\":[{\"$match\":{\"cihaz\":\"" + kacisla(cihaz) + "\",\"aktif\":true}},{\"$limit\":1}]}"; const std::wstring yol = L"/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadLisanslari.aggregate"; HINTERNET istek = WinHttpOpenRequest(baglanti, L"POST", yol.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (!istek) { WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); hata = "istek acilamadi"; return false; } // Sertifika dogrulama (gizli sunucu adresi; bilet ECDSA ile dogrulanir). if (u.https) { DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec)); } BOOL ok = WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)govde.data(), (DWORD)govde.size(), (DWORD)govde.size(), 0); if (ok) ok = WinHttpReceiveResponse(istek, nullptr); if (!ok) { WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); hata = "bilet istegi basarisiz"; return false; } DWORD kod = 0, bs = sizeof(kod); WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &bs, WINHTTP_NO_HEADER_INDEX); std::string cevap; if (kod == 200) { char tampon[8192]; DWORD okunan = 0; while (WinHttpReadData(istek, tampon, sizeof(tampon), &okunan) && okunan > 0) cevap.append(tampon, okunan); } WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); if (kod == 401) { hata = "oturum gerekli (billet sunucusu korumali)"; return false; } if (kod == 403) { hata = "erisim reddedildi"; return false; } if (kod != 200) { hata = "bilet HTTP " + std::to_string(kod); return false; } if (cevap.empty() || cevap.find("\"govde\"") == std::string::npos) { hata.clear(); // bilet yok = sorun degil (yerel .fkl kullanilir) return true; } // ── Govde + imzayi ayikla ve DOGRULA ─────────────────────────────────────── const std::string govdeMetin = alan(cevap, "govde"); const std::string imza = alan(cevap, "imza"); if (govdeMetin.empty() || imza.empty()) { hata = "bilet eksik alanlar"; return false; } // Kanonik govde: normalize et. // ⚠️ ÖNCEKİ HATA: "belge=bilet" satırı KANONİK METİNDEN ÇIKARILIYORDU. // Üretici bu satırı imzaya DAHİL ediyor → istemci farklı metni // doğruladığı için GEÇERLİ bilet "imza gecersiz" diye reddediliyordu. // Düzeltme: belge= satırı da dahil. Kural: sunucunun imzaladığı metnin // BİREBİR aynısı doğrulanmalı; yorum (#) satırları ve boş satırlar // imzaya girmiyorsa burada da düşülür. std::string kanonik; { std::size_t i = 0; while (i <= govdeMetin.size()) { std::size_t son = govdeMetin.find('\n', i); const bool sonSatir = (son == std::string::npos); if (sonSatir) son = govdeMetin.size(); std::string s = govdeMetin.substr(i, son - i); while (!s.empty() && (s.back() == '\r' || s.back() == ' ')) s.pop_back(); std::size_t a = 0; while (a < s.size() && (s[a] == ' ' || s[a] == '\t')) ++a; s = s.substr(a); // Yorum ve boş satırlar atlanır; "belge=" DAHİL EDİLİR. if (!s.empty() && s[0] != '#') { const size_t esit = s.find('='); if (esit != std::string::npos) kanonik += s + "\n"; } if (sonSatir) break; i = son + 1; } } if (!kripto::imzaDogrula(kanonik, imza, lisansAcikAnahtarHex())) { hata = "bilet imzasi gecersiz - reddedildi"; gunluk::uyari("Uzak bilet REDDEDILDI: imza gecersiz ({} bayt kanonik)", kanonik.size()); return false; } // ── Kopma suresini al (sunucu belirler) ──────────────────────────────────── const long long ks = alanSayi(cevap, "kopmaSuresi"); const long long u1 = alanSayi(cevap, "uyari1"); const long long u2 = alanSayi(cevap, "uyari2"); { std::lock_guard lk(g_mtx); if (ks >= 15 && ks <= 600) g_kopmaSuresi = (int)ks; if (u1 >= 0) g_uyari1 = (int)u1; if (u2 > 0) g_uyari2 = (int)u2; g_sonBiletZamani = (i64)std::time(nullptr); g_sonHata.clear(); } gunluk::bilgi("Uzak bilet dogrulandi (kopma suresi {} sn)", g_kopmaSuresi); return true; } } // namespace fiko