Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 330 lines (257 sloc) 9.866 kb
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
1 #! /usr/bin/perl
2
3 =head1 NAME
4
5 ldifdiff.pl -- Generates LDIF change diff between two sorted LDIF files.
6
7 =head1 DESCRIPTION
8
9 ldifdiff.pl takes as input two sorted LDIF files, source and target, and
10 generates on standard output the LDIF changes needed to transform the target
11 into the source.
12
13 =head1 SYNOPSIS
14
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
15 ldifdiff.pl B<-k|--keyattr keyattr> [B<-a|--sourceattrs attr1,attr2,...>] [B<-c|--ciscmp attr1,...>] [B<-n|--numcmp attr1,...>] [B<--dnattrs attr1,...>] [B<--sharedattrs attr1,...>] B<sourcefile> B<targetfile>
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
16
17 =head1 OPTIONS
18
19 =over 4
20
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
21 =item B<-k|--keyattr> keyattr
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
22
23 Specifies the key attribute to use when comparing source and target entries.
24 Entries in both LDIF files must be sorted by this attribute for comparisons to
25 be meaningful. F<ldifsort.pl> can be used to sort LDIF files by a given
26 attribute.
27
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
28 =item B<-a|--sourceattrs attr1,attr2,...>
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
29
30 (Optional) Specifies a list of attributes to consider when comparing
31 source and target entries. By default, all attributes are considered.
32
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
33 =item B<-c|--ciscmp attr1,...>
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
34
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
35 (Optional) Compare values of the specified attributes case-insensitively. The
36 default set is: mail manager member objectclass owner uid uniqueMember
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
37
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
38 =item B<-n|--numcmp attr1,...>
39
40 (Optional) Compare values of the specified attributes numerically. The
41 default set is: employeeNumber
42
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
43 =item B<--dnattrs attr1,...>
44
45 (Optional) Specifies a list of attributes to be treated as DNs when being
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
46 compared. The default set is: manager member owner uniqueMember
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
47
48 =item B<--sharedattrs attr1,...>
49
50 (Optional) Specifies a list of attribues to be treated as "shared" attributes,
51 where the source may not be a sole authoritative source. When modifying
52 these attributes, separate "delete" and "add" LDIF changes are generated,
53 instead of a single "replace" change. The default set is objectClass.
54
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
55 =item B<sourcefile>
56
57 Specifies the source LDIF file.
58
59 =item B<targetfile>
60
61 Specifies the target LDIF file.
62
63 =back
64
65 =cut
66
67 use Net::LDAP;
68 use Net::LDAP::LDIF;
69 use Net::LDAP::Util qw(canonical_dn);
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
70 use Getopt::Long;
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
71
72 use strict;
73
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
74 my @sourceattrs;
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
75 my (%ciscmp, %numcmp, %dnattrs, %sharedattrs);
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
76 my $keyattr;
77 GetOptions('a|sourceattrs=s' => sub { @sourceattrs = split(/,/, $_[1]) },
ab17bd1 @kartiksubbarao Lowercase specified attributes for %ciscmp, %dnattrs, and %sharedattrs.
kartiksubbarao authored
78 'c|ciscmp=s' => sub { my @a = split(/,/,lc $_[1]); @ciscmp{@a} = (1) x @a },
79 'dnattrs=s' => sub { my @a = split(/,/,lc $_[1]); @dnattrs{@a} = (1) x @a },
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
80 'k|keyattr=s' => \$keyattr,
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
81 'n|numcmp=s' => sub { my @a = split(/,/,lc $_[1]); @numcmp{@a} = (1) x @a },
ab17bd1 @kartiksubbarao Lowercase specified attributes for %ciscmp, %dnattrs, and %sharedattrs.
kartiksubbarao authored
82 'sharedattrs=s' => sub {my @a=split(/,/,lc $_[1]);@sharedattrs{@a}=(1) x @a}
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
83 );
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
84 unless (keys %ciscmp) {
b1d0937 @kartiksubbarao Added cn,o,ou to the default set of case-insensitive attributes
kartiksubbarao authored
85 foreach (qw(cn mail manager member o ou objectclass owner uid uniquemember))
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
86 { $ciscmp{$_} = 1 }
87 }
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
88 unless (keys %numcmp) {
89 foreach (qw(employeenumber))
90 { $numcmp{$_} = 1 }
91 }
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
92 unless (keys %dnattrs) {
93 foreach (qw(manager member owner uniquemember))
94 { $dnattrs{$_} = 1 }
95 }
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
96 %sharedattrs = (objectclass => 1)
97 unless keys %sharedattrs;
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
98
99
100 my ($sourcefile, $targetfile);
101 $sourcefile = shift; $targetfile = shift;
102
ab17bd1 @kartiksubbarao Lowercase specified attributes for %ciscmp, %dnattrs, and %sharedattrs.
kartiksubbarao authored
103 die "usage: $0 -k|--keyattr keyattr [-a|--sourceattrs attr1,attr2,...] [-c|--ciscmp attr1,...] [--dnattrs attr1,...] [--sharedattrs attr1,...] sourcefile targetfile\n"
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
104 unless $keyattr && $sourcefile && $targetfile;
105
106 my $source = Net::LDAP::LDIF->new($sourcefile)
ab17bd1 @kartiksubbarao Lowercase specified attributes for %ciscmp, %dnattrs, and %sharedattrs.
kartiksubbarao authored
107 or die "Can't open LDIF file $sourcefile: $!\n";
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
108
109 my $target = Net::LDAP::LDIF->new($targetfile)
110 or die "Can't open LDIF file $targetfile: $!\n";
111
112 my $ldifout = Net::LDAP::LDIF->new('-', 'w');
113 $ldifout->{change} = 1;
114 $ldifout->{wrap} = 78;
115
116 diff($source, $target);
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
117 exit;
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
118
119
120 # Gets the relative distinguished name (RDN) attribute
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
121 sub rdnattr { ($_[0] =~ /^(.*?)=/)[0] }
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
122
123 # Gets the relative distinguished name (RDN) value
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
124 sub rdnval { my $rv = ($_[0] =~ /=(.*)/)[0]; $rv =~ s/(?<!\\),.*//; $rv }
125
126 # Gets the rest of the DN (the part after the RDN)
127 sub dnsuperior { my $rv = ($_[0] =~ /^.*?(?<!\\),(.*)/)[0]; $rv }
128
129 sub cmpDNs
130 {
131 my ($adn, $bdn) = @_;
132 my $cadn = canonical_dn($adn, casefold => 'lower');
133 my $cbdn = canonical_dn($bdn, casefold => 'lower');
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
134 my $rdnattr = lc rdnattr($cadn);
135 if ($ciscmp{$rdnattr}) { $cadn = lc($cadn), $cbdn = lc($cbdn) }
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
136
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
137 return $numcmp{$rdnattr} ? $cadn <=> $cbdn : $cadn cmp $cbdn;
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
138 }
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
139
140 sub cmpEntries
141 {
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
142 my ($a, $b) = @_;
143 my $dncmp = cmpDNs($a->dn, $b->dn);
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
144
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
145 if (lc($keyattr) eq 'dn') {
146 return ($dncmp, $dncmp);
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
147 }
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
148 else {
d7f9845 @kartiksubbarao Correct initial entry comparison to be case-insensitive where appropriat...
kartiksubbarao authored
149 my $aval = $a->get_value($keyattr);
150 my $bval = $b->get_value($keyattr);
151 if ($ciscmp{$keyattr}) {
152 $aval = lc($aval);
153 $bval = lc($bval);
154 }
91a9d9c @kartiksubbarao Added numcmp option to compare attributes numerically.
kartiksubbarao authored
155 return($numcmp{$keyattr} ? $aval <=> $bval : $aval cmp $bval, $dncmp);
d7f9845 @kartiksubbarao Correct initial entry comparison to be case-insensitive where appropriat...
kartiksubbarao authored
156 }
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
157 }
158
159
160 # Diffs two LDIF data sources
161 sub diff
162 {
163 my ($source, $target) = @_;
164 my ($sourceentry, $targetentry, $incr_source, $incr_target, @ldifchanges);
165
166 $sourceentry = $source->read_entry();
167 $targetentry = $target->read_entry();
168
169 while () {
170 # End of all data
171 last if !$sourceentry && !$targetentry;
172
173 # End of source data, but more target data. Delete.
174 if (!$sourceentry && $targetentry) {
175 $targetentry->delete;
176 $ldifout->write_entry($targetentry);
177 $incr_target = 1, next;
178 }
179
180 # End of target data, but more data in source. Add.
181 if ($sourceentry && !$targetentry) {
182 $ldifout->write_entry($sourceentry);
183 $incr_source = 1, next;
184 }
185
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
186 my ($entrycmp, $dncmp) = cmpEntries($sourceentry, $targetentry);
187
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
188 # Check if the current source entry has a higher sort position than
189 # the current target. If so, we interpret this to mean that the
190 # target entry no longer exists on the source. Issue a delete to LDAP.
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
191 if ($entrycmp > 0) {
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
192 $targetentry->delete;
193 $ldifout->write_entry($targetentry);
194 $incr_target = 1, next;
195 }
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
196 # Check if the current source entry has a lower sort position than
197 # the current target entry. If so, we interpret this to mean that the
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
198 # source entry doesn't exist on the target. Issue an add to LDAP.
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
199 elsif ($entrycmp < 0) {
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
200 $ldifout->write_entry($sourceentry);
201 $incr_source = 1, next;
202 }
203
204 # When we get here, we're dealing with the same person in $sourceentry
205 # and $targetentry. Compare the data and generate the update.
206
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
207 # If a mod{R}DN is necessary, it needs to happen before other mods
208 if ($dncmp) {
209 my $rdnattr = rdnattr($sourceentry->dn);
210 my $rdnval = rdnval($sourceentry->dn);
211 my $newsuperior = dnsuperior($sourceentry->dn);
212 my $oldsuperior = dnsuperior($targetentry->dn);
213 my $changetype;
214
215 if (cmpDNs($oldsuperior, $newsuperior)) {
216 $changetype = 'moddn';
217 $targetentry->add(newsuperior => $newsuperior);
218 }
219 else { $changetype = 'modrdn' }
220 $targetentry->{changetype} = $changetype;
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
221 $targetentry->add(newrdn => "$rdnattr=$rdnval",
222 deleteoldrdn => '1');
223 $ldifout->write_entry($targetentry);
224 $targetentry->delete('newrdn');
225 $targetentry->delete('deleteoldrdn');
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
226 $targetentry->delete('newsuperior') if $changetype eq 'moddn';
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
227 delete($targetentry->{changetype});
228
229 $targetentry->dn($sourceentry->dn);
2d2ffad @kartiksubbarao Use replace() for post-modrdn correction instead of lower-level interfac...
kartiksubbarao authored
230 $targetentry->replace($rdnattr, $sourceentry->get_value($rdnattr))
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
231 if $sourceentry->exists($rdnattr);
232 }
233
234 # Check for differences and generate LDIF as appropriate
235 updateFromEntry($sourceentry, $targetentry, @sourceattrs);
236 $ldifout->write_entry($targetentry) if @{$targetentry->{changes}};
237 $incr_source = 1, $incr_target = 1, next;
238
239 } continue {
240 if ($incr_source) {
241 $sourceentry = $source->read_entry(); $incr_source = 0;
242 }
243 if ($incr_target) {
244 $targetentry = $target->read_entry(); $incr_target = 0;
245 }
246 }
247 }
248
249 # Generate LDIF to update $target with information in $source.
250 # Optionally restrict the set of attributes to consider.
251 sub updateFromEntry
252 {
253 my ($source, $target, @attrs) = @_;
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
254 my ($attr, $val, $ldifstr);
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
255
256 unless (@attrs) {
257 # add all source entry attributes
258 @attrs = $source->attributes;
259 # add any other attributes we haven't seen from the target entry
260 foreach my $tattr ($target->attributes) {
261 push(@attrs, $tattr) unless grep(/^$tattr$/i, @attrs)
262 }
263 }
264
265 $target->{changetype} = 'modify';
266
267 foreach $attr (@attrs) {
268 my $lcattr = lc $attr;
269 next if $lcattr eq 'dn'; # Can't handle modrdn here
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
270
271 # Build lists of unique values in the source and target, to
272 # speed up comparisons.
273 my @sourcevals = $source->get_value($attr);
274 my @targetvals = $target->get_value($attr);
275 my (%sourceuniqvals, %targetuniqvals);
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
276 foreach (@sourcevals) {
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
277 my ($origval, $val) = ($_, $_);
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
278 $val = lc $val if $ciscmp{$lcattr};
279 # Get rid of spaces after non-escaped commas in DN attrs
280 $val =~ s/(?<!\\),\s+/,/g if $dnattrs{$lcattr};
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
281 $sourceuniqvals{$val} = $origval;
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
282 }
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
283 foreach (@targetvals) {
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
284 my ($origval, $val) = ($_, $_);
b899583 @kartiksubbarao Added options to specify DN attributes, as well as "shared" attributes.
kartiksubbarao authored
285 $val = lc $val if $ciscmp{$lcattr};
286 # Get rid of spaces after non-escaped commas in DN attrs
287 $val =~ s/(?<!\\),\s+/,/g if $dnattrs{$lcattr};
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
288 $targetuniqvals{$val} = $origval;
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
289 }
290 foreach my $val (keys %sourceuniqvals) {
291 if (exists $targetuniqvals{$val}) {
292 delete $sourceuniqvals{$val};
293 delete $targetuniqvals{$val};
294 }
295 }
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
296
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
297 # Move on if there are no differences
298 next unless keys(%sourceuniqvals) || keys(%targetuniqvals);
299
300 # Make changes as appropriate
301 if ($sharedattrs{$lcattr}) {
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
302 # For 'shared' attributes (e.g. objectclass) where $source may not
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
303 # be a sole authoritative source, we issue separate delete and
304 # add modifications instead of a single replace.
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
305 $target->delete($attr => [ values(%targetuniqvals) ])
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
306 if keys(%targetuniqvals);
83cc8ce @kartiksubbarao Fixed a problem where case was being folded to lowercase for diffs on
kartiksubbarao authored
307 $target->add($attr => [ values(%sourceuniqvals) ])
66f1828 @kartiksubbarao Speeded up attribute value comparisons significantly.
kartiksubbarao authored
308 if keys(%sourceuniqvals);
309 }
310 else {
311 # Issue a replace or delete as needed
312 if (@sourcevals) { $target->replace($attr => [ @sourcevals ]) }
313 else { $target->delete($attr) }
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
314 }
315 }
316
317 # Get rid of the "changetype: modify" if there were no changes
318 delete($target->{changetype}) unless @{$target->{changes}};
319 }
320
321
0c27822 @kartiksubbarao Fixed a couple of problems reported by Anthony Milan in perl.ldap:
kartiksubbarao authored
322 =back
323
8959fd2 @gbarr ldapdiff.pl script from Kartik Subbarao
gbarr authored
324 =head1 AUTHOR
325
326 Kartik Subbarao E<lt>subbarao@computer.orgE<gt>
327
328 =cut
329
Something went wrong with that request. Please try again.