Commit 7337d3e4 authored by Florian Vessaz's avatar Florian Vessaz

Generate uid/gid and primary group for new accounts

parent 66b6b0fe
......@@ -12,6 +12,13 @@ use POSIX;
our $LDAP_URL = 'ldaps://gnusrv2.epfl.ch:636';
# See https://gnugeneration.epfl.ch/membres/numericids
our $FIRST_UID = 5000; # Base for user and private groups
our $FIRST_GID = 10000; # Base for other groups
our $SHADOW_EXPIRE_DISABLED = 7304; # Account disabled (expired on 1990-01-01)
our $USERS_BASE = "ou=Users,dc=gnugen,dc=epfl,dc=ch";
our $GROUPS_BASE = "ou=Groups,dc=gnugen,dc=epfl,dc=ch";
=head1 NAME
GnuGeneration::Member - Convenient acces to GNU Generation member's
......@@ -138,7 +145,7 @@ sub all_members {
_bind_admin($ldap);
my $res = $ldap->search(
base => "ou=Users,dc=gnugen,dc=epfl,dc=ch",
base => "$USERS_BASE",
filter => "(objectclass=gnugenMember)",
attrs => ['', '*']);
......@@ -185,35 +192,133 @@ sub create {
}
my $ldap = _connect_ldap();
_bind_admin($ldap);
my $mesg = $ldap->add(
'cn=' . $args{cn} . ', ou=Users,dc=gnugen,dc=epfl,dc=ch',
attrs => [
cn => $args{cn},
objectclass => [
'inetOrgPerson',
'posixAccount',
'shadowAccount',
'gnugenMember'],
sn => $args{lastName},
givenName => $args{firstName},
uid => $args{username},
mail => $args{mail},
mobile => $args{mobile},
shell => $args{shell},
shadowExpire => 7304,
gnugenMemberSubscriptionPayedDate => 0,
gnugenMemberTermsOfUseAcceptedDate => 0,
userPassword => _hash_password($args{password}),
]
my $home = '/home/' . $username;
my $id = _next_uid();
my $userdn = 'cn=' . escape_dn_value($args{cn})
. ', $USERS_BASE';
my @userattrs = [
cn => $args{cn},
objectclass => [
'inetOrgPerson',
'posixAccount',
'shadowAccount',
'gnugenMember'],
sn => $args{lastName},
givenName => $args{firstName},
uid => $args{username},
mail => $args{mail},
mobile => $args{mobile},
uidNumber => $id,
gidNumber => $id,
shell => '/bin/bash',
shadowExpire => $SHADOW_EXPIRE_DISABLED,
userPassword => $args{password_hash},
gnugenMemberSubscriptionPayedDate => 0,
gnugenMemberTermsOfUseAcceptedDate => 0,
];
my $groupdn = 'cn=' . escape_dn_value($args{username})
. '$GROUPS_BASE';
my @groupattrs = (
'objectClass' => [ "posixGroup" ],
'cn' => $args{username},
'description' => "Private group for $args{username}",
'memberUid' => $args{username},
'gidNumber' => $id,
);
# Create the user entry
my $mesg = $ldap->add($userdn, attrs => \@userattrs);
if ($res->code) {
warn "Failed to user entry: ", $res->error;
return undef;
}
# Synchronize concurrent user creation
$id = _fixup_new_user_id($userdn);
# Create the group entry
$mesg = $ldap->add($groupdn, attrs => \@groupattrs);
if ($res->code) {
warn "Failed to add private group entry: ", $res->error;
$ldap->delete($userdn);
warn "User entry was deleted due to previous errors."
return undef;
}
# Unlike the uidNumber, there is no synchronisation issues for the
# group entries unless some other code or person is doings stupid things.
# Check anyway in case someone did bad things…
$mesg = $ldap->search(base => $GROUPS_BASE,
filter => "(gidNumber=" . escape_filter_value($id) . ")",
attrs => ['dn']
);
if ($mesg->count != 1) {
warn "Some other code or person just made an incredibly stupid "
. "and dangerous edit. You'll have to find the culprit and "
. "take it down!";
$ldap->delete($userdn);
$ldap->delete($groupdn);
warn "Newly created user and group entries were deleted "
. "for safety reasons.";
return undef;
}
if ($mesg->code) {
warn "Couldn't create new member: " . $mesg->error;
my $member = GnuGeneration::Member->new(username => $args{username});
unless ($member) {
warn "Couldn't find newly created entry, LDAP is probably in "
. "an inconsistant state.";
return undef;
}
return GnuGeneration::Member->new(username => $args{username});
$member->add_to_group("members");
$member->add_to_group("gnunas-sftp");
$member->add_to_group("rainbowdash-login");
return $member;
}
# Check that the uidNumber for the given entry is unique.
# If not, update the entry with an unique uidNumber.
# Returns the uidNumber of the entry.
sub _fixup_new_user_id {
my $dn = shift;
my $mesg = $ldap->search(base => $USERS_BASE,
base => $dn,
scope => "base",
filter => "(objectClass=*)",
attrs => "uidNumber",
);
my $entry = $mesg->pop_entry;
my $id = decode('UTF-8', $entry->get_value("uidNumber", asref => 1)->[0]);
my $unique = 0;
while (not $unique) {
$mesg = $ldap->search(base => $USERS_BASE,
filter => "(uidNumber=" . escape_filter_value($id) . ")",
attrs => ['dn']
);
if ($mesg->count > 1) {
warn "Another entry has the same id, generating a new one";
$id = _next_uid();
$ldap->modify($entry, 'replace' => [
'uidNumber' => $id
]);
# Back off with some randomness to avoid infinite loop
# with a concurrently running copy of the code.
sleep(int(rand(5)));
} else {
$unique = 1;
}
}
return $id;
}
# Return a Net::LDAP object connected to the LDAP server
sub _connect_ldap {
my $ldap = Net::LDAP->new($LDAP_URL) or die "$@";
......@@ -226,6 +331,7 @@ sub _connect_ldap {
return $ldap;
}
# Bind the LDAP server with the administrative account
sub _bind_admin {
my ($ldap) = @_;
our $Admin_DN;
......@@ -236,6 +342,7 @@ sub _bind_admin {
}
}
# Populate the member from its LDAP entry
sub _from_entry {
my ($self, $entry) = @_;
return unless defined $entry;
......@@ -273,6 +380,7 @@ sub _from_entry {
$self->{gnugenMemberTermsOfUseAcceptedDate} = decode('UTF-8', $entry->get_value("gnugenMemberTermsOfUseAcceptedDate", asref => 1)->[0]);
}
# Populate the member with the information about access to the given host
sub _check_host_permissions {
my ($self, $ldap, $host) = @_;
if ($self->_check_username_in_group($ldap, $host ."-login")) {
......@@ -283,10 +391,11 @@ sub _check_host_permissions {
}
}
# Check whether the member is in the given Unix group
sub _check_username_in_group {
my ($self, $ldap, $group) = @_;
$group = escape_filter_value($group);
my $mesg = $ldap->search(base => "ou=Groups,dc=gnugen,dc=epfl,dc=ch",
my $mesg = $ldap->search(base => "$GROUPS_BASE",
filter => "cn=$group");
my $entry = $mesg->shift_entry;
if (defined $entry) {
......@@ -296,11 +405,12 @@ sub _check_username_in_group {
return;
}
# Populate the member with the information about host access
sub _add_host_permissions {
my ($ldap, $members, $host) = @_;
my $group = escape_filter_value($host . '-login');
my $res = $ldap->search(base => "ou=Groups,dc=gnugen,dc=epfl,dc=ch",
my $res = $ldap->search(base => "$GROUPS_BASE",
filter => "cn=$group");
my $entry = $res->shift_entry;
for my $username ($entry->get_value("memberUid")) {
......@@ -309,7 +419,7 @@ sub _add_host_permissions {
}
$group = escape_filter_value($host . '-sudoers');
$res = $ldap->search(base => "ou=Groups,dc=gnugen,dc=epfl,dc=ch",
$res = $ldap->search(base => "$GROUPS_BASE",
filter => "cn=$group");
$entry = $res->shift_entry;
for my $username ($entry->get_value("memberUid")) {
......@@ -323,7 +433,7 @@ sub _entry_for_username {
die "Missing username argument" unless defined $username;
my $safe_username = escape_filter_value($username);
my $mesg = $ldap->search(
base => "ou=Users,dc=gnugen,dc=epfl,dc=ch",
base => "$USERS_BASE",
filter => "(&(objectclass=gnugenMember)(uid=$safe_username))");
$mesg->code && warn "Can't find $username: " . $mesg->error;
return $mesg->shift_entry;
......@@ -546,7 +656,7 @@ sub shadow_expire_as_string {
sub shadow_expire_update {
my ($self) = @_;
my $ts = 7304; # Account disabled (expired on 1990-01-01)
my $ts = $SHADOW_EXPIRE_DISABLED;
if ($self->tos_accepted) {
my ($sec,$min,$hour,$mday,$mon,$year) = localtime($self->subscription);
$year += 1 if ($mon >= 9);
......@@ -566,4 +676,27 @@ sub active {
return $self->{shadowExpire} >= $now;
}
# Get the next available uid/gid for a user account and its private group
sub _next_uid {
my $ldap = _connect_ldap();
my $lst = $ldap->search(base => '$USERS_BASE',
scope => 'subtree',
filter => "(objectClass=posixGroup)",
attrs => ['uidNumber']);
$lst->code && die $lst->error;
# Put all used uid in a hash
my %uids = ();
foreach my $entry ($lst->entries) {
my $uid = $entry->get_value('uidNumber');
$uids{$uid} = $entry;
}
# Return the first unused uid
my $uid = $FIRS_UID;
$uid++ while (defined $uids{$uid}) {
return $uid_last;
}
1;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment