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