from pathlib import PurePosixPath
from typing import Any
+from pytest_mh import MultihostUtility
from pytest_mh.conn import ProcessLogLevel
from .base import BaseHost, BaseLinuxHost
if x["origin"] == origin:
self._verify_files.remove(x)
break
+
+
+class LoginDefsConfig(MultihostUtility[ShadowHost]):
+ """
+ Manage /etc/login.defs configuration.
+
+ Usage:
+ # Set a value
+ shadow.login_defs["CREATE_HOME"] = "no"
+
+ # Get a value
+ value = shadow.login_defs["CREATE_HOME"]
+
+ # Remove an option
+ del shadow.login_defs["CREATE_HOME"]
+ """
+
+ def __init__(self, host: ShadowHost) -> None:
+ super().__init__(host)
+ self._backup_path: str | None = None
+
+ def setup(self) -> None:
+ """
+ Backup the original /etc/login.defs file.
+ Called automatically by the framework.
+ """
+ if self.host.fs.exists("/etc/login.defs"):
+ result = self.host.conn.run(
+ """
+ set -ex
+ backup_path=`mktemp`
+ cp /etc/login.defs $backup_path
+ echo $backup_path
+ """,
+ log_level=ProcessLogLevel.Error,
+ )
+ self._backup_path = result.stdout_lines[-1].strip()
+
+ def teardown(self) -> None:
+ """
+ Restore the original /etc/login.defs file.
+ Called automatically by the framework.
+ """
+ if self._backup_path and self.host.fs.exists(self._backup_path):
+ self.host.conn.run(f"mv {self._backup_path} /etc/login.defs")
+
+ def __getitem__(self, key: str) -> str:
+ """
+ Get a value from /etc/login.defs.
+
+ :param key: Option name (e.g., 'CREATE_HOME')
+ :type key: str
+ :return: Current value
+ :rtype: str
+ """
+ result = self.host.conn.run(
+ f"grep -E '^{key}\\s+' /etc/login.defs | tail -1 | awk '{{print $2}}'", raise_on_error=False
+ )
+ if result.rc != 0 or not result.stdout.strip():
+ return ""
+ return result.stdout.strip()
+
+ def __setitem__(self, key: str, value: str) -> None:
+ """
+ Set a value in /etc/login.defs.
+
+ :param key: Option name (e.g., 'CREATE_HOME')
+ :type key: str
+ :param value: Value to set (can contain special characters like /, &, etc.)
+ :type value: str
+ """
+ self.logger.info(f"Setting {key}={value} in /etc/login.defs on {self.host.hostname}")
+
+ # Escape special characters for awk
+ escaped_value = value.replace("/", "\\/")
+
+ self.host.conn.run(
+ f"""
+ if grep -q '^{key}\\s' /etc/login.defs; then
+ awk -v key="{key}" -v val="{escaped_value}" \\
+ '{{if ($0 ~ "^" key "\\\\s") print key " " val; else print $0}}' \\
+ /etc/login.defs > /etc/login.defs.tmp && mv /etc/login.defs.tmp /etc/login.defs
+ elif grep -q '^#\\s*{key}\\s' /etc/login.defs; then
+ awk -v key="{key}" -v val="{escaped_value}" \\
+ '{{if ($0 ~ "^#\\\\s*" key "\\\\s") print key " " val; else print $0}}' \\
+ /etc/login.defs > /etc/login.defs.tmp && mv /etc/login.defs.tmp /etc/login.defs
+ else
+ echo '{key} {value}' >> /etc/login.defs
+ fi
+ """,
+ log_level=ProcessLogLevel.Error,
+ )
+
+ def __delitem__(self, key: str) -> None:
+ """
+ Remove an option from /etc/login.defs (comment it out).
+
+ :param key: Option name to remove
+ :type key: str
+ """
+ self.logger.info(f"Removing {key} from /etc/login.defs on {self.host.hostname}")
+
+ self.host.conn.run(f"sed -i 's/^{key}\\s.*/#&/' /etc/login.defs", log_level=ProcessLogLevel.Error)