// -- 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/core/gunluk.hpp" #include "lisans_yonetim.hpp" 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; 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 // 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(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 (giriş cevabında) ─────────────────────────────────── // Fiber session middleware şunu gönderir: // Set-Cookie: sid=; max-age=86400; path=/; HttpOnly; secure; SameSite=Lax // Oturum sonraki isteklerde `Cookie: sid=` başlığıyla taşınır. // // ⚠️ 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çış ──────────────────────────────────────────────────────────────── 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; } // Basit JSON alan okuyucu ("anahtar":"deger" veya "anahtar":123) bool alanOku(const std::string& json, const char* anahtar, std::string& cikti) { const std::string pat = std::string("\"") + anahtar + "\""; const std::size_t p = json.find(pat); if (p == std::string::npos) return false; std::size_t i = p + pat.size(); while (i < json.size() && (json[i] == ' ' || json[i] == ':')) ++i; if (i >= json.size() || json[i] != '"') return false; ++i; std::string r; while (i < json.size() && json[i] != '"') { if (json[i] == '\\' && i + 1 < json.size()) ++i; r += json[i++]; } cikti = r; return true; } 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 ───────────────────────────────────────────────────────────────── 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(); // Cevap JSON dizi: en dis "[{" ... Her belge icin alanlari oku. std::size_t i = 0; while (true) { const std::size_t b = cevap.find('{', i); if (b == std::string::npos) break; // Belgenin sonunu bul (ice ice {} ve [] yok varsay) const std::size_t e = cevap.find('}', b); if (e == std::string::npos) break; const std::string bel = cevap.substr(b, e - b + 1); i = e + 1; CihazOzet c; std::string v; if (alanOku(bel, "kimlik", v)) c.cihazKimlik = v; if (alanOku(bel, "bilgisayar", v)) c.bilgisayar = v; if (alanOku(bel, "seri", v)) c.seri = v; if (alanOku(bel, "musteri", v)) c.musteri = v; if (alanOku(bel, "surum", v)) c.surum = v; if (alanOku(bel, "ip", v)) c.ip = v; if (alanOku(bel, "tipi", v)) c.tipi = v; if (alanOku(bel, "tarih", v)) c.sonBaglanma = v; if (c.cihazKimlik.empty()) continue; if (c.bilgisayar.empty()) c.bilgisayar = "(bilinmiyor)"; cikti.push_back(std::move(c)); } YonetimDurumu& d = yonetimDurumu(); d.sunucuyaBagli = true; d.yukleniyor = false; d.cevapHatasi.clear(); d.sonHata.clear(); 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; return istek("/fapiv1/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle", govde, cevap, kod, hata); } } // namespace // Once motoru yenile (yeni kurali yuklesin) 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(); // 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 += "}]}"; std::string cevap; int kod = 0; if (!istek("/fapiv1/fikoyt.com.FikoYT.sistem.DB.hareketler.ekle", kural, cevap, kod, hata)) return false; // Motoru yenile (kurali yuklesin) if (!motoruYenile(hata)) { gunluk::uyari("Motor yenilenemedi (60 sn bekleniyor): {}", hata); } // 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; // Kanonik govde + ECDSA imzasi 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; } 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", "lisans.uzat", "lisans.kota.sifirla", // Gorus (guvenli - salt okuma/degistirme) "gorunum.tumunu_sigdir", "gorunum.secimi_cercevele", "gorunum.izometrik", "gorunum.ust", "gorunum.on", "gorunum.ortografik", "gorunum.iziklar", "gorunum.malzeme", // Secim kipleri "secim.kip_govde", "secim.kip_yuz", "secim.kip_kenar", // Analiz (salt okuma) "dosya.fark", "analiz.cekme_acisi", "analiz.kalınligi", "analiz.kurvatur", // Araclar "arac.guncelleme", "arac.geri_bildirim", "sistem.temizle", }; return v; } } // namespace fiko