From afb77e30c596117fb40cfb45389e9d79b5520973 Mon Sep 17 00:00:00 2001 From: "Gerd v. Egidy" Date: Sun, 22 May 2022 15:43:10 +0200 Subject: [PATCH 1/2] sysrescue-initialize.py: Ensure the values given in the config file have the correct types With the new config file merging the user could accidently overwrite the config values with wrong types, for example a boolean with a dict or list. This could lead to the script aborting with an exception. Use explicit type casting and default values to ensure correct operation in this case. This is the same as recently implemented for autorun. Implement a dedicated conversion function for booleans to for example treat a string "0" as False, python by default would interpret it as True. --- .../systemd/scripts/sysrescue-initialize.py | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/airootfs/etc/systemd/scripts/sysrescue-initialize.py b/airootfs/etc/systemd/scripts/sysrescue-initialize.py index 3148b83..cba4984 100755 --- a/airootfs/etc/systemd/scripts/sysrescue-initialize.py +++ b/airootfs/etc/systemd/scripts/sysrescue-initialize.py @@ -31,6 +31,23 @@ def symlink_overwrite(target, link_file): os.replace(tmp.name, link_file) +def strtobool (val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + Function adapted from Pythons distutils.util.py because it will be deprecated soon + Copyright (c) Python Software Foundation; All Rights Reserved + """ + val = str(val).lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return True + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return False + else: + raise ValueError("invalid truth value %r" % (val,)) # ============================================================================== # Initialization @@ -52,34 +69,47 @@ with open(effectivecfg) as file: # ============================================================================== # Sanitize config, initialize variables -# Make sysrescue-initialize work safely without them being defined +# Make sysrescue-initialize work safely without them being defined or have a wrong type # Also show the effective configuration # ============================================================================== print(f"====> Showing the effective global configuration (except clear passwords) ...") -def read_cfg_value(scope, name, printval): +def read_cfg_value(scope, name, defaultval, printval): if not scope in config: - val = None + val = defaultval elif name in config[scope]: - val = config[scope][name] + chkval = config[scope][name] + try: + if isinstance(chkval, list) or isinstance(chkval, dict): + raise TypeError(f"must be a {type(defaultval)}, not a {type(chkval)}") + elif isinstance(defaultval, bool) and not isinstance(chkval, bool): + val = strtobool(chkval) + else: + val = type(defaultval)(chkval) + except (TypeError, ValueError) as e: + if printval: + print(f"config['{scope}']['{name}'] with {chkval} is not the same type as defaultval: {e}") + else: + print(f"config['{scope}']['{name}'] is not the same type as defaultval: {e}") + val = defaultval else: - val = None + val = defaultval if printval: print(f"config['{scope}']['{name}']={val}") return val -setkmap = read_cfg_value('global','setkmap', True) -rootshell = read_cfg_value('global','rootshell', True) -rootpass = read_cfg_value('global','rootpass', False) -rootcryptpass = read_cfg_value('global','rootcryptpass', False) -nofirewall = read_cfg_value('global','nofirewall', True) -noautologin = read_cfg_value('global','noautologin', True) -dostartx = read_cfg_value('global','dostartx', True) -dovnc = read_cfg_value('global','dovnc', True) -vncpass = read_cfg_value('global','vncpass', False) -late_load_srm = read_cfg_value('global','late_load_srm', True) +setkmap = read_cfg_value('global','setkmap', "", True) +rootshell = read_cfg_value('global','rootshell', "", True) +rootpass = read_cfg_value('global','rootpass', "", False) +rootcryptpass = read_cfg_value('global','rootcryptpass', "", False) +nofirewall = read_cfg_value('global','nofirewall', False, True) +noautologin = read_cfg_value('global','noautologin', False, True) +dostartx = read_cfg_value('global','dostartx', False, True) +dovnc = read_cfg_value('global','dovnc', False, True) +vncpass = read_cfg_value('global','vncpass', "", False) +late_load_srm = read_cfg_value('global','late_load_srm', "", True) # ============================================================================== # Apply the effective configuration @@ -87,7 +117,7 @@ late_load_srm = read_cfg_value('global','late_load_srm', True) print(f"====> Applying configuration ...") # Configure keyboard layout if requested in the configuration -if (setkmap != None) and (setkmap != ""): +if setkmap != "": p = subprocess.run(["localectl", "set-keymap", setkmap], text=True) if p.returncode == 0: print (f"Have changed the keymap successfully") @@ -96,7 +126,7 @@ if (setkmap != None) and (setkmap != ""): errcnt+=1 # Configure root login shell if requested in the configuration -if (rootshell != None) and (rootshell != ""): +if rootshell != "": p = subprocess.run(["chsh", "--shell", rootshell, "root"], text=True) if p.returncode == 0: print (f"Have changed the root shell successfully") @@ -105,7 +135,7 @@ if (rootshell != None) and (rootshell != ""): errcnt+=1 # Set the system root password from a clear password -if (rootpass != None) and (rootpass != ""): +if rootpass != "": p = subprocess.run(["chpasswd", "--crypt-method", "SHA512"], text=True, input=f"root:{rootpass}") if p.returncode == 0: print (f"Have changed the root password successfully") @@ -116,7 +146,7 @@ if (rootpass != None) and (rootpass != ""): # Set the system root password from an encrypted password # A password can be encrypted using a one-line python3 command such as: # python3 -c 'import crypt; print(crypt.crypt("MyPassWord123", crypt.mksalt(crypt.METHOD_SHA512)))' -if (rootcryptpass != None) and (rootcryptpass != ""): +if rootcryptpass != "": p = subprocess.run(["chpasswd", "--encrypted"], text=True, input=f"root:{rootcryptpass}") if p.returncode == 0: print (f"Have changed the root password successfully") @@ -155,7 +185,7 @@ if noautologin == True: errcnt+=1 # Set the VNC password from a clear password -if (vncpass != None) and (vncpass != ""): +if vncpass != "": os.makedirs("/root/.vnc", exist_ok = True) p = subprocess.run(["x11vnc", "-storepasswd", vncpass, "/root/.vnc/passwd"], text=True) if p.returncode == 0: @@ -192,7 +222,7 @@ if 'sysconfig' in config and 'ca-trust' in config['sysconfig'] and config['sysco # late-load a SystemRescueModule (SRM) # ============================================================================== -if (late_load_srm != None) and (late_load_srm != ""): +if late_load_srm != "": print(f"====> Late-loading SystemRescueModule (SRM) ...") subprocess.run(["/usr/share/sysrescue/bin/load-srm", late_load_srm]) # the SRM could contain changes to systemd units -> let them take effect From cecd6946ff61b776d7ceca01c1d43b9441e5470e Mon Sep 17 00:00:00 2001 From: "Gerd v. Egidy" Date: Sun, 22 May 2022 15:49:10 +0200 Subject: [PATCH 2/2] sysrescue-autorun: improve type casting for booleans The recently implemented type casting code doesn't work intuitively for booleans: for example it treats a string "0" as False and doesn't show an error message for it. So the user might be unaware of this. So add a dedicated conversion function for booleans. Also add checks for conversions from dicts and lists, forbid to cast them to strings because that is most probably not what the user wants. --- .../etc/systemd/scripts/sysrescue-autorun | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/airootfs/etc/systemd/scripts/sysrescue-autorun b/airootfs/etc/systemd/scripts/sysrescue-autorun index 4fea826..960d0af 100755 --- a/airootfs/etc/systemd/scripts/sysrescue-autorun +++ b/airootfs/etc/systemd/scripts/sysrescue-autorun @@ -124,12 +124,36 @@ def search_autoruns(dirname, suffixes, copyfilefct): found+=1 return found +def strtobool (val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + Function adapted from Pythons distutils.util.py because it will be deprecated soon + Copyright (c) Python Software Foundation; All Rights Reserved + """ + val = str(val).lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return True + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return False + else: + raise ValueError("invalid truth value %r" % (val,)) + def read_cfg_value(name, defaultval): if name in config: + chkval = config[name] try: - val = type(defaultval)(config[name]) + if isinstance(chkval, list) or isinstance(chkval, dict): + raise TypeError(f"must be a {type(defaultval)}, not a {type(chkval)}") + elif isinstance(defaultval, bool) and not isinstance(chkval, bool): + val = strtobool(chkval) + else: + val = type(defaultval)(chkval) except (TypeError, ValueError) as e: - writemsg(f"{name} with {config[name]} is not the same type as defaultval: {e}") + writemsg(f"config['{name}'] with {chkval} is not the same type as defaultval: {e}") val = defaultval else: val = defaultval