forked from pisilinux-rs/yali-rs
refactor: replace shell-based password handling with secure stdin piping for LUKS and GRUB operations
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "yali-rs"
|
name = "yali-rs"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = "0.27"
|
eframe = "0.27"
|
||||||
|
|||||||
+2
-1
@@ -281,7 +281,7 @@ admin_title = "Root Password"
|
|||||||
admin_description = "Set a password for the root (administrator) account. Leave empty to disable direct root login."
|
admin_description = "Set a password for the root (administrator) account. Leave empty to disable direct root login."
|
||||||
admin_password_label = "Root password:"
|
admin_password_label = "Root password:"
|
||||||
admin_password_confirm_label = "Confirm root password:"
|
admin_password_confirm_label = "Confirm root password:"
|
||||||
admin_password_short = "Root password must be at least 4 characters."
|
admin_password_short = "Root password must be at least 8 characters."
|
||||||
|
|
||||||
# ── Date/Time ──────────────────────────────────────────────
|
# ── Date/Time ──────────────────────────────────────────────
|
||||||
datetime = "Date & Time"
|
datetime = "Date & Time"
|
||||||
@@ -532,3 +532,4 @@ lvm_lv_size = "Size (MB, 0=remaining):"
|
|||||||
lvm_lv_action = "Action"
|
lvm_lv_action = "Action"
|
||||||
|
|
||||||
|
|
||||||
|
oem_change_password_warning = "OEM mode: You must change the default password before continuing."
|
||||||
|
|||||||
+2
-1
@@ -293,7 +293,7 @@ admin_title = "Root Parolası"
|
|||||||
admin_description = "Root (yönetici) hesabı için bir parola belirleyin. Boş bırakılırsa doğrudan root girişi devre dışı kalır."
|
admin_description = "Root (yönetici) hesabı için bir parola belirleyin. Boş bırakılırsa doğrudan root girişi devre dışı kalır."
|
||||||
admin_password_label = "Root parolası:"
|
admin_password_label = "Root parolası:"
|
||||||
admin_password_confirm_label = "Root parolası (tekrar):"
|
admin_password_confirm_label = "Root parolası (tekrar):"
|
||||||
admin_password_short = "Root parolası en az 4 karakter olmalıdır."
|
admin_password_short = "Root parolası en az 8 karakter olmalıdır."
|
||||||
|
|
||||||
# ── Tarih & Saat ────────────────────────────────────────────
|
# ── Tarih & Saat ────────────────────────────────────────────
|
||||||
datetime = "Tarih & Saat"
|
datetime = "Tarih & Saat"
|
||||||
@@ -539,3 +539,4 @@ xfce_thunar_description = "Toplu yeniden adlandırma ve eklentiler ile hızlı v
|
|||||||
|
|
||||||
xfce_settings_title = "XFCE Ayarları"
|
xfce_settings_title = "XFCE Ayarları"
|
||||||
xfce_settings_description = "Masaüstü özelleştirmesi için kapsamlı ayar yöneticisi."
|
xfce_settings_description = "Masaüstü özelleştirmesi için kapsamlı ayar yöneticisi."
|
||||||
|
oem_change_password_warning = "OEM modu: Devam etmeden önce varsayılan şifreyi değiştirmelisiniz."
|
||||||
|
|||||||
@@ -66,6 +66,38 @@ pub fn load(path: &std::path::Path) -> Result<AnswerFile, String> {
|
|||||||
pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState) {
|
pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState) {
|
||||||
use crate::installer::{DiskInfo, PartitionPlan, PartitionTableType};
|
use crate::installer::{DiskInfo, PartitionPlan, PartitionTableType};
|
||||||
|
|
||||||
|
// --- Giriş doğrulama ---
|
||||||
|
// Disk yolu: yalnızca /dev/ ile başlayan, alfanumerik + /._- içeren değerler
|
||||||
|
let disk = &af.install.disk;
|
||||||
|
if !disk.starts_with("/dev/")
|
||||||
|
|| disk.contains("..")
|
||||||
|
|| !disk.chars().all(|c| c.is_alphanumeric() || "/_.-".contains(c))
|
||||||
|
{
|
||||||
|
eprintln!("autoinstall: geçersiz disk yolu reddedildi: '{}'", disk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timezone doğrulama
|
||||||
|
let tz = &af.install.timezone;
|
||||||
|
if tz.is_empty()
|
||||||
|
|| tz.contains("..")
|
||||||
|
|| !tz.chars().all(|c| c.is_alphanumeric() || "/._-+".contains(c))
|
||||||
|
{
|
||||||
|
eprintln!("autoinstall: geçersiz timezone reddedildi: '{}'", tz);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locale doğrulama
|
||||||
|
let locale = &af.install.locale;
|
||||||
|
if locale.is_empty()
|
||||||
|
|| locale.contains("..")
|
||||||
|
|| locale.contains('/')
|
||||||
|
|| !locale.chars().all(|c| c.is_alphanumeric() || "._-@".contains(c))
|
||||||
|
{
|
||||||
|
eprintln!("autoinstall: geçersiz locale reddedildi: '{}'", locale);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Temel alanlar
|
// Temel alanlar
|
||||||
state.timezone = af.install.timezone.clone();
|
state.timezone = af.install.timezone.clone();
|
||||||
state.keyboard_layout = af.install.keyboard.clone();
|
state.keyboard_layout = af.install.keyboard.clone();
|
||||||
|
|||||||
@@ -232,6 +232,9 @@ pub struct GlobalState {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub luks_password: String,
|
pub luks_password: String,
|
||||||
pub oem_mode: bool,
|
pub oem_mode: bool,
|
||||||
|
/// OEM modunda: kullanıcı varsayılan şifreyi değiştirdi mi?
|
||||||
|
/// true olmadan kurulum ilerleyemez.
|
||||||
|
pub oem_password_changed: bool,
|
||||||
pub selected_package_groups: Vec<String>,
|
pub selected_package_groups: Vec<String>,
|
||||||
/// branding.toml [slides.icons] tablosundan yüklenen ikon → dosya URI eşlemesi.
|
/// branding.toml [slides.icons] tablosundan yüklenen ikon → dosya URI eşlemesi.
|
||||||
/// Slideshow bu map üzerinden çalışma zamanında görsel yollarını çözümler.
|
/// Slideshow bu map üzerinden çalışma zamanında görsel yollarını çözümler.
|
||||||
@@ -290,6 +293,7 @@ impl Default for GlobalState {
|
|||||||
encrypt_root: false,
|
encrypt_root: false,
|
||||||
luks_password: String::new(),
|
luks_password: String::new(),
|
||||||
oem_mode: false,
|
oem_mode: false,
|
||||||
|
oem_password_changed: false,
|
||||||
selected_package_groups: Vec::new(),
|
selected_package_groups: Vec::new(),
|
||||||
display_manager: "sddm".to_string(),
|
display_manager: "sddm".to_string(),
|
||||||
autologin: true,
|
autologin: true,
|
||||||
|
|||||||
+175
-40
@@ -220,12 +220,32 @@ impl Job for UnmountBindJob {
|
|||||||
"sys",
|
"sys",
|
||||||
"proc",
|
"proc",
|
||||||
];
|
];
|
||||||
|
let mut errors: Vec<String> = Vec::new();
|
||||||
for subdir in &subdirs {
|
for subdir in &subdirs {
|
||||||
let target = format!("{}/{}", mt, subdir);
|
let target = format!("{}/{}", mt, subdir);
|
||||||
ui.log(format!("umount {}", target));
|
ui.log(format!("umount {}", target));
|
||||||
// Hata görmezden gel; bazıları zaten unmount olmuş olabilir
|
match tokio::process::Command::new("umount")
|
||||||
let _ = tokio::process::Command::new("umount")
|
.arg("-lf").arg(&target).status().await
|
||||||
.arg("-lf").arg(&target).status().await;
|
{
|
||||||
|
Ok(status) if status.success() => {}
|
||||||
|
Ok(status) => {
|
||||||
|
// "not mounted" veya "no such file" hataları beklenen durumlar —
|
||||||
|
// bunları uyarı olarak logla ama kritik hata sayma.
|
||||||
|
ui.log(format!("⚠ umount uyarısı (kritik değil, kod {}): {}", status, target));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Komut çalıştırılamadı — bu gerçek bir sorun.
|
||||||
|
let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
|
||||||
|
ui.log(format!("✗ {}", msg));
|
||||||
|
errors.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"Bind mount temizleme hatası — bazı sanal dosya sistemleri bağlı kalmış olabilir:\n{}",
|
||||||
|
errors.join("\n")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -255,11 +275,14 @@ impl Job for GenerateInitramfsJob {
|
|||||||
let _ = tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" lvm \"\n").await;
|
let _ = tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" lvm \"\n").await;
|
||||||
|
|
||||||
// mkinitcpio: add lvm2 hook
|
// mkinitcpio: add lvm2 hook
|
||||||
|
// Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
|
||||||
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
|
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
|
||||||
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
|
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
|
||||||
if let Ok(content) = tokio::fs::read_to_string(&mkinitcpio_conf).await {
|
if let Ok(content) = tokio::fs::read_to_string(&mkinitcpio_conf).await {
|
||||||
if content.contains("HOOKS=") && !content.contains("lvm2") {
|
if content.contains("HOOKS=") && !content.contains("lvm2") {
|
||||||
let new_content = content.replace("HOOKS=\"", "HOOKS=\"lvm2 ");
|
let new_content = content
|
||||||
|
.replace("HOOKS=\"", "HOOKS=\"lvm2 ")
|
||||||
|
.replace("HOOKS=(", "HOOKS=(lvm2 ");
|
||||||
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
|
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,12 +302,15 @@ impl Job for GenerateInitramfsJob {
|
|||||||
.await.map_err(|e| format!("dracut luks conf yazılamadı: {}", e))?;
|
.await.map_err(|e| format!("dracut luks conf yazılamadı: {}", e))?;
|
||||||
|
|
||||||
// mkinitcpio: add encrypt hook
|
// mkinitcpio: add encrypt hook
|
||||||
|
// Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
|
||||||
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
|
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
|
||||||
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
|
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
|
||||||
let content = tokio::fs::read_to_string(&mkinitcpio_conf)
|
let content = tokio::fs::read_to_string(&mkinitcpio_conf)
|
||||||
.await.unwrap_or_default();
|
.await.unwrap_or_default();
|
||||||
if content.contains("HOOKS=") && !content.contains("encrypt") {
|
if content.contains("HOOKS=") && !content.contains("encrypt") {
|
||||||
let new_content = content.replace("HOOKS=\"", "HOOKS=\"encrypt ");
|
let new_content = content
|
||||||
|
.replace("HOOKS=\"", "HOOKS=\"encrypt ")
|
||||||
|
.replace("HOOKS=(", "HOOKS=(encrypt ");
|
||||||
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
|
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,18 +357,49 @@ impl Job for InstallGrubJob {
|
|||||||
let grub_d = format!("{}/etc/grub.d", self.mount);
|
let grub_d = format!("{}/etc/grub.d", self.mount);
|
||||||
let _ = tokio::fs::create_dir_all(&grub_d).await;
|
let _ = tokio::fs::create_dir_all(&grub_d).await;
|
||||||
|
|
||||||
// GRUB şifresi (varsa) — bcrypt hash ile /etc/grub.d/40_custom yazılır
|
// GRUB şifresi (varsa) — grub-mkpasswd-pbkdf2 ile hash üretilir,
|
||||||
|
// düz metin asla diske yazılmaz.
|
||||||
if !self.password.is_empty() {
|
if !self.password.is_empty() {
|
||||||
ui.log("GRUB şifresi yapılandırılıyor…");
|
ui.log("GRUB şifresi PBKDF2 ile hashleniyor…");
|
||||||
|
|
||||||
|
// grub-mkpasswd-pbkdf2 şifreyi stdin'den iki kez okur (şifre + doğrulama)
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
let pw_input = format!("{}\n{}\n", self.password, self.password);
|
||||||
|
let mut child = tokio::process::Command::new("grub-mkpasswd-pbkdf2")
|
||||||
|
.stdin(std::process::Stdio::piped())
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.stderr(std::process::Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("grub-mkpasswd-pbkdf2 başlatılamadı: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(pw_input.as_bytes()).await
|
||||||
|
.map_err(|e| format!("grub-mkpasswd-pbkdf2 stdin hatası: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child.wait_with_output().await
|
||||||
|
.map_err(|e| format!("grub-mkpasswd-pbkdf2 bekleme hatası: {}", e))?;
|
||||||
|
|
||||||
|
// Çıktıdan "grub.pbkdf2.sha512...." token'ını ayrıştır
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let hash = stdout
|
||||||
|
.lines()
|
||||||
|
.find_map(|l| {
|
||||||
|
let pos = l.find("grub.pbkdf2")?;
|
||||||
|
Some(l[pos..].trim().to_string())
|
||||||
|
})
|
||||||
|
.ok_or("grub-mkpasswd-pbkdf2 çıktısında hash bulunamadı")?;
|
||||||
|
|
||||||
let custom_grub = format!(
|
let custom_grub = format!(
|
||||||
"set superusers=\"root\"\npassword_pbkdf2 root {} \n",
|
"#!/bin/sh\nset superusers=\"root\"\npassword_pbkdf2 root {}\n",
|
||||||
self.password
|
hash
|
||||||
);
|
);
|
||||||
let pw_path = format!("{}/etc/grub.d/40_custom", self.mount);
|
let pw_path = format!("{}/etc/grub.d/40_custom", self.mount);
|
||||||
tokio::fs::write(&pw_path, custom_grub)
|
tokio::fs::write(&pw_path, custom_grub)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("GRUB şifre dosyası yazılamadı: {}", e))?;
|
.map_err(|e| format!("GRUB şifre dosyası yazılamadı: {}", e))?;
|
||||||
run_cmd("chmod", &["+x", &pw_path]).await?;
|
run_cmd("chmod", &["+x", &pw_path]).await?;
|
||||||
|
ui.log("GRUB şifresi PBKDF2 hash olarak yazıldı.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRUB timeout ayarı
|
// GRUB timeout ayarı
|
||||||
@@ -552,6 +609,79 @@ async fn run_chroot(mount: &str, cmd: &[&str]) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// LUKS bölümünü güvenli şekilde formatlar.
|
||||||
|
///
|
||||||
|
/// Şifre, `sh -c` + string interpolasyon yerine doğrudan `stdin` pipe'ı
|
||||||
|
/// üzerinden iletilir; böylece shell injection saldırıları önlenir.
|
||||||
|
async fn run_luks_format(device: &str, password: &str) -> Result<(), String> {
|
||||||
|
if DEMO_MODE.load(Ordering::Relaxed) {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
let mut child = tokio::process::Command::new("cryptsetup")
|
||||||
|
.args([
|
||||||
|
"luksFormat", "-q", "--type", "luks2",
|
||||||
|
"--key-file", "-",
|
||||||
|
device,
|
||||||
|
])
|
||||||
|
.stdin(std::process::Stdio::piped())
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("cryptsetup luksFormat başlatılamadı: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(password.as_bytes()).await
|
||||||
|
.map_err(|e| format!("cryptsetup stdin yazma hatası: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child.wait_with_output().await
|
||||||
|
.map_err(|e| format!("cryptsetup bekleme hatası: {}", e))?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LUKS bölümünü güvenli şekilde açar (open).
|
||||||
|
///
|
||||||
|
/// Şifre, shell interpolasyon yerine `stdin` pipe'ı üzerinden iletilir.
|
||||||
|
async fn run_luks_open(device: &str, name: &str, password: &str) -> Result<(), String> {
|
||||||
|
if DEMO_MODE.load(Ordering::Relaxed) {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
let mut child = tokio::process::Command::new("cryptsetup")
|
||||||
|
.args(["open", "--key-file", "-", device, name])
|
||||||
|
.stdin(std::process::Stdio::piped())
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("cryptsetup open başlatılamadı: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(password.as_bytes()).await
|
||||||
|
.map_err(|e| format!("cryptsetup open stdin yazma hatası: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child.wait_with_output().await
|
||||||
|
.map_err(|e| format!("cryptsetup open bekleme hatası: {}", e))?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Bölümlendirme öncesi diski hazırlar:
|
/// Bölümlendirme öncesi diski hazırlar:
|
||||||
/// mevcut bağlamaları ve swap'ı ayırır, ardından kernel'e yeni
|
/// mevcut bağlamaları ve swap'ı ayırır, ardından kernel'e yeni
|
||||||
/// bölüm tablosunu duyurarak aygıt düğümlerinin hazır olmasını bekler.
|
/// bölüm tablosunu duyurarak aygıt düğümlerinin hazır olmasını bekler.
|
||||||
@@ -791,25 +921,17 @@ impl Job for PartitionJob {
|
|||||||
run_cmd("swapon", &[&swap_part]).await?;
|
run_cmd("swapon", &[&swap_part]).await?;
|
||||||
|
|
||||||
// LUKS şifreleme (kök bölümü)
|
// LUKS şifreleme (kök bölümü)
|
||||||
|
// Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
|
||||||
|
// iletilir; `sh -c` + string interpolasyon kullanılmaz.
|
||||||
let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() {
|
let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() {
|
||||||
let luks_name = "cryptroot".to_string();
|
let luks_name = "cryptroot";
|
||||||
ui.log(format!("cryptsetup luksFormat {}…", root_part));
|
ui.log(format!("cryptsetup luksFormat {}…", root_part));
|
||||||
let _ = run_cmd("cryptsetup", &[
|
run_luks_format(&root_part, &self.plan.luks_password).await
|
||||||
"luksFormat", "-q", "--type", "luks2", &root_part,
|
.map_err(|e| format!("LUKS format hatası: {}", e))?;
|
||||||
"--key-file", "-",
|
|
||||||
]).await;
|
|
||||||
// Burada batch modda çalıştığımız için şifreyi pipe ile veremeyiz;
|
|
||||||
// bunun yerine echo -n şifre | cryptsetup ile çalışır
|
|
||||||
let password_input = format!("echo -n '{}' | cryptsetup luksFormat -q --type luks2 {} --key-file=-", self.plan.luks_password, root_part);
|
|
||||||
let _ = tokio::process::Command::new("sh")
|
|
||||||
.args(["-c", &password_input])
|
|
||||||
.output().await;
|
|
||||||
|
|
||||||
ui.log(format!("cryptsetup open {} {}…", root_part, luks_name));
|
ui.log(format!("cryptsetup open {} {}…", root_part, luks_name));
|
||||||
let open_cmd = format!("echo -n '{}' | cryptsetup open {} {}", self.plan.luks_password, root_part, luks_name);
|
run_luks_open(&root_part, luks_name, &self.plan.luks_password).await
|
||||||
let _ = tokio::process::Command::new("sh")
|
.map_err(|e| format!("LUKS open hatası: {}", e))?;
|
||||||
.args(["-c", &open_cmd])
|
|
||||||
.output().await;
|
|
||||||
|
|
||||||
format!("/dev/mapper/{}", luks_name)
|
format!("/dev/mapper/{}", luks_name)
|
||||||
} else {
|
} else {
|
||||||
@@ -1289,6 +1411,17 @@ impl Job for ConfigureLocaleJob {
|
|||||||
fn name(&self) -> &str { "job_locale" }
|
fn name(&self) -> &str { "job_locale" }
|
||||||
|
|
||||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||||
|
// Locale değeri doğrulama: yalnızca alfanumerik, '.', '_', '-', '@'
|
||||||
|
// karakterlerine izin ver. Yol manipülasyonu ve injection önlenir.
|
||||||
|
let locale_valid = !self.locale.is_empty()
|
||||||
|
&& !self.locale.contains("..")
|
||||||
|
&& !self.locale.contains('/')
|
||||||
|
&& !self.locale.contains('\n')
|
||||||
|
&& self.locale.chars().all(|c| c.is_alphanumeric() || "._-@".contains(c));
|
||||||
|
if !locale_valid {
|
||||||
|
return Err(format!("Geçersiz locale değeri: '{}'", self.locale));
|
||||||
|
}
|
||||||
|
|
||||||
let etc_dir = format!("{}/etc", self.mount);
|
let etc_dir = format!("{}/etc", self.mount);
|
||||||
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
||||||
let _ = tokio::fs::create_dir_all(format!("{}/etc/env.d", self.mount)).await;
|
let _ = tokio::fs::create_dir_all(format!("{}/etc/env.d", self.mount)).await;
|
||||||
@@ -1331,9 +1464,9 @@ impl Job for ConfigureLocaleJob {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("/etc/locale-gen yazılamadı: {}", e))?;
|
.map_err(|e| format!("/etc/locale-gen yazılamadı: {}", e))?;
|
||||||
|
|
||||||
// locale-gen çalıştır
|
// locale-gen çalıştır — kurulum sonrası locale desteği için zorunlu
|
||||||
ui.log("locale-gen çalıştırılıyor…");
|
ui.log("locale-gen çalıştırılıyor…");
|
||||||
//run_chroot(&self.mount, &["locale-gen"]).await?;
|
run_chroot(&self.mount, &["locale-gen"]).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1350,6 +1483,16 @@ impl Job for ConfigureTimezoneJob {
|
|||||||
fn name(&self) -> &str { "job_timezone" }
|
fn name(&self) -> &str { "job_timezone" }
|
||||||
|
|
||||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||||
|
// Path traversal koruması: timezone değeri yalnızca alfanumerik,
|
||||||
|
// '/', '_', '-', '+' karakterlerine izin verir; ".." asla geçemez.
|
||||||
|
let tz = &self.timezone;
|
||||||
|
let tz_valid = !tz.is_empty()
|
||||||
|
&& !tz.contains("..")
|
||||||
|
&& tz.chars().all(|c| c.is_alphanumeric() || "/._-+".contains(c));
|
||||||
|
if !tz_valid {
|
||||||
|
return Err(format!("Geçersiz timezone değeri: '{}'", tz));
|
||||||
|
}
|
||||||
|
|
||||||
// Sembolik link: /etc/localtime → /usr/share/zoneinfo/<tz>
|
// Sembolik link: /etc/localtime → /usr/share/zoneinfo/<tz>
|
||||||
let link_target = format!("/usr/share/zoneinfo/{}", self.timezone);
|
let link_target = format!("/usr/share/zoneinfo/{}", self.timezone);
|
||||||
let link_path = format!("{}/etc/localtime", self.mount);
|
let link_path = format!("{}/etc/localtime", self.mount);
|
||||||
@@ -1775,6 +1918,8 @@ impl Job for CustomPartitionJob {
|
|||||||
let path = &part.device;
|
let path = &part.device;
|
||||||
|
|
||||||
// LUKS şifreleme (kök veya isteğe bağlı bölümler)
|
// LUKS şifreleme (kök veya isteğe bağlı bölümler)
|
||||||
|
// Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
|
||||||
|
// iletilir; `sh -c` + string interpolasyon kullanılmaz.
|
||||||
let fs_device = if part.encrypt && !part.luks_password.is_empty() {
|
let fs_device = if part.encrypt && !part.luks_password.is_empty() {
|
||||||
let luks_name = if part.luks_name.is_empty() {
|
let luks_name = if part.luks_name.is_empty() {
|
||||||
format!("crypt_{}", part.mountpoint.replace("/", "_"))
|
format!("crypt_{}", part.mountpoint.replace("/", "_"))
|
||||||
@@ -1784,22 +1929,12 @@ impl Job for CustomPartitionJob {
|
|||||||
let luks_name = luks_name.trim_start_matches('_').to_string();
|
let luks_name = luks_name.trim_start_matches('_').to_string();
|
||||||
|
|
||||||
ui.log(format!("cryptsetup luksFormat {}…", path));
|
ui.log(format!("cryptsetup luksFormat {}…", path));
|
||||||
let format_cmd = format!(
|
run_luks_format(path, &part.luks_password).await
|
||||||
"echo -n '{}' | cryptsetup luksFormat -q --type luks2 {} --key-file=-",
|
.map_err(|e| format!("LUKS format hatası ({}): {}", path, e))?;
|
||||||
part.luks_password, path
|
|
||||||
);
|
|
||||||
let _ = tokio::process::Command::new("sh")
|
|
||||||
.args(["-c", &format_cmd])
|
|
||||||
.output().await;
|
|
||||||
|
|
||||||
ui.log(format!("cryptsetup open {} {}…", path, luks_name));
|
ui.log(format!("cryptsetup open {} {}…", path, luks_name));
|
||||||
let open_cmd = format!(
|
run_luks_open(path, &luks_name, &part.luks_password).await
|
||||||
"echo -n '{}' | cryptsetup open {} {}",
|
.map_err(|e| format!("LUKS open hatası ({}): {}", path, e))?;
|
||||||
part.luks_password, path, luks_name
|
|
||||||
);
|
|
||||||
let _ = tokio::process::Command::new("sh")
|
|
||||||
.args(["-c", &open_cmd])
|
|
||||||
.output().await;
|
|
||||||
|
|
||||||
format!("/dev/mapper/{}", luks_name)
|
format!("/dev/mapper/{}", luks_name)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -245,6 +245,9 @@ impl Default for YaliApp {
|
|||||||
if !branding.oem.default_password.is_empty() {
|
if !branding.oem.default_password.is_empty() {
|
||||||
app.state.password = branding.oem.default_password.clone();
|
app.state.password = branding.oem.default_password.clone();
|
||||||
app.state.password_confirm = branding.oem.default_password.clone();
|
app.state.password_confirm = branding.oem.default_password.clone();
|
||||||
|
// OEM varsayılan şifresi yüklendi; kullanıcı değiştirene kadar
|
||||||
|
// ilerleme engellenir.
|
||||||
|
app.state.oem_password_changed = false;
|
||||||
}
|
}
|
||||||
// OEM ön tanımlı disk planı varsa?
|
// OEM ön tanımlı disk planı varsa?
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-3
@@ -193,12 +193,24 @@ impl InstallerStep for UsersStep {
|
|||||||
|
|
||||||
// Şifre
|
// Şifre
|
||||||
ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text()));
|
ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text()));
|
||||||
|
// OEM modunda varsayılan şifre uyarısı göster
|
||||||
|
if state.oem_mode && !state.oem_password_changed {
|
||||||
|
ui.colored_label(
|
||||||
|
theme::c_error(),
|
||||||
|
t!("oem_change_password_warning"),
|
||||||
|
);
|
||||||
|
}
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
let before = state.password.clone();
|
||||||
if self.show_password {
|
if self.show_password {
|
||||||
ui.text_edit_singleline(&mut state.password);
|
ui.text_edit_singleline(&mut state.password);
|
||||||
} else {
|
} else {
|
||||||
ui.add(egui::TextEdit::singleline(&mut state.password).password(true));
|
ui.add(egui::TextEdit::singleline(&mut state.password).password(true));
|
||||||
}
|
}
|
||||||
|
// Kullanıcı şifreyi değiştirdiyse OEM bayrağını kaldır
|
||||||
|
if state.oem_mode && state.password != before {
|
||||||
|
state.oem_password_changed = true;
|
||||||
|
}
|
||||||
if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() {
|
if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() {
|
||||||
self.show_password = !self.show_password;
|
self.show_password = !self.show_password;
|
||||||
}
|
}
|
||||||
@@ -280,7 +292,7 @@ impl InstallerStep for UsersStep {
|
|||||||
if ui.small_button(if self.show_root_password { "🙈" } else { "👁" }).clicked() {
|
if ui.small_button(if self.show_root_password { "🙈" } else { "👁" }).clicked() {
|
||||||
self.show_root_password = !self.show_root_password;
|
self.show_root_password = !self.show_root_password;
|
||||||
}
|
}
|
||||||
if !state.root_password.is_empty() && state.root_password.len() < 4 {
|
if !state.root_password.is_empty() && state.root_password.len() < 8 {
|
||||||
ui.colored_label(theme::c_error(), t!("admin_password_short"));
|
ui.colored_label(theme::c_error(), t!("admin_password_short"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -321,9 +333,14 @@ impl InstallerStep for UsersStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||||
|
// OEM modunda varsayılan şifre değiştirilmeden ilerlenmez.
|
||||||
|
if state.oem_mode && !state.oem_password_changed {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let user_ok = is_valid_username(&state.username)
|
let user_ok = is_valid_username(&state.username)
|
||||||
&& is_valid_hostname(&state.hostname)
|
&& is_valid_hostname(&state.hostname)
|
||||||
&& state.password.len() >= 4
|
&& state.password.len() >= 8
|
||||||
&& state.password == state.password_confirm;
|
&& state.password == state.password_confirm;
|
||||||
|
|
||||||
if self.use_same_for_admin {
|
if self.use_same_for_admin {
|
||||||
@@ -331,7 +348,7 @@ impl InstallerStep for UsersStep {
|
|||||||
} else {
|
} else {
|
||||||
user_ok && (
|
user_ok && (
|
||||||
state.root_password.is_empty()
|
state.root_password.is_empty()
|
||||||
|| (state.root_password.len() >= 4 && state.root_password == state.root_password_confirm)
|
|| (state.root_password.len() >= 8 && state.root_password == state.root_password_confirm)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user