diff --git a/rust/src/normalization.rs b/rust/src/normalization.rs index e8cdc23baf..513464191f 100644 --- a/rust/src/normalization.rs +++ b/rust/src/normalization.rs @@ -13,7 +13,9 @@ use lazy_static::lazy_static; use ostree_ext::gio; use std::convert::TryInto; use std::ffi::{OsStr, OsString}; -use std::io::{Read, Seek, Write}; +use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; +use std::path::Path; +use subprocess::{Exec, Redirection}; lazy_static! { static ref SOURCE_DATE_EPOCH_RAW: Option = std::env::var_os("SOURCE_DATE_EPOCH"); @@ -129,6 +131,26 @@ pub(crate) fn normalize_rpmdb>( } } +#[context("Rewriting /etc/shadow to remove lastchg field")] +pub(crate) fn normalize_etc_shadow(rootfs: &openat::Dir) -> Result<()> { + let mut shadow = rootfs.update_file("usr/etc/shadow", 0o400)?; + let entries = BufReader::new(&mut shadow) + .lines() + .map(|l| { + let l = l?; + Ok(l.split(':').map(|s| s.to_string()).collect()) + }) + .collect::>>>()?; + shadow.seek(SeekFrom::Start(0))?; + shadow.set_len(0)?; + for mut entry in entries { + entry[2] = String::new(); + writeln!(shadow, "{}", entry.join(":"))?; + } + + Ok(()) +} + mod rpmdb_bdb { use super::SOURCE_DATE_EPOCH; use anyhow::{anyhow, Context, Result}; diff --git a/rust/src/passwd.rs b/rust/src/passwd.rs index 2f741bec57..1debd68c9b 100644 --- a/rust/src/passwd.rs +++ b/rust/src/passwd.rs @@ -5,6 +5,7 @@ use crate::cxxrsutil::*; use crate::ffiutil; use crate::nameservice; +use crate::normalization; use crate::treefile::{CheckGroups, CheckPasswd, Treefile}; use anyhow::{anyhow, Context, Result}; use fn_error_context::context; @@ -518,6 +519,11 @@ fn complete_pwgrp(rootfs: &openat::Dir) -> Result<()> { // In actuality, nothing should change /etc/shadow or /etc/gshadow, so // we'll just have to pay the (tiny) cost of re-checksumming. + // /etc/shadow ends up with a timestamp in it thanks to the `lastchg` + // field. This can be made empty safely, especially for accounts that + // are locked. + normalization::normalize_etc_shadow(rootfs)?; + Ok(()) }