// -- Yonetim paneli HTTP katmani ------------------------------------------------ // Uretici makinesi -> fikoyt-sunucu: kokDB (.aggregate/.ekle) + tetikleyici // (yenile / manuel-tetikle) cagrilari. WinHTTP (lisans_uzak.cpp deseni). // // OTURUM: giris.yap doner `Set-Cookie: sid=...`; sonraki isteklerde Cookie // basligi ile gonderilir. Sunucu session'i MongoDB'de (go_sessions) tutar. #include #include #include #include #include #include #include #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include "fiko/cekirdek/kripto.hpp" #include "fiko/cekirdek/lisans_dosya.hpp" #include "fiko/core/gunluk.hpp" #include "lisans_yonetim.hpp" #include "simdjson.h" namespace fiko { namespace { // ?? Oturum durumu (panel genelinde paylasilir) ???????????????????????????????? struct HttpDurum { HINTERNET oturum = nullptr; std::string sunucu = "fikoyt.com"; int port = 9000; bool https = true; std::string cookie; // "sid=..." bool oturumAcik = false; std::string sonHata; std::string cevapGovdesi; int cevapKodu = 0; }; HttpDurum g_http; // Komut imzalama icin ozel anahtar yolu (UI "Sec"/anahtar kutusundan doldurur; // bossa %USERPROFILE%\.fikoyt\lisans_ozel.key varsayilir). std::string g_ozelAnahtarYol; struct UrlParca { bool https = true; int port = 9000; std::string host, yol; }; // "https://fikoyt.com:9000" veya "fikoyt.com:9000" -> ayristir 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 e = kalan.find('/'); e != std::string_view::npos) { yetki = kalan.substr(0, e); yol = kalan.substr(e); } std::string host = yetki; if (const std::size_t p = yetki.rfind(':'); p != std::string_view::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; } void oturumKapat() { if (g_http.oturum) { WinHttpCloseHandle(g_http.oturum); g_http.oturum = nullptr; } g_http.cookie.clear(); g_http.oturumAcik = false; } // ?? Tek HTTP istegi (POST JSON) ??????????????????????????????????????????????? bool istek(const std::string& yol, const std::string& govde, std::string& cevap, int& kod, std::string& hata) { UrlParca u; if (!urlCoz(g_http.sunucu, u)) { hata = "gecersiz sunucu adresi"; return false; } if (!g_http.oturum) { g_http.oturum = WinHttpOpen(L"FikoYT-LisansYonetim/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!g_http.oturum) { hata = "WinHttpOpen basarisiz"; return false; } WinHttpSetTimeouts(g_http.oturum, 10000, 10000, 20000, INFINITE); } // ?? std::wstring(...) ge�ici nesnesi yerine sabit tampon: ge�ici nesnenin // yasam s�resi belirsiz oldugu 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(g_http.oturum, wbuf, (INTERNET_PORT)u.port, 0); if (!baglanti) { hata = "baglanti hatasi"; return false; } HINTERNET istekH = WinHttpOpenRequest(baglanti, L"POST", std::wstring(yol.begin(), yol.end()).c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (!istekH) { WinHttpCloseHandle(baglanti); hata = "istek acilamadi"; return false; } // ?? Sertifika dogrulama ???????????????????????????????????????????????????? // Sunucu adresi GIZLIDIR (musteriye gosterilmez; sertifika "fikoyt.com" icin // uretilmistir, gizli adres/ IP o sertifikayi TUTMAZ). Bu yuzden varsayilan // olarak sunucu adi + sertifika adi eslesmesi GEVSETILIR. // // Guvenlik notu: bu ayar yalniz URETICI makinesinde derlenen yonetim // konsolunda aktiftir (FIKO_URETICI_UI). Musteri ikilisinde bu dosya hic // derlenmez. Kanitin gucunden dusulmez: her yanit ECDSA ile dogrulanir, // oturum karolasi sunucudan gelir. Dogrulama kapatmak yalnizca "bu // adrese baglanma" bilgisini kaldirir; sahte sunucu imzali bilet uretemez. if (u.https) { DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(istekH, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec)); } // ?? COOKIE'YI BIZ YONETIRIZ: WinHTTP varsayilan olarak cookie'leri kendi oturum deposunda // tutar ve Set-Cookie basligini donen basliklardan GIZLER ? "cookie alinamadi". // Otomatik cookie devre disi birakilinca Set-Cookie ham basliklarda gorunur ve elle // gonderdigimiz Cookie: basligi da oldugu gibi iletilir. { DWORD dis = WINHTTP_DISABLE_COOKIES; WinHttpSetOption(istekH, WINHTTP_OPTION_DISABLE_FEATURE, &dis, sizeof(dis)); } // Basliklar: JSON govde icin Content-Type SART (yoksa sunucu govdeyi ayristirmaz ? // giris basarisiz ? Set-Cookie hic uretilmez) + varsa oturum cookie'si. std::wstring ekBasliklar = L"Content-Type: application/json\r\nAccept: application/json"; if (!g_http.cookie.empty()) ekBasliklar += L"\r\nCookie: " + std::wstring(g_http.cookie.begin(), g_http.cookie.end()); const LPCWSTR baslik = ekBasliklar.c_str(); const BOOL ok = WinHttpSendRequest(istekH, baslik, (DWORD)-1, (LPVOID)govde.data(), (DWORD)govde.size(), (DWORD)govde.size(), 0) && WinHttpReceiveResponse(istekH, nullptr); if (!ok) { WinHttpCloseHandle(istekH); WinHttpCloseHandle(baglanti); hata = "istek/yanit hatasi (sunucu erisilemiyor olabilir)"; return false; } // ?? Set-Cookie yakala (giris cevabinda) ??????????????????????????????????? // Fiber session middleware sunu g�nderir: // Set-Cookie: sid=; max-age=86400; path=/; HttpOnly; secure; SameSite=Lax // Oturum sonraki isteklerde `Cookie: sid=` basligiyla tasinir. // // ?? WinHTTP baslik sorgusunun DOGRU iki adimi: // 1) WINHTTP_QUERY_RAW_HEADERS ile BAYT sayisi sorulur - bu bayt turunden // TAMPON BOYUTUDUR, baslik sayisi DEGIL. (CRLF varyanti soruldugunda // kucuk bir SAYI doner; tampon 2 byte'a duser ve yalnizca "H" okunur - // onceki "Basliklar: H" hatasinin sebebi tam olarak buydu.) // 2) WINHTTP_QUERY_RAW_HEADERS_CRLF ile metin alinir. { // ?? GERCEK DUZELTME (GLE 122): WinHTTP basliklari UTF-16 (WCHAR) dondurur ve // RAW_HEADERS_CRLF, RAW_HEADERS'tan UZUNDUR (satir basi 2 karakter vs 1 null). // Tampon boyutu MUTLAKA ayni sorgu turuyle (CRLF) sorulmali; sonuc BAYT cinsindendir. DWORD gerekli = 0; WinHttpQueryHeaders(istekH, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, nullptr, &gerekli, WINHTTP_NO_HEADER_INDEX); if (gerekli > 16 && gerekli < (1u << 20)) { // makul ust sinir: 1 MB std::wstring whdr(gerekli / sizeof(wchar_t) + 1, L'\0'); DWORD alindi = 0; DWORD tamponBayt = (DWORD)(whdr.size() * sizeof(wchar_t)); std::string hdr; if (WinHttpQueryHeaders(istekH, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, &whdr[0], &tamponBayt, WINHTTP_NO_HEADER_INDEX)) { // tamponBayt = yazilan bayt (sonlandirici null haric) ? wchar sayisi whdr.resize(tamponBayt / sizeof(wchar_t)); // Basliklar ASCII/Latin: dar stringe basit daraltma yeterli (cookie sid=). hdr.reserve(whdr.size()); for (wchar_t wc : whdr) hdr.push_back(wc < 0x80 ? (char)wc : '?'); alindi = (DWORD)hdr.size(); // Arama icin kucuk harf kopya; kesme orijinalden yapilir. std::string kucuk = hdr; for (char& c : kucuk) c = (char)std::tolower((unsigned char)c); // 1) Once "Set-Cookie" basliginin kendisini bul std::size_t sc = kucuk.find("set-cookie:"); if (sc == std::string::npos) sc = 0; // baslik adi yoksa tum metni tara const std::size_t sid = kucuk.find("sid=", sc); if (sid != std::string::npos) { const std::size_t bas = sid + 4; std::size_t son2 = bas; while (son2 < kucuk.size() && kucuk[son2] != ';' && kucuk[son2] != '\r' && kucuk[son2] != '\n' && kucuk[son2] != ' ' && kucuk[son2] != '\t') ++son2; if (son2 > bas) g_http.cookie = "sid=" + hdr.substr(bas, son2 - bas); } if (g_http.cookie.empty()) gunluk::uyari("Set-Cookie bulunamadi ({} bayt). Basliklar: {}", alindi, hdr.substr(0, hdr.size() < 300 ? hdr.size() : 300)); else gunluk::bilgi("Oturum cookie alindi ({} bayt baslik okundu)", alindi); } else { gunluk::uyari("Baslik metni okunamadi (GLE {})", GetLastError()); } } else { gunluk::uyari("Baslik tampon boyutu anormal: {} bayt (GLE {})", gerekli, GetLastError()); } } // HTTP kodu DWORD k = 0, kb = sizeof(k); WinHttpQueryHeaders(istekH, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &k, &kb, WINHTTP_NO_HEADER_INDEX); kod = (int)k; // Govde cevap.clear(); char tampon[8192]; DWORD okunan = 0; while (WinHttpReadData(istekH, tampon, sizeof(tampon), &okunan) && okunan > 0) cevap.append(tampon, okunan); WinHttpCloseHandle(istekH); WinHttpCloseHandle(baglanti); if (kod < 200 || kod >= 300) { hata = "HTTP " + std::to_string(kod); return false; } return true; } // ?? JSON ka�is ???????????????????????????????????????????????????????????????? std::string kacisla(const std::string& s) { std::string o; o.reserve(s.size() + 8); for (char c : s) { if (c == '"' || c == '\\') { o += '\\'; o += c; } else if (c == '\n') o += "\\n"; else if (c == '\r') {} else if ((unsigned char)c < 0x20) o += ' '; else o += c; } return o; } // ?? simdjson gercek JSON okuyucu (elle yazilmis tarayicinin YERINE) ??????????? // Eski "alanOku" belgeyi ilk '}' de kesiyordu: `kimlik` Mongo genisletilmis // JSON `{"$oid":..}` ve `bilgiler` ic ice oldugu icin TUM belgelerde alan // bulunamaz, liste hep bos gorunurdu. namespace sj = simdjson; // Unix zaman damgasini kisaZaman'in bekledigi ISO bicimine cevir // ("2026-10-08T14:22:31"). 10 hane = saniye, 13 hane = milisaniye; diger // degerler oldugu gibi birakilir. std::string unixIso(const std::string& s) { const bool sn = (s.size() == 10), ms = (s.size() == 13); if (!sn && !ms) return s; for (char ch : s) if (ch < '0' || ch > '9') return s; i64 damga = std::strtoll(s.c_str(), nullptr, 10); if (damga <= 0) return s; if (ms) damga /= 1000; const time_t t = (time_t)damga; std::tm tm{}; localtime_s(&tm, &t); char b[32]; std::strftime(b, sizeof b, "%Y-%m-%dT%H:%M:%S", &tm); return b; } // Ham JSON degerini metne cevirir: string / sayi / MongoDB genisletilmis JSON // sarmalayicilari ({"$oid":..}, {"$date":..}, {"$numberLong":..}). // Donus: bir sey cikarildi mi. bool degerMetin(sj::ondemand::value v, std::string& cikti, int derinlik = 0) { std::string_view sv; if (v.get_string().get(sv) == sj::SUCCESS) { cikti.assign(sv); return true; } int64_t tam = 0; if (v.get_int64().get(tam) == sj::SUCCESS) { cikti = std::to_string(tam); return true; } uint64_t sayi = 0; if (v.get_uint64().get(sayi) == sj::SUCCESS) { cikti = std::to_string(sayi); return true; } double kayan = 0; if (v.get_double().get(kayan) == sj::SUCCESS) { char b[32]; std::snprintf(b, sizeof b, "%g", kayan); cikti = b; return true; } if (derinlik >= 3) return false; sj::ondemand::object o; if (v.get_object().get(o) != sj::SUCCESS) return false; // Sarmalayicilar tek anahtarli: ilk anahtara bakip icine in. for (auto f : o) { auto ak = f.unescaped_key(); if (ak.error()) break; const std::string_view k = ak.value_unsafe(); if (k == "$oid" || k == "$date" || k == "$numberLong" || k == "$numberInt" || k == "$numberDouble" || k == "$numberDecimal") { sj::ondemand::value alt; if (f.value().get(alt) != sj::SUCCESS) return false; return degerMetin(alt, cikti, derinlik + 1); } break; // bilinmeyen ilk anahtar -> bu bir sarmalayici degil } return false; } // Bir belgeyi tek geciste okur ("bilgiler" ic ice nesnesi de ayni yolla). // On-Demand ileri-yonlu: alanlar DOSYA SIRASINDA gezilir; ayni alan iki kez // gelirse ILK deger korunur (ust/alt duzel cakismasiz). // Sunucu semasi (kokDB goCihazlar): // kimlik ({$oid}|duz string), tipi, tarih, bilgiler.{seri,musteri, // bilgisayar,surum,adres,...} void belgeOku(sj::ondemand::object o, CihazOzet& c) { for (auto f : o) { auto ak = f.unescaped_key(); if (ak.error()) continue; const std::string_view k = ak.value_unsafe(); if (k == "bilgiler" || k == "bilgi") { // ic ice bilgi nesnesi sj::ondemand::object alt; if (f.value().get_object().get(alt) == sj::SUCCESS) belgeOku(alt, c); continue; } // Donanim parmak izi dizisi (bilgiler.makine): lisans makineye bu listeyle kilitlenir. if (k == "makine") { if (c.makine.empty()) { // ilk deger kazanir sj::ondemand::array arr; if (f.value().get_array().get(arr) == sj::SUCCESS) { for (auto e : arr) { std::string_view sv; if (e.get_string().get(sv) == sj::SUCCESS && !sv.empty()) c.makine.emplace_back(sv); } } } continue; } std::string* hedef = nullptr; bool tarihMi = false; if (k == "kimlik") hedef = &c.cihazKimlik; else if (k == "bilgisayar") hedef = &c.bilgisayar; else if (k == "seri") hedef = &c.seri; else if (k == "musteri") hedef = &c.musteri; else if (k == "surum") hedef = &c.surum; else if (k == "tipi") hedef = &c.tipi; else if (k == "ip") hedef = &c.ip; else if (k == "uretici") hedef = &c.uretici; // "1" = uretici makinesi else if (k == "adres") hedef = &c.ip; // bilgiler.adres = soket IP:port else if (k == "tarih") { hedef = &c.sonBaglanma; tarihMi = true; } else continue; // diger alan: deger okunmaz if (!hedef->empty()) continue; // ilk deger kazanir sj::ondemand::value v; if (f.value().get(v) != sj::SUCCESS) continue; std::string d; if (!degerMetin(v, d) || d.empty()) continue; if (tarihMi) d = unixIso(d); *hedef = std::move(d); } } std::string isoZaman(i64 unix) { if (unix <= 0) return "-"; const time_t t = (time_t)unix; std::tm tm{}; localtime_s(&tm, &t); char b[32]; std::strftime(b, sizeof b, "%d.%m.%Y %H:%M", &tm); return b; } } // namespace // ?? Genel durum ??????????????????????????????????????????????????????????????? YonetimDurumu& yonetimDurumu() { static YonetimDurumu d; return d; } const std::vector& yonetimCihazlari() { static std::vector v; return v; } const std::vector& yonetimCevaplari() { static std::vector v; return v; } // ?? Oturum ac ????????????????????????????????????????????????????????????????? // Komut imzasi icin kullanilacak ozel anahtar yolunu ayarla (UI doldurur). void yonetimOzelAnahtarYolAyarla(const std::string& yol) { g_ozelAnahtarYol = yol; } bool yonetimOturumAc(const std::string& sunucu, const std::string& kullaniciAdi, const std::string& parola, std::string& hata) { UrlParca u; if (!urlCoz(sunucu, u)) { hata = "Gecersiz sunucu adresi"; return false; } g_http.sunucu = sunucu; oturumKapat(); const std::string govde = "{\"kullaniciAdi\":\"" + kacisla(kullaniciAdi) + "\",\"parola\":\"" + kacisla(parola) + "\"}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.giris.yap", govde, cevap, kod, hata)) return false; g_http.oturumAcik = !g_http.cookie.empty(); if (!g_http.oturumAcik) { // Sunucu giris hatalarini HTTP 200 + govdede durum.hatalar[] ile doner (orn. "Kullanici // Bulunamadi"). Once bu mesaji cikar; yoksa ham govde ozetini goster. auto jsonAlan = [&](const char* anahtar, std::size_t baslangic) -> std::string { const std::string ara = std::string("\"") + anahtar + "\":\""; const std::size_t a = cevap.find(ara, baslangic); if (a == std::string::npos) return {}; const std::size_t b0 = a + ara.size(); const std::size_t b1 = cevap.find('"', b0); return b1 == std::string::npos ? std::string() : cevap.substr(b0, b1 - b0); }; const std::size_t hataYeri = cevap.find("\"hatalar\""); const std::string sunucuBaslik = hataYeri == std::string::npos ? std::string() : jsonAlan("baslik", hataYeri); const std::string sunucuMesaj = hataYeri == std::string::npos ? std::string() : jsonAlan("mesaj", hataYeri); if (!sunucuBaslik.empty() || !sunucuMesaj.empty()) { hata = "Giris reddedildi: " + sunucuBaslik + (sunucuMesaj.empty() ? std::string() : " - " + sunucuMesaj); } else { hata = "Oturum acilamadi (cookie alinamadi; HTTP " + std::to_string(kod) + "): " + cevap.substr(0, 160); } gunluk::uyari("Yonetim giris basarisiz. HTTP {} govde: {}", kod, cevap.substr(0, 300)); return false; } YonetimDurumu& d = yonetimDurumu(); d.sunucu = sunucu; d.kullaniciAdi = kullaniciAdi; d.oturumAcik = true; d.cevapHatasi.clear(); gunluk::bilgi("Yonetim oturumu acildi: {}", kullaniciAdi); return true; } // ?? Cihaz listesini tazele (kokDB .aggregate -> goCihazlar) ??????????????????? bool yonetimCihazlariYenile(std::vector& cikti, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } // goCihazlar'a erisim beyaz listede (saltOkunurKoleksiyonlar). const std::string govde = "{\"aggregate\":[{\"$match\":{}},{\"$sort\":{\"tarih\":-1}},{\"$limit\":200}]}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.goCihazlar.aggregate", govde, cevap, kod, hata)) return false; cikti.clear(); // ?? Gercek JSON okuyucu (simdjson On-Demand) ?????????????????????????????? // Sunucu fapiv1 yanitini kuresel sarmalayica koyar: // {"durum":{"kodu":200,...},"sonuc":[belge,...]} // Sarmalayici yoksa (govde duz dizi ise) o da kabul edilir. sj::ondemand::parser ayristirici; sj::padded_string dolu(cevap); sj::ondemand::document kok; if (ayristirici.iterate(dolu).get(kok) != sj::SUCCESS) { hata = "Cevap JSON degil (HTTP " + std::to_string(kod) + "): " + cevap.substr(0, 160); return false; } sj::ondemand::array dizi; auto kokTip = kok.type(); // yalniz bakar, tuketmez if (kokTip.error()) { hata = "Cevap okunamadi"; return false; } if (kokTip.value() == sj::ondemand::json_type::array) { if (kok.get_array().get(dizi) != sj::SUCCESS) { hata = "Cevap dizisi okunamadi: " + cevap.substr(0, 160); return false; } } else if (kokTip.value() == sj::ondemand::json_type::object) { sj::ondemand::object kokO; if (kok.get_object().get(kokO) != sj::SUCCESS) { hata = "Cevap nesnesi okunamadi: " + cevap.substr(0, 160); return false; } if (kokO["sonuc"].get_array().get(dizi) != sj::SUCCESS) { // Dizi yok: sunucunun durum.mesajini yuzeye cikar (yetki/rol hatasi vb.). std::string_view mesaj; sj::ondemand::object durum; std::string ek; if (kokO["durum"].get_object().get(durum) == sj::SUCCESS && durum["mesaj"].get_string().get(mesaj) == sj::SUCCESS && !mesaj.empty()) ek = ": " + std::string(mesaj); hata = "Cevapta 'sonuc' dizisi yok" + ek; gunluk::uyari("goCihazlar cevabi cozulemedi. Govde: {}", cevap.substr(0, 400)); return false; } } else { hata = "Beklenmeyen cevap bicimi: " + cevap.substr(0, 160); return false; } int ham = 0; for (auto belge : dizi) { sj::ondemand::object o; if (belge.get_object().get(o) != sj::SUCCESS) continue; // belge disi oge ++ham; CihazOzet c; belgeOku(o, c); if (c.cihazKimlik.empty()) continue; if (c.bilgisayar.empty()) c.bilgisayar = "(bilinmiyor)"; cikti.push_back(std::move(c)); } if (cikti.empty() && ham > 0) { // Belgeler geldi ama kimlik okunamadi -> sema/sarmalayici degismis demektir. gunluk::uyari("goCihazlar: {} belge alindi, hicbirinde 'kimlik' okunamadi. Govde: {}", ham, cevap.substr(0, 400)); } else { gunluk::bilgi("goCihazlar: {} belge -> {} cihaz", ham, cikti.size()); } YonetimDurumu& d = yonetimDurumu(); d.sunucuyaBagli = true; d.yukleniyor = false; d.cevapHatasi.clear(); d.sonHata.clear(); return true; } // ?? Cihaz kaydini sil (kokDB .sil -> goCihazlar) ?????????????????????????????? // Uretici/yonetici yetkisi gerekir (sunucu: dbErisimKontrolYazma). Filtre kimlik // uzerinden gider: sunucu IsleObjectId() ile 24 haneli hex degeri ObjectID'ye // cevirir - goCihazlar'da da uzakKimlikDeger ayni kurali uyguladigi icin turler // eslesir; duz string kimlikler oldugu gibi kalir. Cihaz kaydi silinince bir // sonraki baglantasinda kendiliginden yeniden kayit olur. bool yonetimCihazlariSil(const std::vector& kimlikler, int& silinen, std::string& hata) { silinen = -1; if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } if (kimlikler.empty()) { hata = "Silinecek cihaz secilmedi"; return false; } // Dizi govde -> sunucu "Array" dalini calistirir (her eleman icin DeleteMany). std::string govde = "["; for (std::size_t i = 0; i < kimlikler.size(); ++i) { if (i) govde += ","; govde += "{\"kimlik\":\"" + kacisla(kimlikler[i]) + "\"}"; } govde += "]"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.goCihazlar.sil", govde, cevap, kod, hata)) return false; // Silinen sayisi: {"durum":..,"sonuc":{"DeletedCount":N}} (opsiyonel okuma). sj::ondemand::parser ayristirici; sj::padded_string dolu(cevap); sj::ondemand::document kok; if (ayristirici.iterate(dolu).get(kok) == sj::SUCCESS) { sj::ondemand::object kokO; if (kok.get_object().get(kokO) == sj::SUCCESS) { sj::ondemand::object sonuc; if (kokO["sonuc"].get_object().get(sonuc) == sj::SUCCESS) { int64_t n = -1; if (sonuc["DeletedCount"].get_int64().get(n) == sj::SUCCESS || sonuc["deletedCount"].get_int64().get(n) == sj::SUCCESS) silinen = (int)n; } } } YonetimDurumu& dr = yonetimDurumu(); dr.cevapHatasi.clear(); if (silinen >= 0) gunluk::bilgi("goCihazlar: {} kayit silindi", silinen); else gunluk::bilgi("goCihazlar: {} kimlik icin silme istendi", kimlikler.size()); return true; } // ?? Cihaz adlari (satici takma adlari) -> kokDB fikocadCihazAdlari ????????????? // Belge: {kimlik, cihazAdi, zaman}. _id'ye BAGLI DEGILIZ (sunucu 24 haneli hex // degerleri ObjectID'ye cevirebilir; tur uyumsuzlugu riski): hem .sil hem .aggregate // "kimlik" alani uzerinden gider, .ekle _id'yi sunucuya birakir. Ad degisince once // eski kayit silinir, sonra yenisi eklenir -> cihaz basina tek kayit kalir. bool yonetimCihazAdiYaz(const std::string& kimlik, const std::string& ad, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } if (kimlik.empty()) { hata = "Cihaz kimligi bos"; return false; } std::string cevap; int kod = 0; const std::string silGovde = "[{\"kimlik\":\"" + kacisla(kimlik) + "\"}]"; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadCihazAdlari.sil", silGovde, cevap, kod, hata)) { if (!cevap.empty()) hata += ": " + cevap.substr(0, 160); return false; } if (ad.empty()) { // sifirlama: yalniz silme yeterli gunluk::bilgi("Cihaz adi sunucudan silindi: {}", kimlik); return true; } std::string j = "{\"kimlik\":\"" + kacisla(kimlik) + "\""; j += ",\"cihazAdi\":\"" + kacisla(ad) + "\""; j += ",\"zaman\":" + std::to_string((i64)std::time(nullptr)) + "}"; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadCihazAdlari.ekle", j, cevap, kod, hata)) { if (!cevap.empty()) hata += ": " + cevap.substr(0, 160); return false; } gunluk::bilgi("Cihaz adi sunucuya yazildi: {} -> {}", kimlik, ad); return true; } bool yonetimCihazAdlariOku(std::map& adlar, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } const std::string govde = "{\"aggregate\":[{\"$match\":{}},{\"$limit\":1000}]}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadCihazAdlari.aggregate", govde, cevap, kod, hata)) { if (!cevap.empty()) hata += ": " + cevap.substr(0, 160); return false; } adlar.clear(); // Koleksiyon henuz bos/yok: sunucu "sonuc":null donebilir -> hata degil, bos liste. if (cevap.find("\"sonuc\":null") != std::string::npos) return true; sj::ondemand::parser ayristirici; sj::padded_string dolu(cevap); sj::ondemand::document kok; if (ayristirici.iterate(dolu).get(kok) != sj::SUCCESS) { hata = "Cevap JSON degil: " + cevap.substr(0, 160); return false; } sj::ondemand::array dizi; auto kokTip = kok.type(); if (kokTip.error()) { hata = "Cevap okunamadi"; return false; } if (kokTip.value() == sj::ondemand::json_type::array) { if (kok.get_array().get(dizi) != sj::SUCCESS) { hata = "Cevap dizisi okunamadi"; return false; } } else if (kokTip.value() == sj::ondemand::json_type::object) { sj::ondemand::object kokO; if (kok.get_object().get(kokO) != sj::SUCCESS) { hata = "Cevap nesnesi okunamadi"; return false; } if (kokO["sonuc"].get_array().get(dizi) != sj::SUCCESS) { // Dizi yok: sunucunun durum.mesajini yuzeye cikar (yetki / koleksiyon reddi vb.). std::string_view mesaj; sj::ondemand::object durum; std::string ek; if (kokO["durum"].get_object().get(durum) == sj::SUCCESS && durum["mesaj"].get_string().get(mesaj) == sj::SUCCESS && !mesaj.empty()) ek = ": " + std::string(mesaj); hata = "Cevapta 'sonuc' dizisi yok" + ek; return false; } } else { hata = "Beklenmeyen cevap bicimi: " + cevap.substr(0, 160); return false; } for (auto belge : dizi) { sj::ondemand::object o; if (belge.get_object().get(o) != sj::SUCCESS) continue; std::string kimlik, ad; for (auto f : o) { auto ak = f.unescaped_key(); if (ak.error()) continue; const std::string_view k = ak.value_unsafe(); std::string* hedef = k == "kimlik" ? &kimlik : (k == "cihazAdi" ? &ad : nullptr); if (!hedef) continue; sj::ondemand::value v; if (f.value().get(v) != sj::SUCCESS) continue; degerMetin(v, *hedef); } if (!kimlik.empty() && !ad.empty()) adlar[kimlik] = ad; } return true; } // ?? Tetikleyici uzerinden ANINDA komut gonder ???????????????????????????????? // Kural yazar -> motoru yeniler -> manuel tetikler -> sokete emit. // Bu 3 cagri sunucuda ~200-800 ms surer. namespace { // Kural kimligi: MongoDB ObjectID (12 bayt = 24 hex karakter). // // NEDEN ObjectID? Sunucudaki tetikleyici motoru kurallari `tetikleyiciKural` // struct'ina decode eder ve o struct'ta `_id primitive.ObjectID` turunde // tanimlidir. String bir _id yazarsak motor TUM KURALLARI decode hatasyla // atar ("an ObjectID string must be exactly 12 bytes long") ve kural sayisi // 0 kalir - komut gonderme sessizce calismaz. Bu bir tuzaktir, 12 bayt uretmek // sarttir. // // Rastgelelik: kripto::rastgeleBayt (BCryptGenRandom) basarisiz olursa zaman // damgasindan turetilir - kimlik benzersizligi bozulmaz (motor yalnizca // varlik/donus icin kullanir, guvenlik burada degil, komut imzasindadir). std::string kuralIdUret() { u8 d[12]; if (!kripto::rastgeleBayt(d, 12)) { const i64 t = (i64)std::time(nullptr); for (int i = 0; i < 8; ++i) d[i] = (u8)((t >> (i * 8)) & 0xFF); d[8] = d[9] = d[10] = d[11] = 0x5B; // "[" ile baslayan 12 bayt } return kripto::baytHex(d, 12); } bool tetikleyiciCagir(const std::string& govde, std::string& hata) { std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle", govde, cevap, kod, hata)) return false; // Sunucu, komutun gidecegi bagli soket sayisini "ulasan" ile bildirir: 0 = hedef // cevrimdisi ya da kimligi eslesmiyor. Eskiden bu durum sessizce "Gonderildi" gorunuyordu. if (cevap.find("\"ulasan\":0") != std::string::npos) { hata = "Cihaz cevrimdisi - komut hicbir cihaza iletilemedi"; return false; } return true; } } // namespace namespace { // Ileri bildirim: gercek tanim dosyanin altinda (lisans bileti bolumu). // yonetimKomutGonder komutu ozel anahtarla imzalayacagi icin erken gorunmesi gerekir. bool ozelAnahtarOku(const std::string& yol, std::string& ozelHex, std::string& hata); } // namespace // Once motoru yenile (yeni kurali yuklesin) [[maybe_unused]] static bool motoruYenile(std::string& hata) { std::string cevap; int kod = 0; return istek("/fapiv1/fikoyt.com.FikoYT.sistem.tetikleyici.yenile", "{}", cevap, kod, hata); } bool yonetimKomutGonder(const CihazOzet& cihaz, const std::string& komutKodu, const std::string& mesaj, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } if (cihaz.cihazKimlik.empty()) { hata = "Cihaz secilmedi"; return false; } if (komutKodu.empty()) { hata = "Komut secilmedi"; return false; } // Beyaz liste denetimi (istemci tarafi da denetleyecek; burada erken geri bildirim) const std::vector& bl = yonetimBeyazListe(); if (std::find(bl.begin(), bl.end(), komutKodu) == bl.end()) { hata = "Komut beyaz listede degil: " + komutKodu; return false; } const std::string kid = kuralIdUret(); // ?? Komut imzasi (istemci 0. adimda ECDSA P-256 dogrular) ????????????????? // Istemcinin bekledigi kanonik metinle BIREBIR ayni sira: // komutId=\nkomut_kodu=\nmesaj=\n // Imza yoksa istemci "komut imzasi gecersiz veya eksik" ile REDDEDER ve // konsolda "Gonderildi" gorunmesine ragmen cihazda HICBIR SEY OLMAZ. std::string anahtarYol = g_ozelAnahtarYol; if (anahtarYol.empty()) { if (const char* up = std::getenv("USERPROFILE"); up && *up) anahtarYol = std::string(up) + "\\.fikoyt\\lisans_ozel.key"; } std::string ozelHex; if (!ozelAnahtarOku(anahtarYol, ozelHex, hata)) return false; const std::string kanonik = "komutId=" + kid + "\nkomut_kodu=" + komutKodu + "\nmesaj=" + mesaj + "\n"; std::string imza; if (!kripto::imzala(kanonik, ozelHex, imza)) { hata = "Komut imzalanamadi (ozel anahtar gecersiz olabilir)"; return false; } // kural govdesi: once kurali yaz (hareketler, vt=tetikleyiciler) // // ?? kosul.tip OLMAZSA motor kurali calistirmaz: kuralEslesiyor() switch'in // default kolunda `return false` yapar ve olay sessizce yutulur (HTTP yine 200 // doner, kural "calisti" sanilir). Bu yuzden "cihaz_olay" acikca yazilir. std::string kural = "{"; kural += "\"_id\":\"" + kid + "\""; kural += ",\"vt\":\"FikoYT.sistem.tetikleyiciler\""; kural += ",\"ad\":\"Yonetim: " + kacisla(komutKodu) + "\""; kural += ",\"aktif\":true"; kural += ",\"kosul\":{\"tip\":\"cihaz_olay\",\"cihazId\":\"" + kacisla(cihaz.cihazKimlik) + "\",\"olayKodu\":\"manuel\"}"; kural += ",\"eylemler\":[{"; kural += "\"tip\":\"cihaz_komut\""; kural += ",\"hedefCihazId\":\"" + kacisla(cihaz.cihazKimlik) + "\""; kural += ",\"komutKodu\":\"" + kacisla(komutKodu) + "\""; kural += ",\"mesaj\":\"" + kacisla(mesaj) + "\""; kural += ",\"komutId\":\"" + kid + "\""; kural += ",\"imza\":\"" + kacisla(imza) + "\""; kural += "}]}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.hareketler.ekle", kural, cevap, kod, hata)) return false; // Motor yenilemesi GEREKMIYOR: sunucu manuel-tetikle'de kurali dogrudan DB'den // cozup yalniz onu calistirir (eskiden once yenile + tum kurallar tetiklenirdi). // Manuel tetikle const std::string tetik = "{\"tetikleyiciId\":\"" + kid + "\"}"; if (!tetikleyiciCagir(tetik, hata)) { hata = "Tetikleme basarisiz: " + hata; return false; } YonetimDurumu& d = yonetimDurumu(); ++d.komutSayaci; d.cevapHatasi.clear(); return true; } // ?? Lisans bileti yaz (kokDB .ekle -> fikocadLisanslari) ??????????????????????? namespace { // Ozel anahtari dosyadan oku (ureticide: %USERPROFILE%\.fikoyt\lisans_ozel.key) bool ozelAnahtarOku(const std::string& yol, std::string& ozelHex, std::string& hata) { if (yol.empty()) { hata = "Ozel anahtar yolu bos"; return false; } std::ifstream f(yol, std::ios::binary); if (!f) { hata = "Anahtar dosyasi okunamadi: " + yol; return false; } std::string icerik((std::istreambuf_iterator(f)), std::istreambuf_iterator()); // En uzun kesintisiz hex dizisini ayikla (dosya "# yorum" icerebilir) std::string enIyi, guncel; for (char c : icerik) { const bool hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); if (hex) guncel += c; else { if (guncel.size() > enIyi.size()) enIyi = guncel; guncel.clear(); } } if (guncel.size() > enIyi.size()) enIyi = guncel; ozelHex = enIyi; if (ozelHex.size() != 192) { // X||Y||d = 96 bayt = 192 hex hata = "Anahtar gecersiz (192 hex karakter bekleniyordu, " + std::to_string(ozelHex.size()) + " bulundu)"; return false; } return true; } // Kanonik govde kur (C++ tarafi ve uretici penceresi ile ayni sira) std::string biletGovdeKur(const std::string& cihaz, const std::string& musteri, const std::string& seri, const std::vector& komutlar, i64 bitis) { std::string g = "belge=bilet\n"; g += "cihaz=" + cihaz + "\n"; g += "musteri=" + musteri + "\n"; g += "seri=" + seri + "\n"; g += "bitis=" + std::to_string(bitis) + "\n"; for (const std::string& k : komutlar) g += "komut=" + k + "\n"; return g; } } // namespace bool yonetimLisansYaz(const std::string& ozelAnahtarYolu, const CihazOzet& cihaz, const std::string& musteri, const std::string& seri, const std::vector& komutlar, i64 bitisUnix, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } if (cihaz.cihazKimlik.empty()) { hata = "Cihaz secilmedi"; return false; } std::string ozelHex; if (!ozelAnahtarOku(ozelAnahtarYolu, ozelHex, hata)) return false; // YENI AKIS: tam .fkl �ret (fiko_lisans ile ayni bi�im) ? imzala ? sifrele ? sunucuya yaz. // M�steri bu kaydi sunucudan �ekip ayni dogrulama hattindan ge�irir (yerel dosya yok). { std::string govdeF = "# FikoCAD Lisans v1\n"; govdeF += "musteri=" + musteri + "\n"; govdeF += "seri=" + seri + "\n"; govdeF += "urun=fikocad\n"; govdeF += "kisitli=1\n"; for (const std::string& k : komutlar) govdeF += "komut=" + k + "\n"; govdeF += "bitis=" + std::to_string(bitisUnix) + "\n"; // MAKINE KILIDI: cihazin donanim parmak izi (sunucu goCihazlar.bilgiler.makine) yazilir. // Parmak izi yoksa lisans verilmez (makinesiz lisans her makinede �alisirdi). if (cihaz.makine.empty()) { hata = "Cihazin makine kimligi yok - cihaz sunucuya baglanip merhaba g�ndermeli"; return false; } for (const std::string& m : cihaz.makine) govdeF += "makine=" + m + "\n"; const int tolerans = cihaz.makine.size() >= 4 ? 3 : (int)cihaz.makine.size(); govdeF += "makineTolerans=" + std::to_string(tolerans) + "\n"; const std::string kanonikF = lisansKanonikGovde(govdeF); std::string imzaF; if (!kripto::imzala(kanonikF, ozelHex, imzaF)) { hata = "Imzalama basarisiz (anahtar gecersiz olabilir)"; return false; } std::string tamF = govdeF + "imza=" + imzaF + "\n"; u8 zarfAnahtar[32]; lisansZarfAnahtari(zarfAnahtar); const std::string zarf = lisansZarfla(tamF, zarfAnahtar); if (zarf.empty()) { hata = "Lisans sifrelenemedi"; return false; } return yonetimFklYaz(cihaz, zarf, bitisUnix, hata); } // ESKI AKIS (kaldirilacak): komut-only imzali kayit. Yukaridaki blok d�ner. const std::string govde = biletGovdeKur(cihaz.cihazKimlik, musteri, seri, komutlar, bitisUnix); std::string imza; if (!kripto::imzala(govde, ozelHex, imza)) { hata = "Imzalama basarisiz (anahtar gecersiz olabilir)"; return false; } // kokDB .ekle -> fikocadLisanslari (cihaz bazinda tek kayit: _id=cihaz) std::string j = "{"; j += "\"_id\":\"" + kacisla(cihaz.cihazKimlik) + "\""; j += ",\"cihaz\":\"" + kacisla(cihaz.cihazKimlik) + "\""; j += ",\"musteri\":\"" + kacisla(musteri) + "\""; j += ",\"seri\":\"" + kacisla(seri) + "\""; j += ",\"bitis\":" + std::to_string(bitisUnix); j += ",\"komutlar\":["; for (std::size_t i = 0; i < komutlar.size(); ++i) { if (i) j += ","; j += "\"" + kacisla(komutlar[i]) + "\""; } j += "]"; j += ",\"govde\":\"" + kacisla(govde) + "\""; j += ",\"imza\":\"" + kacisla(imza) + "\""; j += ",\"aktif\":true"; j += ",\"uretimZamani\":" + std::to_string((i64)std::time(nullptr)); // Kopma suresi: sunucu belirler (varsayilan 30 sn) j += ",\"kopmaSuresi\":30,\"uyari1\":5,\"uyari2\":15"; j += "}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadLisanslari.ekle", j, cevap, kod, hata)) return false; // Fikocad'in ANINDA fark etmesi icin lisans yenile komutu gonder YonetimDurumu& d = yonetimDurumu(); d.cevapHatasi.clear(); std::string uyari; yonetimKomutGonder(cihaz, "lisans.yenile", "Lisans guncellendi", uyari); gunluk::bilgi("Uzak lisans yazildi: {} ({} komut)", seri, komutlar.size()); return true; } namespace { // Sunucu HTTP 200 donup govdede durum.hata=true ile reddedebilir (yetki/oturum vb.). // Bu durumda istek() basarili sayilir; cevap burada kontrol edilmelidir. bool cevapHataliMi(const std::string& cevap, std::string& mesaj) { if (cevap.find("\"hata\":true") == std::string::npos) return false; mesaj = "sunucu hata dondu"; const std::size_t m = cevap.find("\"mesaj\":\""); if (m != std::string::npos) { const std::size_t b0 = m + 9; const std::size_t b1 = cevap.find('"', b0); if (b1 != std::string::npos) mesaj = cevap.substr(b0, b1 - b0); } return true; } } // namespace // Engelli bilgisayar kimlikleri: lisans sunucusu /lisans-kontrol/api/kararlar (X-Token). // Karar anahtari PC kimligidir (seri alani); engelli=true olanlar listelenir. bool yonetimEngelliKimlikleri(const std::string& yoneticiToken, std::vector& engelliler, std::string& hata) { engelliler.clear(); if (yoneticiToken.empty()) { hata = "Yonetici token bos"; return false; } UrlParca u; if (!urlCoz(g_http.sunucu, u)) { hata = "Gecersiz sunucu adresi"; return false; } HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansYonetim/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { hata = "WinHttpOpen basarisiz"; return false; } std::wstring whost(u.host.begin(), u.host.end()); HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), (INTERNET_PORT)u.port, 0); HINTERNET istekH = baglanti ? WinHttpOpenRequest(baglanti, L"GET", L"/lisans-kontrol/api/kararlar", nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0) : nullptr; if (!istekH) { if (baglanti) WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); hata = "Baglanti kurulamadi"; return false; } if (u.https) { DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(istekH, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec)); } const std::wstring tokenW(yoneticiToken.begin(), yoneticiToken.end()); const std::wstring basliklar = L"X-Token: " + tokenW; const BOOL ok = WinHttpSendRequest(istekH, basliklar.c_str(), (DWORD)-1, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(istekH, nullptr); DWORD kod = 0, kb = sizeof(kod); if (ok) WinHttpQueryHeaders(istekH, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &kb, WINHTTP_NO_HEADER_INDEX); std::string cevap; if (ok && kod == 200) { DWORD kullanilabilir = 0; while (WinHttpQueryDataAvailable(istekH, &kullanilabilir) && kullanilabilir > 0) { std::string parca(kullanilabilir, '\0'); DWORD okunan = 0; if (!WinHttpReadData(istekH, parca.data(), kullanilabilir, &okunan) || okunan == 0) break; cevap.append(parca.data(), okunan); } } WinHttpCloseHandle(istekH); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); if (!ok || kod != 200) { hata = ok ? ("Sunucu reddetti (HTTP " + std::to_string(kod) + ")") : "Istek gonderilemedi"; return false; } sj::ondemand::parser ayristirici; sj::padded_string dolu(cevap); sj::ondemand::document kok; sj::ondemand::array dizi; if (ayristirici.iterate(dolu).get(kok) != sj::SUCCESS || kok.get_array().get(dizi) != sj::SUCCESS) { hata = "Karar cevabi okunamadi"; return false; } for (auto e : dizi) { sj::ondemand::object o; if (e.get_object().get(o) != sj::SUCCESS) continue; std::string_view seri; bool engelli = false; if (o["seri"].get_string().get(seri) != sj::SUCCESS) continue; o["engelli"].get_bool().get(engelli); if (engelli) engelliler.emplace_back(seri); } return true; } bool yonetimEngelDegistir(const CihazOzet& cihaz, bool engelli, const std::string& yoneticiToken, std::string& hata) { const std::string seri = cihaz.cihazKimlik; // karar anahtarı: PC kimliği if (yoneticiToken.empty()) { hata = "Yonetici token bos"; return false; } if (seri.empty()) { hata = "Bilgisayar kimligi bos"; return false; } UrlParca u; if (!urlCoz(g_http.sunucu, u)) { hata = "Gecersiz sunucu adresi"; return false; } HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansYonetim/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { hata = "WinHttpOpen basarisiz"; return false; } std::wstring whost(u.host.begin(), u.host.end()); HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), (INTERNET_PORT)u.port, 0); HINTERNET istekH = baglanti ? WinHttpOpenRequest(baglanti, L"POST", L"/lisans-kontrol/api/karar", nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0) : nullptr; if (!istekH) { if (baglanti) WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); hata = "Baglanti kurulamadi"; return false; } if (u.https) { DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(istekH, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec)); } const std::string govde = "{\"kimlik\":\"" + kacisla(cihaz.cihazKimlik) + "\",\"seri\":\"" + kacisla(seri) + "\",\"engelli\":" + (engelli ? "true" : "false") + ",\"mesaj\":\"\"}"; const std::wstring tokenW(yoneticiToken.begin(), yoneticiToken.end()); const std::wstring basliklar = L"Content-Type: application/json\r\nX-Token: " + tokenW; const BOOL ok = WinHttpSendRequest(istekH, basliklar.c_str(), (DWORD)-1, (LPVOID)govde.data(), (DWORD)govde.size(), (DWORD)govde.size(), 0) && WinHttpReceiveResponse(istekH, nullptr); DWORD kod = 0, kb = sizeof(kod); if (ok) WinHttpQueryHeaders(istekH, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &kb, WINHTTP_NO_HEADER_INDEX); WinHttpCloseHandle(istekH); WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); if (!ok || kod != 200) { hata = ok ? ("Sunucu reddetti (HTTP " + std::to_string(kod) + ")") : "Istek gonderilemedi"; return false; } gunluk::bilgi("Izin karari gonderildi: {}", seri); return true; } bool yonetimFklYaz(const CihazOzet& cihaz, const std::string& fklMetin, i64 bitisUnix, std::string& hata) { if (!g_http.oturumAcik) { hata = "Once oturum acin"; return false; } if (cihaz.cihazKimlik.empty()) { hata = "Cihaz secilmedi"; return false; } if (fklMetin.empty()) { hata = "Lisans metni bos"; return false; } // Sunucuda tek kayit (_id=cihaz). Icerik SIFRELI .fkl: sunucu acamaz, yalniz tasir. std::string j = "{"; j += "\"_id\":\"" + kacisla(cihaz.cihazKimlik) + "\""; j += ",\"cihaz\":\"" + kacisla(cihaz.cihazKimlik) + "\""; j += ",\"bitis\":" + std::to_string(bitisUnix); j += ",\"fkl\":\"" + kacisla(fklMetin) + "\""; j += ",\"aktif\":true"; j += ",\"uretimZamani\":" + std::to_string((i64)std::time(nullptr)); j += "}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadLisanslari.ekle", j, cevap, kod, hata)) return false; { std::string sunucuMesaj; if (cevapHataliMi(cevap, sunucuMesaj)) { hata = "Sunucu lisansi yazmadi: " + sunucuMesaj; gunluk::uyari("Lisans yazilamadi ({}): {}", cihaz.cihazKimlik, sunucuMesaj); return false; } } // Cihaz bagliysa aninda yenile; bagli degilse kayit kalir, baglaninca alinir. std::string uyari; yonetimKomutGonder(cihaz, "lisans.yenile", "Lisans guncellendi", uyari); gunluk::bilgi("Sifreli lisans yazildi: {}", cihaz.cihazKimlik); return true; } bool yonetimKomutYetkilendir(const CihazOzet& cihaz, const std::vector& komutlar, std::string& hata) { if (komutlar.empty()) { hata = "Komut secilmedi"; return false; } std::string mesaj; for (std::size_t i = 0; i < komutlar.size(); ++i) { if (i) mesaj += ","; mesaj += komutlar[i]; } return yonetimKomutGonder(cihaz, "lisans.komutlari", mesaj, hata); } bool yonetimKilitle(const CihazOzet& cihaz, bool kilitli, const std::string& mesaj, std::string& hata) { return yonetimKomutGonder(cihaz, kilitli ? "lisans.kilitle" : "lisans.kilitac", mesaj, hata); } // ?? Beyaz liste: uzaktan calistirilabilecek komutlar ??????????????????????????? const std::vector& yonetimBeyazListe() { static const std::vector v = { // Lisans yonetimi "lisans.yenile", "lisans.komutlari", "lisans.kilitle", "lisans.kilitac", // Gorunum (goruntu/kamera; model degistirmez) "gorunum.isiklar", "gorunum.malzeme", "gorunum.secimi_cercevele", "gorunum.tumunu_sigdir", "gorunum.izometrik", "gorunum.ust", "gorunum.on", "gorunum.ortografik", "gorunum.nesneye_yakinlas", "gorunum.gizle", "gorunum.hover_kart", "gorunum.baglam_hud", "gorunum.perf_hud", // Secim kipleri ve akilli secim (yalniz secim; model degistirmez) "secim.kip_govde", "secim.kip_yuz", "secim.kip_kenar", "secim.kip_nokta", "secim.akilli_tanjant_yuz", "secim.akilli_koplanar_yuz", "secim.akilli_paralel_yuz", "secim.akilli_benzer_yuz", "secim.akilli_kenar_zinciri", "secim.akilli_benzer_kenar", // Analiz (salt okuma; dosya acan cad_fark/web_fark haric) "analiz.cekme_acisi", "analiz.undercut", "analiz.et_kalinligi", "analiz.radius", "analiz.kurvatur", "analiz.sureklilik", "analiz.zebra", "analiz.yansima_hatti", "analiz.kesit_duzlemi", "analiz.kesit_listesi", // Olcum "olcum.pencere", "olcum.kumpas", "olcum.cetvel", "olcum.zincir", "olcum.temizle", // Araclar (guvenli) "arac.guncelleme", "arac.geri_bildirim", }; return v; } } // namespace fiko