nixos/users-groups: check format of passwd entries

Things will get quite broken if an /etc/passwd entry contains a
colon (which terminates a field), or a newline (which terminates a
record). I know because I just accidentally made a user whose home
directory path contained a newline!

So let's make sure that can't happen.

Changed files
+14 -8
nixos
modules
+14 -8
nixos/modules/config/users-groups.nix
···
ids = config.ids;
cfg = config.users;
+
isPasswdCompatible = str: !(hasInfix ":" str || hasInfix "\n" str);
+
passwdEntry = type: lib.types.addCheck type isPasswdCompatible // {
+
name = "passwdEntry ${type.name}";
+
description = "${type.description}, not containing newlines or colons";
+
};
+
# Check whether a password hash will allow login.
allowsLogin = hash:
hash == "" # login without password
···
options = {
name = mkOption {
-
type = types.str;
+
type = passwdEntry types.str;
apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
description = ''
The name of the user account. If undefined, the name of the
···
};
description = mkOption {
-
type = types.str;
+
type = passwdEntry types.str;
default = "";
example = "Alice Q. User";
description = ''
···
};
home = mkOption {
-
type = types.path;
+
type = passwdEntry types.path;
default = "/var/empty";
description = "The user's home directory.";
};
···
};
shell = mkOption {
-
type = types.nullOr (types.either types.shellPackage types.path);
+
type = types.nullOr (types.either types.shellPackage (passwdEntry types.path));
default = pkgs.shadow;
defaultText = "pkgs.shadow";
example = literalExample "pkgs.bashInteractive";
···
};
hashedPassword = mkOption {
-
type = with types; nullOr str;
+
type = with types; nullOr (passwdEntry str);
default = null;
description = ''
Specifies the hashed password for the user.
···
};
initialHashedPassword = mkOption {
-
type = with types; nullOr str;
+
type = with types; nullOr (passwdEntry str);
default = null;
description = ''
Specifies the initial hashed password for the user, i.e. the
···
options = {
name = mkOption {
-
type = types.str;
+
type = passwdEntry types.str;
description = ''
The name of the group. If undefined, the name of the attribute set
will be used.
···
};
members = mkOption {
-
type = with types; listOf str;
+
type = with types; listOf (passwdEntry str);
default = [];
description = ''
The user names of the group members, added to the