#include "lisans_canli.hpp" #include #include #include #include #include #include #include #include #include #include #include "ayar_kalici.hpp" // ayarOku/ayarYaz (URL) #include "fiko/cekirdek/lisans_uzak.hpp" // canliKararCoz (imzalı karar doğrulama) #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko/cekirdek/surum.hpp" // SURUM_METIN #include "fiko/core/gunluk.hpp" #include "imgui.h" #include "lisans_uzaktan.hpp" // lisansTlsSunucuDogrula (TLS parmak izi sabitleme) #include "lisans_yukle.hpp" // lisansAcikAnahtarHex, lisansOturum #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include namespace fiko { namespace { // Karakter sabitleri (kaçışsız): JSON üretimi/çözümlemesi ASCII kodlarıyla. constexpr char NL = static_cast(10); constexpr char TAB = static_cast(9); constexpr char TERS = static_cast(92); // JSON kaçış işareti constexpr char TIRNAK = static_cast(34); constexpr const char* CANLI_URL_AYAR = "lisans_canli_url"; constexpr DWORD YENIDEN_BAGLANMA_MIN_MS = 5000; // kopunca ilk deneme constexpr DWORD YENIDEN_BAGLANMA_MAKS_MS = 60000; // tavana büyür constexpr DWORD PING_ARALIGI_SN = 30; // NAT canlı tutma // ── Durum ──────────────────────────────────────────────────────────────────── enum class KanalDurum : u8 { Kapali = 0, Baglaniyor, Bagli, Kopuk }; std::mutex g_mtx; std::map g_kararlar; // seri → son imzalı karar KanalDurum g_durum = KanalDurum::Kapali; std::string g_hata; // son bağlantı hatası i64 g_sonKararZaman = 0; // son kabul edilen kararın zamanı int g_kararSayisi = 0; // bu oturumda kabul edilen karar sayısı std::string g_sunucuAdi; // bağlı host (özet için) // Kapanış güvenliği: iş parçacığı detached, statik nesneler (g_mtx, g_hata, // g_durum) program kapanırında yok edilir. Thread durmazsa yarış durumu // oluşur → 0xC0000409. Ölçüldü: "Eklenti kaldırıldı" logundan sonra fast-fail. std::atomic g_durduruldu{false}; std::mutex g_durmus_mtx; std::condition_variable g_durmus_cv; bool g_durdu = true; // Ping iş parçacığının durumu — DOSYA KAPSAMINDA olmalıdır. // ⚠️ ÖNCEKİ ÇÖKME: bunlar wsOturumu'nun YEREL değişkenleriydi ve ping // iş parçacığı [&] ile yakalıyordu. wsOturumu döndüğünde yığın çerçevesi // yok edilir, iş parçacığı hâlâ erişir → dangling ref → 0xDEAD0002. std::atomic g_pingDur{false}; std::mutex g_pingMtx; std::condition_variable g_pingCv; // "Bağlantı ZORUNLU" kilidi için: izleme ne zaman başladı + en son ne zaman // gerçekten BAĞLI idik (0 = hiç bağlanmadık). uzakCanliZorunluIhlalMi() bunları // kullanır — g_mtx ile korunur (diğer durum alanlarıyla aynı kilit). i64 g_izlemeBaslangic = 0; i64 g_sonBagliZaman = 0; std::atomic g_isParcacigiSuruyor{false}; std::atomic g_taze{false}; // yeni karar → pompa lisansı yeniden yükler std::atomic g_yenidenBaglanIste{false}; // "Şimdi bağlan" düğmesi std::string urlOku() { std::string url = ayarOku(CANLI_URL_AYAR, ""); std::string kirp; for (char c : url) if (!std::isspace((unsigned char)c)) kirp.push_back(c); if (kirp.empty()) return "https://fikoyt.com:9000/isans.socket.io/"; return kirp; } // ── URL: [http://|https://]host[:port]/yol ──────────────────────────────────── // Yeni format: Socket.IO v3 endpoint'i (engine.io v3). Örn: // https://fikoyt.com/isans.socket.io/ (eski wss://.../lisans-ws formatı artık yok) 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 == static_cast(92)) c = '/'; // ters bölü → eğik (yanlışlıkla) 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; } // ── JSON üretimi (değerler kaçışlanır — kaynak içinde kaçış dizisi YOK) ────── // Alanlar arasina virgul: onceki alan varsa (son karakter '{' veya '[' degilse) ekle. // (Eskiden virgul YOKTU → {"tip":"merhaba""seri":..} gecersiz JSON → socket.io-parser // "parse error" ile sunucu istemciyi aninda atiyordu, ws rc=12030.) inline void jsonVirgulGerekirse(std::string& j) { if (!j.empty() && j.back() != '{' && j.back() != '[') j += ','; } void jsonDizgeEkle(std::string& j, const char* anahtar, const std::string& deger) { jsonVirgulGerekirse(j); j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; j += TIRNAK; for (char c : deger) { if (c == TIRNAK || c == TERS) { j += TERS; // kaçış işareti j += c; // ardından kendisi (" veya \) } else if (c == NL) { j += TERS; j += 'n'; } else if (c == TAB) { j += TERS; j += 't'; } else if ((unsigned char)c < 0x20) { // diğer kontrol karakterleri → boşluk (mesaj/bilgisayar adı temizliği) j += ' '; } else { j += c; } } j += TIRNAK; } void jsonSayiEkle(std::string& j, const char* anahtar, long long deger) { jsonVirgulGerekirse(j); j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; j += std::to_string(deger); } // Alt nesne/dizi açıcılar: önceki alanla araya virgül koyar (JSON geçerliliği). void jsonNesneAc(std::string& j, const char* anahtar) { j += ','; j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; } void jsonDiziAc(std::string& j, const char* anahtar) { jsonNesneAc(j, anahtar); j += '['; } // Merhaba mesajı: sunucu panosunda satır olarak görünecek her şey. std::string merhabaMesajiUret() { const LisansOturum& o = lisansOturum(); std::string j = "{"; jsonDizgeEkle(j, "tip", "merhaba"); jsonDizgeEkle(j, "seri", o.bilgi.seri); jsonDizgeEkle(j, "musteri", o.bilgi.musteri); if (const char* cn = std::getenv("COMPUTERNAME"); cn && *cn) jsonDizgeEkle(j, "bilgisayar", cn); jsonDizgeEkle(j, "surum", SURUM_METIN ? SURUM_METIN : ""); jsonSayiEkle(j, "pid", (long long)GetCurrentProcessId()); jsonDiziAc(j, "makine"); bool ilk = true; for (const std::string& h : makineParmakIzi().hashListesi()) { if (!ilk) j += ','; ilk = false; j += TIRNAK; for (char c : h) { if (c == TIRNAK || c == TERS || c == NL) { j += TERS; j += 'n'; } else { j += c; } } j += TIRNAK; } j += "]}"; return j; } // ── Karar uygulama ─────────────────────────────────────────────────────────── void kararUygula(const CanliKarar& k) { { std::lock_guard lk(g_mtx); g_kararlar[k.kimlik] = k; ++g_kararSayisi; g_sonKararZaman = k.zaman; } gunluk::bilgi("Canlı lisans kararı: {} → {} ({})", k.kimlik, k.engelli ? "ENGELLI" : "izinli", k.mesaj); g_taze = true; // pompa UI iş parçacığında lisansı yeniden yükler } // ── Socket.IO v3 transport (engine.io v3, websocket) ───────────────────────── // engine.io v3 wire formatı: // - Socket.io EVENT packet: 2[/isans,eventname,{"args"}] // - websocket frame'i = socket.io EVENT packet (length prefix YOK) // - polling handshake: GET /isans.socket.io/?EIO=3&transport=polling → sid // - websocket yükseltme: GET /isans.socket.io/?EIO=3&transport=websocket&sid=... // // Sunucu (/isans namespace'i) merhaba olayını bekler, lisans-karar ile yansır. // Transport: yalnızca websocket. // UTF-16 kod birimi sayısı (ASCII için byte sayısı; unicode için UTF-16 kod birimi). size_t utf16KarakterSayisi(const std::string& s) { size_t kodBirimi = 0; for (size_t i = 0; i < s.size();) { unsigned char c = static_cast(s[i]); size_t adim; if (c < 0x80) adim = 1; else if ((c >> 5) == 0x6) adim = 2; else if ((c >> 4) == 0xE) adim = 3; else adim = 4; i += adim; if (i > s.size()) break; // surrogate çifti (U+10000+) → UTF-16'da 2 kod birimi if (adim == 4 && i + 1 <= s.size()) kodBirimi += 2; else kodBirimi += 1; } return kodBirimi; } // Socket.io EVENT packet'ını engine.io v3 payload'a sar. // event = ad, argsJson = tek nesne argüman (JSON). Nsp = /isans. // Socket.IO 2.x websocket cerceveleri (engine.io v3): uzunluk oneki YOK. // olay: 42/isans,["olay",{...}] (4=engine.io message, 2=socket.io EVENT) std::string socketioEventUret(const std::string& event, const std::string& argsJson) { std::string p = "42/isans,["; p += TIRNAK; p += event; p += TIRNAK; p += ','; p += argsJson; p += ']'; return p; } // "42/isans,[\"olay\",{...}]" (veya namespace'siz "42[...]") -> olay adi + arguman JSON'u. bool engineioPayloadCoz(const std::string& frame, std::string& outEvent, std::string& outJson) { if (frame.size() < 4 || frame[0] != '4' || frame[1] != '2') return false; size_t i = 2; if (frame[i] == '/') { const size_t virgul = frame.find(',', i); if (virgul == std::string::npos) return false; i = virgul + 1; } if (i >= frame.size() || frame[i] != '[') return false; ++i; if (i >= frame.size() || frame[i] != TIRNAK) return false; const size_t adBit = frame.find(TIRNAK, i + 1); if (adBit == std::string::npos) return false; outEvent = frame.substr(i + 1, adBit - i - 1); const size_t virg2 = frame.find(',', adBit); if (virg2 == std::string::npos) { outJson.clear(); return true; } const size_t son = frame.find_last_of(']'); if (son == std::string::npos || son <= virg2) return false; outJson = frame.substr(virg2 + 1, son - virg2 - 1); return true; } // JSON sayısal alan ("anahtar":123). bool jsonSayiAl(std::string_view j, const char* anahtar, long long& out) { std::string pat; pat += TIRNAK; pat += anahtar; pat += TIRNAK; const std::size_t p = j.find(pat); if (p == std::string_view::npos) return false; std::size_t i = p + pat.size(); while (i < j.size() && (j[i] == ' ' || j[i] == TAB)) ++i; if (i >= j.size() || j[i] != ':') return false; ++i; while (i < j.size() && (j[i] == ' ' || j[i] == TAB)) ++i; bool neg = false; if (i < j.size() && j[i] == '-') { neg = true; ++i; } if (i >= j.size() || j[i] < '0' || j[i] > '9') return false; long long v = 0; while (i < j.size() && j[i] >= '0' && j[i] <= '9') { v = v * 10 + (j[i] - '0'); ++i; } out = neg ? -v : v; return true; } // HTTP long-polling ile Socket.IO handshake'i başlat → sid + ping parametreleri. // Dönüş: true başarılı. [[maybe_unused]] bool socketioBaslat(const UrlParca& u, std::string& sid, DWORD& pingIntervalMs) { const std::wstring whost(u.host.begin(), u.host.end()); HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) return false; WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE); HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), u.port, 0); if (!baglanti) { WinHttpCloseHandle(oturum); return false; } gunluk::uyari("lisans: connect OK ({}:{})", u.host, u.port); // GET /isans.socket.io/?EIO=3&transport=polling std::string yolQuery = u.yol + "?EIO=3&transport=polling"; const std::wstring wpath(yolQuery.begin(), yolQuery.end()); // ⚠️ WinHttpOpenRequest BAGLANTI handle'i ister (oturum degil). // Oturum handle'i verilince 12018 (SECURE_FAILURE) donuyordu ve // 'baglanti' olusturulup bir satir sonra kapatiliyordu. HINTERNET istek = WinHttpOpenRequest( baglanti, L"GET", wpath.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (!istek) { DWORD err = GetLastError(); gunluk::uyari("lisans: OpenRequest FAIL, WinHTTP error {}", err); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return false; } // Aynı sertifika politikası (gizli üretici adresi) — yoksa 12018 dönüyor. 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)); } DWORD kod = 0, boyut = sizeof(kod); if (!WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !WinHttpReceiveResponse(istek, nullptr)) { DWORD err = GetLastError(); gunluk::uyari("lisans: SendRequest/ReceiveResponse FAIL, WinHTTP error {}", err); WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return false; } WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX); if (kod != 200) { gunluk::uyari("lisans: polling HTTP {}", kod); WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return false; } // Yanıt: {"upgrades":[...],"pingInterval":25000,"pingTimeout":20000,"sid":"..."} std::string yanit; DWORD okunan = 0, toplam = 0; char tampon[2048]; do { okunan = 0; if (!WinHttpReadData(istek, tampon, sizeof(tampon), &okunan) || okunan == 0) break; yanit.append(tampon, okunan); toplam += okunan; } while (toplam < sizeof(tampon) * 8); WinHttpCloseHandle(istek); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); // sid çıkar const size_t sidPos = yanit.find("\"sid\""); if (sidPos == std::string::npos) { gunluk::uyari("lisans: sid bulunamadi (HTTP {}, body {} byte)", kod, yanit.size()); return false; } size_t n = yanit.find(':', sidPos); if (n == std::string::npos) return false; ++n; while (n < yanit.size() && (yanit[n] == ' ' || yanit[n] == TAB)) ++n; if (n >= yanit.size() || yanit[n] != TIRNAK) return false; ++n; size_t bit = n; while (bit < yanit.size() && yanit[bit] != TIRNAK) ++bit; if (bit == n) return false; sid = yanit.substr(n, bit - n); if (sid.empty()) return false; // pingInterval çıkar (opsiyonel) pingIntervalMs = 25000; const size_t pingPos = yanit.find("\"pingInterval\""); if (pingPos != std::string::npos) { long long val = 0; if (jsonSayiAl(yanit, "pingInterval", val)) pingIntervalMs = static_cast(val); } return true; } // Socket.IO ile bağlan → merhaba gönder → lisans-karar dinle. // Transport: yalnızca websocket. // Dönüş: ne kadar sonra yeniden bağlanma denenecek (ms). // Socket.IO 2.x / engine.io v3, YALNIZ websocket (sunucu: transports:['websocket']). // Akis (uzunluk oneki YOK, her websocket frame'i bir engine.io paketi): // GET ?EIO=3&transport=websocket (sid YOK) -> 101 // S->C 0{"sid":..,"pingInterval":25000,..} engine.io open // S->C 40 varsayilan namespace (yok say) // C->S 40/isans /isans namespace'ine baglan // S->C 40/isans onay -> merhaba gonder // C->S 42/isans,["merhaba",{...}] // S->C 42/isans,["lisans-karar",{...}] // C->S 2 (ping) S->C 3 (pong) | S->C 2 -> C->S 3 DWORD wsOturumu(const UrlParca& u) { HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { std::lock_guard lk(g_mtx); g_hata = "WinHttpOpen basarisiz"; return YENIDEN_BAGLANMA_MIN_MS; } // resolve, connect, send, receive(SONSUZ: websocket okuma bloklayici) WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE); // std::wstring gecici nesnesi yerine sabit tampon (host ASCII varsayimi). 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) { std::lock_guard lk(g_mtx); g_hata = "HTTP baglantisi kurulamadi"; WinHttpCloseHandle(oturum); return YENIDEN_BAGLANMA_MIN_MS; } gunluk::uyari("lisans: websocket dogrudan baglaniliyor ({}:{})", u.host, (int)u.port); HINTERNET ws = nullptr; { const std::string wsPath = u.yol + "?EIO=3&transport=websocket"; const std::wstring wpath(wsPath.begin(), wsPath.end()); // ayni string: begin/end guvenli HINTERNET istek = WinHttpOpenRequest(baglanti, L"GET", wpath.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (istek) { // TLS: Windows CA/CN/tarih dogrulamasi ACIK (gevsetme bayragi YOK); ek olarak sunucu // sertifika parmak izi ReceiveResponse sonrasi lisansTlsSunucuDogrula ile sabitlenir. // UPGRADE_TO_WEB_SOCKET tampon ALMAZ: (nullptr, 0) if (WinHttpSetOption(istek, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0)) { if (WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(istek, nullptr)) { const bool tlsTamam = !u.https || lisansTlsSunucuDogrula(istek); if (!tlsTamam) gunluk::uyari("lisans: TLS sunucu dogrulamasi basarisiz - baglanti reddedildi"); DWORD kod = 0, boyut = sizeof(kod); if (tlsTamam) WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX); if (kod == 101) { // ⚠️ ÖNCEKİ ÇÖKME (0xDEAD0002): Başarılı yükseltmeden sonra // WinHttpCloseHandle(istek) çağrılıyordu. Bu handle, WebSocket // handle'ının dayandığı YÜKLEME (load) yapısını paylaşır; // kapatılınca ws geçersizleşir ve sonraki WinHttpWebSocketReceive // çağrısı stack cookie ihlali (0xDEAD0002) üretir. // DÜZELTME: istek handle'ı yalnızca BAŞARISIZ yükseltmede kapatılır. ws = WinHttpWebSocketCompleteUpgrade(istek, 0); if (ws) { gunluk::uyari("lisans: websocket 101 OK"); // istek handle'i asagida kapatilir (MSDN deseni); baglanti+ws oturum sonuna kadar acik. } else { gunluk::uyari("lisans: websocket complete FAIL, error {}", GetLastError()); } } else { gunluk::uyari("lisans: websocket upgrade HTTP {}", kod); } } else { gunluk::uyari("lisans: websocket send/recv FAIL, error {}", GetLastError()); } } else { gunluk::uyari("lisans: websocket setoption FAIL, error {}", GetLastError()); } if (istek) WinHttpCloseHandle(istek); } } // ⚠️ DÜZELTME: baglanti handle'i BURADA kapatılmaz. WebSocket handle'ı // aynı yükleme (load) yapısını paylaşır; erken kapatma ws'yi // geçersizleştirir → sonraki Receive'de 0xDEAD0002. Oturum kapanırken // ws ve baglanti birlikte kapatılır (aşağıdaki temizlik bloğunda). if (!ws) { std::lock_guard lk(g_mtx); g_hata = "websocket'e yukseltilemedi"; WinHttpCloseHandle(oturum); return YENIDEN_BAGLANMA_MIN_MS; } // Gonderimler tek seferde tek thread: ana dongu + ping thread'i mutex ile serilesir. std::mutex gonderMtx; auto gonder = [&](const std::string& s) -> DWORD { std::lock_guard lk(gonderMtx); return WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)s.data(), (DWORD)s.size()); }; DWORD pingIntervalMs = 25000; std::thread pingIs; bool nsBagli = false; bool bitir = false; std::string birikim; // ⚠️ tampon DÖNGÜ DIŞINA alındı: for içinde tanımlı olması her turda // yeni yığın çerçevesi açıyor, ping thread'i ile yarışan erişimlerde // stack cookie ihlali (0xDEAD0002) üretiyordu. char tampon[4096]; for (; !bitir;) { DWORD okunan = 0; WINHTTP_WEB_SOCKET_BUFFER_TYPE tur = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE; const DWORD rc = WinHttpWebSocketReceive(ws, tampon, sizeof(tampon), &okunan, &tur); if (rc != NO_ERROR) { gunluk::uyari("lisans: ws okuma hatasi rc={} (12030=baglanti kesildi)", rc); std::lock_guard lk(g_mtx); g_hata = "baglanti koptu (okuma)"; break; } if (tur == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { // ⚠️ Güvenlik: close nedeni 123 baytlık tampona yazılır; sınır kontrolü // şart. Tüketilen bayt tampona sığmıyorsa GÖSTERİLMEZ (okunmamış // bellek sızıntısı ve stack cookie ihlali riski → 0xDEAD0002). USHORT kapKod = 0; char kapNeden[123] = {0}; DWORD kapNedenUz = 0; WinHttpWebSocketQueryCloseStatus(ws, &kapKod, kapNeden, sizeof(kapNeden) - 1, &kapNedenUz); const DWORD goster = (kapNedenUz < sizeof(kapNeden)) ? kapNedenUz : 0; gunluk::uyari("lisans: sunucu ws kapatti kod={} neden={}", (int)kapKod, std::string(kapNeden, goster)); std::lock_guard lk(g_mtx); g_hata = "sunucu baglantiyi kapatti"; break; } // Alınan bayt sayısı tampona sığmalı (savunma amaçlı doğrulama). if (okunan > sizeof(tampon)) { gunluk::uyari("lisans: ws okuma {} bayt > tampon {} — baglanti kesiliyor", okunan, sizeof(tampon)); break; } birikim.append(tampon, okunan); if (birikim.size() > (1u << 20)) { // 1 MB ust sinir: bellek korumasi gunluk::uyari("lisans: ws mesaji cok buyuk, baglanti kapatiliyor"); break; } if (tur != WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) continue; // parca: devam const std::string frame = std::move(birikim); birikim.clear(); if (frame.empty()) continue; switch (frame[0]) { case '0': { // engine.io open long long v = 0; if (jsonSayiAl(frame, "pingInterval", v) && v >= 1000 && v < 600000) pingIntervalMs = (DWORD)v; gunluk::uyari("lisans: engine.io open, pingInterval={}", (int)pingIntervalMs); if (gonder("40/isans") != NO_ERROR) { bitir = true; break; } if (!pingIs.joinable()) { // ⚠️ ÖNCEKİ ÇÖKME (0xDEAD0002 = stack cookie ihlali): ping iş parçacığı // "[&]" ile wsOturumu'nun YEREL değişkenlerinin TAMAMINI yakalıyordu. // Bu değişkenler (pingMtx, pingCv, pingDur, pingIs, ws, gonder...) // fonksiyon döndüğünde yok edilir; iş parçacığı hâlâ onlara // erişir → dangling reference → stack cookie ihlali. // // DÜZELTME: ping döngüsü, wsOturumu'nun YEREL DURUMUNU KULLANMAZ. // Tüm paylaşılan durum (g_mtx, g_durmu*) dosya kapsamındadır. // Yalnız process-ömrü boyunca geçerli olan ws handle'ı ve aralık // DEĞERLE yakalanır; pingMtx/pingCv/pingDur dosya kapsamındadır (g_*). // (aşağıda tanımlı) ve join edilir. const HINTERNET wsPaylas = ws; const DWORD aralikPaylas = pingIntervalMs; pingIs = std::thread([wsPaylas, aralikPaylas] { try { std::unique_lock lk(g_pingMtx); while (!g_pingDur.load() && !g_durduruldu.load()) { if (g_pingCv.wait_for(lk, std::chrono::milliseconds(aralikPaylas), [] { return g_pingDur.load() || g_durduruldu.load(); })) break; lk.unlock(); // engine.io ping frame'i: "2" const char ping = '2'; const DWORD prc = WinHttpWebSocketSend( wsPaylas, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)&ping, 1); lk.lock(); if (prc != NO_ERROR) break; } } catch (...) { } }); } break; } case '1': // engine.io close bitir = true; break; case '2': // sunucu ping -> pong if (gonder("3") != NO_ERROR) bitir = true; break; case '3': // pong break; case '4': { // engine.io message -> socket.io paketi if (frame.rfind("40/isans", 0) == 0) { // /isans namespace onayi ("40" tek basina yok sayilir) if (!nsBagli) { nsBagli = true; { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Bagli; g_hata.clear(); g_sunucuAdi = u.host; } gunluk::bilgi("Canli lisans kanali baglandi (Socket.IO): {}", u.host); if (gonder(socketioEventUret("merhaba", merhabaMesajiUret())) != NO_ERROR) { bitir = true; } else { gunluk::uyari("lisans: merhaba gonderildi"); } } } else if (frame.rfind("41/isans", 0) == 0) { // sunucu namespace'i kapatti gunluk::uyari("lisans: sunucu /isans namespace'ini kapatti"); bitir = true; } else if (frame.rfind("44", 0) == 0) { // socket.io hata paketi gunluk::uyari("lisans: socket.io hata paketi: {}", frame.substr(0, 120)); } else { std::string ev, json; if (engineioPayloadCoz(frame, ev, json) && ev == "lisans-karar") { CanliKarar k; std::string hata; const UzakPolitikaDurum d = canliKararCoz(json, lisansAcikAnahtarHex(), k, hata); if (d == UzakPolitikaDurum::Gecerli) { kararUygula(k); } else if (d == UzakPolitikaDurum::Bicim && hata.find("tip") != std::string::npos) { // ping/pong benzeri -> sessizce yok say } else { gunluk::uyari("Canli kanal mesaji reddedildi ({}): {}", hata, json.substr(0, 120)); } } } break; } default: break; } } // Temizlik: once ping thread'ini durdur ve BEKLE (ws/oturum kapanmadan once). { std::lock_guard lk(g_pingMtx); g_pingDur = true; } g_pingCv.notify_all(); if (pingIs.joinable()) pingIs.join(); // Ping thread'i bitti; artık yeni bir WS bağlantısında yeniden başlatılabilir. g_pingDur = false; // Ping thread'i bitti; artık yeni bir WS bağlantısında yeniden başlatılabilir. g_pingDur = false; WinHttpWebSocketClose(ws, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0); WinHttpCloseHandle(ws); WinHttpCloseHandle(baglanti); // ws ile birlikte, en son WinHttpCloseHandle(oturum); { std::lock_guard lk(g_mtx); if (g_durum == KanalDurum::Bagli) { g_durum = KanalDurum::Kopuk; g_sonBagliZaman = (i64)std::time(nullptr); } } gunluk::bilgi("Canli lisans kanali kapandi (Socket.IO)"); return YENIDEN_BAGLANMA_MIN_MS; } // Arka plan iş parçacığı: URL olduğu sürece bağlan/kop/yeniden bağlan. void canliIsParcacigi() { DWORD beklemeMs = 0; for (;;) { if (g_durduruldu.load()) break; // kapanış: statik nesnelere dokunma if (urlOku().empty()) { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Kapali; // URL silindi → bekle; ayar tekrar dolarsa dışarıdan yeniden başlatılır. for (int i = 0; i < 100 && urlOku().empty() && !g_durduruldu.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(200)); continue; } if (g_yenidenBaglanIste.exchange(false)) beklemeMs = 0; UrlParca u; if (!urlCoz(urlOku(), u)) { { std::lock_guard lk(g_mtx); g_hata = "geçersiz URL"; g_durum = KanalDurum::Kopuk; } std::this_thread::sleep_for(std::chrono::seconds(10)); continue; } { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Baglaniyor; } beklemeMs = wsOturumu(u); // Üstel geri çekilme: her başarısız turda büyüt, tavanla sınırla. beklemeMs = std::min(beklemeMs * 2, YENIDEN_BAGLANMA_MAKS_MS); for (DWORD bekledi = 0; bekledi < beklemeMs; bekledi += 250) { if (g_yenidenBaglanIste.load()) break; // elle yeniden bağlanma isteği if (g_durduruldu.load()) break; // kapanış std::this_thread::sleep_for(std::chrono::milliseconds(250)); } } // Kapanış/sonlandırma bildir. { std::lock_guard lk(g_durmus_mtx); g_durdu = true; } g_durmus_cv.notify_all(); } } // namespace void uzakCanliBaslat() { static const bool birKez = [] { { std::lock_guard lk(g_mtx); if (g_izlemeBaslangic == 0) g_izlemeBaslangic = (i64)std::time(nullptr); } if (urlOku().empty()) { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Kapali; return true; // kanal kapalı — iş parçacığı başlatma } if (g_isParcacigiSuruyor.exchange(true)) return true; std::thread([] { // Detached thread'de yakalanmayan istisna = std::terminate = süreç ölümü (0xC0000409). // Lisans kanalı yardımcı bir servis: hata uygulamayı düşürmemeli, yalnız loglanmalı. try { canliIsParcacigi(); } catch (const std::exception& ex) { gunluk::uyari("lisans: canli kanal istisnasi yakalandi: {}", ex.what()); } catch (...) { gunluk::uyari("lisans: canli kanal bilinmeyen istisna yakalandi"); } g_isParcacigiSuruyor = false; }).detach(); return true; }(); (void)birKez; } void uzakCanliDurdur() { // ⚠️ Kapanışta bu kanalın detached iş parçacığı statik nesnelere // (g_mtx, g_hata, g_durum) erişmeye devam ederse, statik yok etme // sırasında yarış durumu → 0xC0000409. Kurulum (uzakCanliBaslat) // sırasında çağrılmalı; sonra yeniden başlatılabilir. g_durduruldu = true; { std::lock_guard lk(g_durmus_mtx); g_durdu = true; } g_durmus_cv.notify_all(); } bool uzakCanliKararVarMi(const std::string& seri) { std::lock_guard lk(g_mtx); return g_kararlar.count(seri) != 0; } bool uzakCanliEngelliMi(const std::string& seri) { std::lock_guard lk(g_mtx); const auto it = g_kararlar.find(seri); return it != g_kararlar.end() && it->second.engelli; } std::string uzakCanliMesaj(const std::string& seri) { std::lock_guard lk(g_mtx); const auto it = g_kararlar.find(seri); return it != g_kararlar.end() ? it->second.mesaj : std::string(); } bool uzakCanliTazeKontrol() { return g_taze.exchange(false); } bool uzakCanliZorunluIhlalMi() { const bool zorunlu = ayarOku("lisans_canli_zorunlu", "0") == "1"; if (!zorunlu) return false; std::lock_guard lk(g_mtx); if (g_durum == KanalDurum::Bagli) return false; if (g_izlemeBaslangic == 0) return false; // izleme henüz başlamadı (uzakCanliBaslat çağrılmadı) const i64 simdi = (i64)std::time(nullptr); const i64 referans = g_sonBagliZaman != 0 ? g_sonBagliZaman : g_izlemeBaslangic; const int esikSn = std::max(10, std::atoi(ayarOku("lisans_canli_zorunlu_gecikme_sn", "120").c_str())); return (simdi - referans) > esikSn; } std::string uzakCanliDurumOzet() { std::lock_guard lk(g_mtx); switch (g_durum) { case KanalDurum::Kapali: return "Canlı kanal: kapalı (URL ayarlanmamış)"; case KanalDurum::Baglaniyor: return "Canlı kanal: bağlanıyor..."; case KanalDurum::Bagli: return "Canlı kanal: BAĞLI — " + g_sunucuAdi + " · " + std::to_string(g_kararSayisi) + " karar"; case KanalDurum::Kopuk: return "Canlı kanal: kopuk, yeniden bağlanıyor" + (g_hata.empty() ? std::string() : " (" + g_hata + ")"); } return "Canlı kanal: ?"; } void uzakCanliAyarBolumu() { ImGui::TextDisabled("%s", uzakCanliDurumOzet().c_str()); static char buf[512] = ""; static bool bufDolu = false; if (!bufDolu) { std::string v = urlOku(); snprintf(buf, sizeof(buf), "%s", v.c_str()); bufDolu = true; } ImGui::InputText("##canliurl", buf, sizeof(buf)); if (ImGui::IsItemHovered()) { std::string ipucu = "Canlı lisans sunucusu (Socket.IO v3). Örn: https://fikoyt.com:9000/isans.socket.io/"; ipucu += NL; ipucu += "Boş = canlı kanal kapalı (.pol dosyalı uzak politika bağımsız çalışır)"; ImGui::SetTooltip("%s", ipucu.c_str()); } ImGui::SameLine(); if (ImGui::Button("Bağlan")) { ayarYaz(CANLI_URL_AYAR, buf); uzakCanliBaslat(); g_yenidenBaglanIste = true; } ImGui::SameLine(); if (ImGui::Button("Kanalı kapat")) ayarYaz(CANLI_URL_AYAR, ""); static bool zorunluDolu = false; static bool zorunlu = false; static int gecikmeSn = 120; if (!zorunluDolu) { zorunlu = ayarOku("lisans_canli_zorunlu", "0") == "1"; gecikmeSn = std::max(10, std::atoi(ayarOku("lisans_canli_zorunlu_gecikme_sn", "120").c_str())); zorunluDolu = true; } if (ImGui::Checkbox("Bağlantı ZORUNLU (yoksa uygulama kilitlenir)", &zorunlu)) ayarYaz("lisans_canli_zorunlu", zorunlu ? "1" : "0"); if (ImGui::IsItemHovered()) { std::string ipucu = "Açıksa: kanal aşağıdaki gecikme süresinden daha uzun süredir BAĞLI değilse,"; ipucu += NL; ipucu += "lisans ne olursa olsun (Serbest/Kısıtlı fark etmez) uygulama görüntüleyici"; ipucu += NL; ipucu += "kipine düşer: hiçbir komut çalışmaz. Sunucu tekrar ulaşılır olunca otomatik açılır."; ImGui::SetTooltip("%s", ipucu.c_str()); } if (zorunlu) { ImGui::SetNextItemWidth(120.0f); if (ImGui::DragInt("Gecikme (sn)##canlizorunlugecikme", &gecikmeSn, 1.0f, 10, 3600)) ayarYaz("lisans_canli_zorunlu_gecikme_sn", std::to_string(gecikmeSn)); } } } // namespace fiko