diff -Nur -x '*.orig' -x '*.rej' e-smith-ldap-5.2.0/root/var/service/ldap/convert_ldif mezzanine_patched_e-smith-ldap-5.2.0/root/var/service/ldap/convert_ldif --- e-smith-ldap-5.2.0/root/var/service/ldap/convert_ldif 2010-12-01 17:00:30.000000000 +0100 +++ mezzanine_patched_e-smith-ldap-5.2.0/root/var/service/ldap/convert_ldif 2010-12-01 16:48:15.000000000 +0100 @@ -1,67 +1,415 @@ -#! /usr/bin/perl +#!/usr/bin/perl -w use strict; use warnings; - - +use Net::LDAP; use Net::LDAP::LDIF; +use Date::Parse; +use esmith::ConfigDB; +use esmith::AccountsDB; use esmith::util; +use Getopt::Long qw(:config bundling); + +$ENV{'PATH'} = '/bin:/usr/bin:/sbin:/usr/sbin'; +$ENV{'LANG'} = 'C'; + +sub dnsort { + my %type = ( add => 1, modrdn => 2, moddn => 2, modify => 3, delete => 4); + my %attr = ( dc => 1, ou => 2, cn => 3, uid => 4); + + my ($oa) = ($a->get_value('newrdn') || $a->dn) =~ /^([^=]+)=/; + my ($ob) = ($b->get_value('newrdn') || $b->dn) =~ /^([^=]+)=/; + my ($ua, $ub) = map { my $tu = $_->get_value('uidnumber'); defined $tu && $tu ne '' ? $tu : -1 } ($a, $b); + my ($ga, $gb) = map { my $tg = $_->get_value('gidnumber'); defined $tg && $tg ne '' ? $tg : -1 } ($a, $b); + + ($attr{$oa} || 9) <=> ($attr{$ob} || 9) || ($type{$a->changetype} || 9) <=> ($type{$b->changetype} || 9) || + $ua <=> $ub || $ga <=> $gb || ($a->get_value('newrdn') || $a->dn) cmp ($b->get_value('newrdn') || $b->dn); +} -my $olddomain = shift; -my $newdomain = shift; +my $c = esmith::ConfigDB->open_ro; +my $a = esmith::AccountsDB->open_ro; -my $ldif = Net::LDAP::LDIF->new( "/dev/stdin", "r", onerror => 'undef' ); -my $writer = Net::LDAP::LDIF->new("/dev/stdout", "w", onerror => 'undef' ); +my $auth = $c->get('ldap')->prop('Authentication') || 'disabled'; +my $schema = '/etc/openldap/schema/samba.schema'; -my $new = esmith::util::ldapBase($newdomain); -my $old = esmith::util::ldapBase($olddomain); +my $domain = $c->get('DomainName')->value; +my $basedn = esmith::util::ldapBase($domain); -while( not $ldif->eof()) -{ - my $entry = $ldif->read_entry(); - if ($ldif->error()) - { - print "Error msg: ", $ldif->error(), "\n"; - print "Error lines:\n", $ldif->error_lines(), "\n"; - next; - } - next unless $entry; - my $dn = $entry->dn; - my @object_classes = $entry->get_value('objectClass'); - my %object_classes = map { $_ => 1 } @object_classes; - if ($dn eq $old) - { - # this is the domain container object - objectClasses will be - # 'top' and 'domain' - my $dc = $new; - $dc =~ s/,.*//g; - $dc =~ s/^dc=//; - $entry->replace(dc => $dc); - } - if ($object_classes{group}) - { - # We used to create group entries with invalid objectClass group - # - fix these if we find them - # possibly not required any more, but harmless - @object_classes = grep { $_ ne 'group' } @object_classes; - $entry->replace(objectClass => [ @object_classes, 'posixGroup' ] ); - } - # do any other object transformations - - # Update the mail attributes - if ($entry->exists('mail')){ - my @newmails = (); - foreach ($entry->get_value('mail')){ - $_ =~ s/\@$olddomain$/\@$newdomain/; - push (@newmails,$_); +my $userou = 'ou=Users'; +my $groupou = 'ou=Groups'; +my $compou = 'ou=Computers'; + +my ($dc) = split /\./, $domain; +my $company = $c->get_prop('ldap', 'defaultCompany') || $domain; + +my %opt; +GetOptions ( \%opt, "diff|d", "update|u", "input|i=s", "output|o=s" ); +$opt{input} = '/usr/sbin/slapcat -c 2> /dev/null|' unless $opt{input} && ($opt{input} eq '-' || -f "$opt{input}" || -c "$opt{input}"); +$opt{diff} = 1 if $opt{update}; +if ( $opt{output} && $opt{output} =~ m{^([-\w/.]+)$}) { + $opt{output} = $1; +} else { + $opt{output} = '-'; +} + +my ($data, $dn); + +# Top object (base) +$data->{$basedn} = { + objectclass => [qw/organization dcObject top/], + dc => $dc, + o => $company, +}; + +# Top containers for users/groups/computers +foreach (qw/Users Groups Computers/) { + $data->{"ou=$_,$basedn"} = { + objectclass => [qw/organizationalUnit top/], + ou => $_, + }; +} + +# Common accounts needed for SME to work properly +$data->{"cn=nobody,$groupou,$basedn"}->{objectclass} = [ qw/posixGroup/ ]; +$data->{"uid=www,$userou,$basedn"}->{objectclass} = [ qw/account/ ]; +$data->{"cn=www,$groupou,$basedn"} = { objectclass => [ qw/posixGroup/ ], memberuid => [ qw/admin/ ] }; +$data->{"cn=shared,$groupou,$basedn"} = { + objectclass => [ qw/posixGroup mailboxRelatedObject/ ], + mail => "everyone\@$domain", + memberuid => [ qw/www/ ] +}; + +# Read in accounts database information +foreach my $acct ($a->get('admin'), $a->users, $a->groups, $a->ibays, $a->get_all_by_prop(type => 'machine')) { + my $key = $acct->key; + my $type = $acct->prop('type'); + + next if $key eq 'Primary'; + + $dn = "uid=$key,".($type eq 'machine' ? $compou : $userou).",$basedn"; + if ($type =~ /^(?:user|group|machine|ibay)$/ || $key eq 'admin') { + if ($type eq 'user' || $key eq 'admin') { + # Allow removal of obsolete person objectclass and samba attributes + push @{$data->{$dn}->{_delete}->{objectclass}}, 'person'; + + + push @{$data->{$dn}->{objectclass}}, 'inetOrgPerson'; + $data->{$dn}->{mail} = "$key\@$domain"; + @{$data->{$dn}}{qw/givenname sn telephonenumber o ou l street/} = + map { $acct->prop($_) || [] } qw/FirstName LastName Phone Company Dept City Street/; + $data->{$dn}->{cn} = $data->{$dn}->{gecos} = $acct->prop('FirstName').' '.$acct->prop('LastName'); + } + else { + push @{$data->{$dn}->{objectclass}}, 'account'; + } + + # users/ibays need to be a member of shared + push @{$data->{"cn=shared,$groupou,$basedn"}->{memberuid}}, $key if $type =~ /^(user|ibay)$/ || $key eq 'admin'; + + if ($auth ne 'enabled') { + # Allow removal of shadow properties + push @{$data->{$dn}->{_delete}->{objectclass}}, 'shadowAccount'; + $data->{$dn}->{_delete}->{lc($_)} = 1 foreach qw/userPassword shadowLastChange shadowMin shadowMax + shadowWarning shadowInactive shadowExpire shadowFlag/; + + if ( -f "$schema" ) { + # If we will be adding samba properties then allow removal + push @{$data->{$dn}->{_delete}->{objectclass}}, 'sambaSamAccount'; + $data->{$dn}->{_delete}->{lc($_)} = 1 foreach qw/displayName sambaAcctFlags sambaLMPassword sambaNTPassword + sambaNTPassword sambaPrimaryGroupSID sambaPwdLastSet sambaSID/; + } } - $entry->replace(mail => [ @newmails ]); } - # Update basedb suffix - $dn =~ s/$old$/$new/; + $dn = "cn=$key,$groupou,$basedn"; + push @{$data->{$dn}->{objectclass}}, 'posixGroup'; + if ($type eq 'group') { + # Allways replace memberuid with new set + $data->{$dn}->{_delete}->{memberuid} = 1; + + push @{$data->{$dn}->{objectclass}}, 'mailboxRelatedObject'; + + $data->{$dn}->{mail} = "$key\@$domain"; + $data->{$dn}->{description} = $acct->prop('Description') || []; + push @{$data->{$dn}->{memberuid}}, split /,/, ($acct->prop('Members') || ''); + + # www needs to be a memeber of every group + push @{$data->{$dn}->{memberuid}}, 'www'; + + if ($auth ne 'enabled' && -f "$schema" ) { + # If we will be adding samba properties then allow removal + push @{$data->{$dn}->{_delete}->{objectclass}}, 'sambaGroupMapping'; + $data->{$dn}->{_delete}->{lc($_)} = 1 foreach qw/displayName sambaGroupType sambaSID/; + } + } + elsif ($type eq 'ibay') { + $dn = "cn=".$acct->prop('Group').",$groupou,$basedn"; + push @{$data->{$dn}->{memberuid}}, $acct->key; + } +} + +if ($auth ne 'enabled') { + # Read in information from unix (passwd) system + open PASSWD, '/etc/passwd'; + while () { + chomp; + my @passwd = split /:/, $_; + next unless scalar @passwd == 7; + + $dn = "uid=$passwd[0],".($passwd[0] =~ /\$$/ ? $compou : $userou).",$basedn"; + next unless exists $data->{$dn}; + + push @{$data->{$dn}->{objectclass}}, 'posixAccount'; + @{$data->{$dn}}{qw/cn uid uidnumber gidnumber homedirectory loginshell gecos/} = + map { $passwd[$_] ? $passwd[$_] : [] } (4,0,2,3,5,6,4); + } + close (PASSWD); + + # Shadow file defaults (pulled from cpu.conf) + my %shadow_def = ( 1 => [], 2 => 11192, 3 => -1, 4 => 99999, 5 => 7, 6 => -1, 7 => -1, 8 => 134538308 ); + + # Read in information from unix (shadow) system + open SHADOW, '/etc/shadow'; + while () { + chomp; + my @shadow = split /:/, $_; + next unless scalar @shadow >= 6; + $shadow[1] = '!*' if $shadow[1] eq '!!'; + $shadow[1] = "{CRYPT}$shadow[1]" unless $shadow[1] =~ /^\{/; + + $dn = "uid=$shadow[0],".($shadow[0] =~ /\$$/ ? $compou : $userou).",$basedn"; + next unless exists $data->{$dn}; + + push @{$data->{$dn}->{objectclass}}, 'shadowAccount'; + @{$data->{$dn}}{ map { lc($_) } qw/userPassword shadowLastChange shadowMin shadowMax shadowWarning shadowInactive + shadowExpire shadowFlag/} = map { $shadow[$_] ? $shadow[$_] : $shadow_def{$_} } (1..8); + } + close (SHADOW); + + # Read in information from unix (group) system + open GROUP, '/etc/group'; + while () { + chomp; + my @group = split /:/, $_; + next unless scalar @group >= 3; + $group[3] = [ split /,/, ($group[3] || '') ]; + + $dn = "cn=$group[0],$groupou,$basedn"; + next unless exists $data->{$dn}; + + push @{$data->{$dn}->{objectclass}}, 'posixGroup'; + @{$data->{$dn}}{qw/cn gidnumber/} = map { $group[$_] ? $group[$_] : [] } (0,2); + push @{$data->{$dn}->{memberuid}}, @{$group[3]}; + } + close (GROUP); + + my %smbprop = ( + 'User SID' => 'sambasid', + 'Account Flags' => 'sambaacctflags', + 'Primary Group SID' => 'sambaprimarygroupsid', + 'Full Name' => 'displayname', + 'Password last set' => 'sambapwdlastset', + ); + + # Read in information from unix (smbpasswd) system + if ( -f "$schema" && -x '/usr/bin/pdbedit' ) { + $dn = undef; + open SMBDETAIL, '/usr/bin/pdbedit -vL 2> /dev/null|'; + while () { + chomp; + + $dn = ("uid=$1,".($1 =~ /\$$/ ? $compou : $userou).",$basedn") if m/^Unix username:\s+(\S.*)$/; + next unless $dn && exists $data->{$dn}; + + # Map the samba account properties that we care about + $data->{$dn}->{$smbprop{$1}} = ($2 ? str2time($2) : (defined $3 ? $3 : [])) + if m/^(.+):\s+(?:(\S.*\d{4} \d{2}:\d{2}:\d{2}.*)|(.*))$/ && exists $smbprop{$1}; + } + close (SMBDETAIL); + + open SMBPASSWD, '/usr/bin/pdbedit -wL 2> /dev/null|'; + while () { + chomp; + my @smbpasswd = split /:/, $_; + next unless scalar @smbpasswd >= 6; + + $dn = "uid=$smbpasswd[0],".($smbpasswd[0] =~ /\$$/ ? $compou : $userou).",$basedn"; + next unless exists $data->{$dn} && exists $data->{$dn}->{uidnumber} && $data->{$dn}->{uidnumber} eq $smbpasswd[1]; + + push @{$data->{$dn}->{objectclass}}, 'sambaSamAccount'; + @{$data->{$dn}}{qw/sambalmpassword sambantpassword/} = map { $smbpasswd[$_] ? $smbpasswd[$_] : [] } (2,3); + } + close (SMBPASSWD); + } + + if ( -f "$schema" && -x '/usr/bin/net' ) { + open GROUPMAP, '/usr/bin/net groupmap list 2> /dev/null|'; + while () { + chomp; + + if (m/^(.+) \((.+)\) -> (.+)$/) { + # Skip local machine accounts + next if $2 =~ /S-1-5-32-\d+/; + + $dn = "cn=$3,$groupou,$basedn"; + next unless exists $data->{$dn}; + + push @{$data->{$dn}->{objectclass}}, 'sambaGroupMapping'; + @{$data->{$dn}}{qw/displayname sambasid sambagrouptype/} = ($1, $2, 2); + } + } + close (GROUPMAP); + } +} + +my @ldif; + +# Loop through ldap data and update as necessary +my $reader = Net::LDAP::LDIF->new( $opt{input}, 'r', onerror => 'undef' ); +while( not $reader->eof()) { + my $entry = $reader->read_entry() || next; + $dn = $entry->dn; + + # Ensure the basedn is correct + $dn = "$1$basedn" if $dn =~ /^((?:(?!dc=)[^,]+,)*)dc=/; + + # Ensure correct ou is part of user/groups/computers + if ($dn =~ /^(uid=([^,\$]+)(\$)?),((?:(?!dc=)[^,]+,)*)dc=/) { + if ( defined $3 && $3 eq '$') { + $dn = "$1,$compou,$basedn"; + } + elsif (grep /posixGroup/, @{$entry->get_value('objectclass', asref => 1) || []}) { + $dn = "cn=$2,$groupou,$basedn"; + + # Cleanup attributes that the modrdn will perform + $entry->add(cn => $2); + $entry->delete(uid => [$2]); + } + else { + $dn = "$1,$userou,$basedn"; + } + } + elsif ($dn =~ /^(cn=[^,]+),((?:(?!dc=)[^,]+,)*)dc=/) { + $dn = "$1,$groupou,$basedn" unless $2 =~ /^ou=auto\./; + } + + # Don't process records twice + next if $data->{$dn}->{_done}; + + # Rename existing entry into place if we can + if ($dn ne $entry->dn) { + my $rdn = Net::LDAP::Entry->new; + $rdn->dn($entry->dn); + $rdn->changetype('modrdn'); + my ($newdn, $newbase) = split /,/, $dn, 2; + $rdn->add(newrdn => $newdn, deleteoldrdn => 1, newsuperior => $newbase); + push @ldif, $rdn; + + # Now we can change the entry to new dn + $entry->dn($dn); + } + + # Change type to modify so that we can keep track of changes we make + $entry->changetype('modify'); + + # Hack to make upgrades work (add calEntry if calFGUrl attributes exists) + if ($entry->exists('calFBURL') && -f "/etc/openldap/schema/rfc2739.schema") { + push @{$data->{$dn}->{objectclass}}, 'calEntry'; + } + + my %attributes = (); + @attributes{ keys %{$data->{$dn}}, exists $data->{$dn}->{_delete} ? map { lc($_) } keys %{$data->{$dn}->{_delete}} : () } = (); + + foreach my $attr (sort keys %attributes) { + # Skip the pseudo attributes + next if $attr =~ /^_/; + + my @l = @{$entry->get_value($attr, asref => 1) || []}; + my @u = exists $data->{$dn}->{$attr} ? (ref $data->{$dn}->{$attr} ? @{$data->{$dn}->{$attr}} : ($data->{$dn}->{$attr})) : (); + + # Figure out differences between attributes + my (@lonly, @uonly, @donly, %lseen, %useen, %dseen) = () x 6; + + # Unique lists of what is in ldap and what needs to be in ldap + @lseen{@l} = (); + @useen{@u} = (); + + # Create list of attributes that aren't in the other + @uonly = grep { ! exists $lseen{$_} } keys %useen; + @lonly = grep { ! exists $useen{$_} } keys %lseen; + + # Determine which of the ldap only attributes we need to remove + if ((keys %useen == 1 && keys %lseen == 1) || (keys %useen == 0 && exists $data->{$dn}->{$attr})) { + # Replacing a single entry or erasing entire entry + @donly = @lonly; + } + elsif ($data->{$dn}->{_delete} && $data->{$dn}->{_delete}->{$attr}) { + if (my $ref = ref($data->{$dn}->{_delete}->{$attr})) { + # Map hash keys or array elemts to valid values to delete + @dseen{$ref eq 'HASH' ? keys %{$data->{$dn}->{_delete}->{$attr}} : @{$data->{$dn}->{_delete}->{$attr}}} = (); + @donly = grep { exists $dseen{$_} } @lonly; + } + else { + # Permission to remove all values + @donly = @lonly; + } + } + + if (@donly && @donly == @lonly) { + # If we are removing all ldap only attributes do a remove or full delete + if (@uonly) { + $entry->replace($attr => [ @uonly ]); + } + else { + $entry->delete($attr => [ @donly == keys %lseen ? () : @donly ]); + } + } + else { + $entry->delete($attr => [ @donly ]) if @donly; + $entry->add($attr => [ @uonly ]) if @uonly; + } + } + + $data->{$dn}->{_done} = 1; + push @ldif, $entry; +} +$reader->done(); + +# Add missing records that didn't exist in ldap yet +foreach $dn (grep { ! exists $data->{$_}->{_done} } sort keys %$data) { + my $entry = Net::LDAP::Entry->new; $entry->dn($dn); - $writer->write($entry); + + foreach my $attr (sort keys %{$data->{$dn}}) { + # Skip the pseudo attributes + next if $attr =~ /^_/; + + my %seen = (); + @seen{ref $data->{$dn}->{$attr} ? @{$data->{$dn}->{$attr}} : ($data->{$dn}->{$attr})} = (); + $entry->add($attr => [ sort keys %seen ]) if keys %seen != 0; + } + + push @ldif, $entry; +} + +#------------------------------------------------------------ +# Update LDAP database entry. +#------------------------------------------------------------ +my $ldap; +if ($opt{update}) { + $ldap = Net::LDAP->new('localhost') or die "$@"; + $ldap->bind( dn => "cn=root,$basedn", password => esmith::util::LdapPassword() ); +} + +my $writer = Net::LDAP::LDIF->new( $opt{output}, 'w', onerror => 'undef', wrap => 0, sort => 1, change => $opt{diff} ); +foreach my $entry (sort dnsort @ldif) { + if ($opt{update} && ($entry->changetype ne 'modify' || @{$entry->{changes}}) ) { + my $result = $entry->update($ldap); + warn "Failure to ",$entry->changetype," ",$entry->dn,": ",$result->error,"\n" if $result->code; + } + + if ($writer->{change} || $entry->changetype !~ /modr?dn/) { + $writer->write_entry($entry); + } } -$ldif->done( ); diff -Nur -x '*.orig' -x '*.rej' e-smith-ldap-5.2.0/root/var/service/ldap/run mezzanine_patched_e-smith-ldap-5.2.0/root/var/service/ldap/run --- e-smith-ldap-5.2.0/root/var/service/ldap/run 2010-12-01 17:00:30.000000000 +0100 +++ mezzanine_patched_e-smith-ldap-5.2.0/root/var/service/ldap/run 2010-12-01 16:48:48.000000000 +0100 @@ -47,7 +47,7 @@ if [ -e "$old_ldif" ] then grep -q "objectClass: dcObject" "$ldif" || /sbin/e-smith/expand-template /home/e-smith/db/ldap/ldif - perl ./convert_ldif $old_domain $domain < $old_ldif | \ + perl ./convert_ldif -i - -o - < $old_ldif | \ setuidgid ldap slapadd -c else if [ \! -e "$ldif" ]