|
|
|
@ -1,11 +1,10 @@ |
|
|
|
|
use strict; |
|
|
|
|
use warnings; |
|
|
|
|
use File::Path qw(make_path); |
|
|
|
|
use File::Slurp; |
|
|
|
|
use Getopt::Long; |
|
|
|
|
use JSON; |
|
|
|
|
|
|
|
|
|
make_path("/var/lib/nixos", { mode => 0755 }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Keep track of deleted uids and gids. |
|
|
|
|
my $uidMapFile = "/var/lib/nixos/uid-map"; |
|
|
|
|
my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {}; |
|
|
|
@ -13,12 +12,19 @@ my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {}; |
|
|
|
|
my $gidMapFile = "/var/lib/nixos/gid-map"; |
|
|
|
|
my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {}; |
|
|
|
|
|
|
|
|
|
my $is_dry = ($ENV{'NIXOS_ACTION'} // "") eq "dry-activate"; |
|
|
|
|
GetOptions("dry-activate" => \$is_dry); |
|
|
|
|
make_path("/var/lib/nixos", { mode => 0755 }) unless $is_dry; |
|
|
|
|
|
|
|
|
|
sub updateFile { |
|
|
|
|
my ($path, $contents, $perms) = @_; |
|
|
|
|
return if $is_dry; |
|
|
|
|
write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sub nscdInvalidate { |
|
|
|
|
system("nscd", "--invalidate", $_[0]) unless $is_dry; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sub hashPassword { |
|
|
|
|
my ($password) = @_; |
|
|
|
@ -28,6 +34,14 @@ sub hashPassword { |
|
|
|
|
return crypt($password, '$6$' . $salt . '$'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sub dry_print { |
|
|
|
|
if ($is_dry) { |
|
|
|
|
print STDERR ("$_[1] $_[2]\n") |
|
|
|
|
} else { |
|
|
|
|
print STDERR ("$_[0] $_[2]\n") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in |
|
|
|
|
# /etc/login.defs. |
|
|
|
@ -51,7 +65,7 @@ sub allocGid { |
|
|
|
|
my ($name) = @_; |
|
|
|
|
my $prevGid = $gidMap->{$name}; |
|
|
|
|
if (defined $prevGid && !defined $gidsUsed{$prevGid}) { |
|
|
|
|
print STDERR "reviving group '$name' with GID $prevGid\n"; |
|
|
|
|
dry_print("reviving", "would revive", "group '$name' with GID $prevGid"); |
|
|
|
|
$gidsUsed{$prevGid} = 1; |
|
|
|
|
return $prevGid; |
|
|
|
|
} |
|
|
|
@ -63,15 +77,14 @@ sub allocUid { |
|
|
|
|
my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1); |
|
|
|
|
my $prevUid = $uidMap->{$name}; |
|
|
|
|
if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) { |
|
|
|
|
print STDERR "reviving user '$name' with UID $prevUid\n"; |
|
|
|
|
dry_print("reviving", "would revive", "user '$name' with UID $prevUid"); |
|
|
|
|
$uidsUsed{$prevUid} = 1; |
|
|
|
|
return $prevUid; |
|
|
|
|
} |
|
|
|
|
return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Read the declared users/groups. |
|
|
|
|
# Read the declared users/groups |
|
|
|
|
my $spec = decode_json(read_file($ARGV[0])); |
|
|
|
|
|
|
|
|
|
# Don't allocate UIDs/GIDs that are manually assigned. |
|
|
|
@ -134,7 +147,7 @@ foreach my $g (@{$spec->{groups}}) { |
|
|
|
|
if (defined $existing) { |
|
|
|
|
$g->{gid} = $existing->{gid} if !defined $g->{gid}; |
|
|
|
|
if ($g->{gid} != $existing->{gid}) { |
|
|
|
|
warn "warning: not applying GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})\n"; |
|
|
|
|
dry_print("warning: not applying", "warning: would not apply", "GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})"); |
|
|
|
|
$g->{gid} = $existing->{gid}; |
|
|
|
|
} |
|
|
|
|
$g->{password} = $existing->{password}; # do we want this? |
|
|
|
@ -163,7 +176,7 @@ foreach my $name (keys %groupsCur) { |
|
|
|
|
my $g = $groupsCur{$name}; |
|
|
|
|
next if defined $groupsOut{$name}; |
|
|
|
|
if (!$spec->{mutableUsers} || defined $declGroups{$name}) { |
|
|
|
|
print STDERR "removing group ‘$name’\n"; |
|
|
|
|
dry_print("removing group", "would remove group", "‘$name’"); |
|
|
|
|
} else { |
|
|
|
|
$groupsOut{$name} = $g; |
|
|
|
|
} |
|
|
|
@ -175,7 +188,7 @@ my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members} |
|
|
|
|
(sort { $a->{gid} <=> $b->{gid} } values(%groupsOut)); |
|
|
|
|
updateFile($gidMapFile, to_json($gidMap)); |
|
|
|
|
updateFile("/etc/group", \@lines); |
|
|
|
|
system("nscd --invalidate group"); |
|
|
|
|
nscdInvalidate("group"); |
|
|
|
|
|
|
|
|
|
# Generate a new /etc/passwd containing the declared users. |
|
|
|
|
my %usersOut; |
|
|
|
@ -196,7 +209,7 @@ foreach my $u (@{$spec->{users}}) { |
|
|
|
|
if (defined $existing) { |
|
|
|
|
$u->{uid} = $existing->{uid} if !defined $u->{uid}; |
|
|
|
|
if ($u->{uid} != $existing->{uid}) { |
|
|
|
|
warn "warning: not applying UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})\n"; |
|
|
|
|
dry_print("warning: not applying", "warning: would not apply", "UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})"); |
|
|
|
|
$u->{uid} = $existing->{uid}; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
@ -211,7 +224,7 @@ foreach my $u (@{$spec->{users}}) { |
|
|
|
|
|
|
|
|
|
# Ensure home directory incl. ownership and permissions. |
|
|
|
|
if ($u->{createHome}) { |
|
|
|
|
make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home}; |
|
|
|
|
make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home} and ! $is_dry; |
|
|
|
|
chown $u->{uid}, $u->{gid}, $u->{home}; |
|
|
|
|
chmod 0700, $u->{home}; |
|
|
|
|
} |
|
|
|
@ -250,7 +263,7 @@ foreach my $name (keys %usersCur) { |
|
|
|
|
my $u = $usersCur{$name}; |
|
|
|
|
next if defined $usersOut{$name}; |
|
|
|
|
if (!$spec->{mutableUsers} || defined $declUsers{$name}) { |
|
|
|
|
print STDERR "removing user ‘$name’\n"; |
|
|
|
|
dry_print("removing user", "would remove user", "‘$name’"); |
|
|
|
|
} else { |
|
|
|
|
$usersOut{$name} = $u; |
|
|
|
|
} |
|
|
|
@ -261,7 +274,7 @@ foreach my $name (keys %usersCur) { |
|
|
|
|
(sort { $a->{uid} <=> $b->{uid} } (values %usersOut)); |
|
|
|
|
updateFile($uidMapFile, to_json($uidMap)); |
|
|
|
|
updateFile("/etc/passwd", \@lines); |
|
|
|
|
system("nscd --invalidate passwd"); |
|
|
|
|
nscdInvalidate("passwd"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Rewrite /etc/shadow to add new accounts or remove dead ones. |
|
|
|
@ -293,7 +306,7 @@ updateFile("/etc/shadow", \@shadowNew, 0640); |
|
|
|
|
my $uid = getpwnam "root"; |
|
|
|
|
my $gid = getgrnam "shadow"; |
|
|
|
|
my $path = "/etc/shadow"; |
|
|
|
|
chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!"; |
|
|
|
|
(chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!") unless $is_dry; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Rewrite /etc/subuid & /etc/subgid to include default container mappings |
|
|
|
|