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.

+14 -8
+14 -8
nixos/modules/config/users-groups.nix
··· 6 ids = config.ids; 7 cfg = config.users; 8 9 # Check whether a password hash will allow login. 10 allowsLogin = hash: 11 hash == "" # login without password ··· 54 options = { 55 56 name = mkOption { 57 - type = types.str; 58 apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x; 59 description = '' 60 The name of the user account. If undefined, the name of the ··· 63 }; 64 65 description = mkOption { 66 - type = types.str; 67 default = ""; 68 example = "Alice Q. User"; 69 description = '' ··· 128 }; 129 130 home = mkOption { 131 - type = types.path; 132 default = "/var/empty"; 133 description = "The user's home directory."; 134 }; ··· 157 }; 158 159 shell = mkOption { 160 - type = types.nullOr (types.either types.shellPackage types.path); 161 default = pkgs.shadow; 162 defaultText = "pkgs.shadow"; 163 example = literalExample "pkgs.bashInteractive"; ··· 217 }; 218 219 hashedPassword = mkOption { 220 - type = with types; nullOr str; 221 default = null; 222 description = '' 223 Specifies the hashed password for the user. ··· 251 }; 252 253 initialHashedPassword = mkOption { 254 - type = with types; nullOr str; 255 default = null; 256 description = '' 257 Specifies the initial hashed password for the user, i.e. the ··· 323 options = { 324 325 name = mkOption { 326 - type = types.str; 327 description = '' 328 The name of the group. If undefined, the name of the attribute set 329 will be used. ··· 340 }; 341 342 members = mkOption { 343 - type = with types; listOf str; 344 default = []; 345 description = '' 346 The user names of the group members, added to the
··· 6 ids = config.ids; 7 cfg = config.users; 8 9 + isPasswdCompatible = str: !(hasInfix ":" str || hasInfix "\n" str); 10 + passwdEntry = type: lib.types.addCheck type isPasswdCompatible // { 11 + name = "passwdEntry ${type.name}"; 12 + description = "${type.description}, not containing newlines or colons"; 13 + }; 14 + 15 # Check whether a password hash will allow login. 16 allowsLogin = hash: 17 hash == "" # login without password ··· 60 options = { 61 62 name = mkOption { 63 + type = passwdEntry types.str; 64 apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x; 65 description = '' 66 The name of the user account. If undefined, the name of the ··· 69 }; 70 71 description = mkOption { 72 + type = passwdEntry types.str; 73 default = ""; 74 example = "Alice Q. User"; 75 description = '' ··· 134 }; 135 136 home = mkOption { 137 + type = passwdEntry types.path; 138 default = "/var/empty"; 139 description = "The user's home directory."; 140 }; ··· 163 }; 164 165 shell = mkOption { 166 + type = types.nullOr (types.either types.shellPackage (passwdEntry types.path)); 167 default = pkgs.shadow; 168 defaultText = "pkgs.shadow"; 169 example = literalExample "pkgs.bashInteractive"; ··· 223 }; 224 225 hashedPassword = mkOption { 226 + type = with types; nullOr (passwdEntry str); 227 default = null; 228 description = '' 229 Specifies the hashed password for the user. ··· 257 }; 258 259 initialHashedPassword = mkOption { 260 + type = with types; nullOr (passwdEntry str); 261 default = null; 262 description = '' 263 Specifies the initial hashed password for the user, i.e. the ··· 329 options = { 330 331 name = mkOption { 332 + type = passwdEntry types.str; 333 description = '' 334 The name of the group. If undefined, the name of the attribute set 335 will be used. ··· 346 }; 347 348 members = mkOption { 349 + type = with types; listOf (passwdEntry str); 350 default = []; 351 description = '' 352 The user names of the group members, added to the