/[cvs]/nfo/perl/scripts/outlook2ldap/libs/Torus/Driver/ldap.pm
ViewVC logotype

Contents of /nfo/perl/scripts/outlook2ldap/libs/Torus/Driver/ldap.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (show annotations)
Sat Jan 18 15:57:17 2003 UTC (21 years, 7 months ago) by joko
Branch: MAIN
CVS Tags: v008-1
+ initial check-in

1 package Torus::Driver::ldap;
2
3 require Exporter;
4 our @ISA = qw(Exporter);
5 our @EXPORT = qw( );
6
7 use strict;
8 use warnings;
9
10 # load configuration-data from ini-file
11 BEGIN {
12 use loadConfig;
13 }
14
15 use Net::LDAP;
16 use Net::LDAP::Entry;
17 #use Unicode::MapUTF8 qw(to_utf8 from_utf8 utf8_supported_charset);
18 use Data::Transform::Encode qw( var2utf8 var_utf2iso );
19 use Data::Storage::Handler::File qw( a2f );
20 use Data::Dumper;
21
22 my $hr = "=" x 80 . "\n";
23
24 # debugging?
25 my $DEBUGLEVEL = $config->get("debug_level");
26 my $TRACELEVEL = $config->get("trace_level");
27
28 my $dnCache;
29 my $criticalCache;
30 my $msgCache;
31
32
33 # ==================================================
34 # configure here
35 # ==================================================
36 my $binddn = 'cn=root, dc=labnet, dc=de';
37
38 # V1: hardcoded target ou
39 #my $basedn = 'ou=Adressen, dc=labnet, dc=de';
40
41 # V2: now passed-in to "addEntry"
42
43 my $cfg_objectclasses = [ qw(
44 Person
45 inetOrgPerson
46 organizationalPerson
47 pilotPerson
48 groupOfNames
49 msMapi
50 ) ];
51 # outlookPerson
52
53 #
54 # other object classes:
55 # outlookPerson
56 # ==================================================
57
58
59 my $ldap;
60 my $map;
61
62 my $ldap_errors_file = '../log/ldap_errors.log';
63 sub ldapError {
64 my $message = shift;
65 #print Dumper($message);
66 my $textMessage = $message->error . " - code: " . $message->code;
67 $textMessage .= " - dn: " . $message->{matchedDN} if $message->{matchedDN};
68 #print "msg: '$textMessage'", "\n";
69 a2f($ldap_errors_file, $textMessage) if !$TRACELEVEL;
70 return $message;
71 }
72
73 sub connectStore {
74
75 #print Dumper($config->get("ldapserver_host"));
76
77 #$ldap = Net::LDAP->new('localhost', onerror => 'warn');
78 $ldap = Net::LDAP->new(
79 $config->get("ldapserver_host"),
80 #port => 389,
81 #timeout => 120,
82 debug => 0,
83 #async => 1,
84 #onerror => 'warn',
85 onerror => \&ldapError,
86 #version => 3,
87 )
88 or die("could not connect to ldap-server!");
89
90 $ldap->bind (
91 $binddn,
92 password => 'Geheim'
93 ) or die "$@";
94
95 }
96
97 sub disconnectStore {
98 $ldap->unbind; # take down session
99 }
100
101 sub getEntry {
102
103 die("getEntry!");
104
105 my $basedn = '';
106
107 connectStore();
108
109 my $mesg = $ldap->search ( # perform a search
110 base => $basedn,
111 filter => "(&(sn=*))"
112 );
113
114 $mesg->code && die $mesg->error;
115
116 foreach my $entry ($mesg->all_entries) {
117 $entry->dump;
118 print "\n";
119 }
120
121 disconnectStore();
122
123 }
124
125 sub _example {
126 print "abcdef", "\n";
127 my $result = $ldap->add (
128 'cn = Barbara Jensen, o=University of Michigan, c=us',
129 attr => [ 'cn' => [ 'Barbara Jensen', 'Barbs Jensen' ],
130 'sn' => 'Jensen',
131 'mail' => 'b.jensen@umich.edu',
132 'objectclass' => ['top', 'person',
133 'organizationalPerson',
134 'inetOrgPerson' ],
135 ]
136 );
137
138 }
139
140 sub changeEntry {
141
142 my $dn = shift;
143 my $
144
145 $ldap->modify( $dn,
146 changes => [
147 add => [ sn => 'Barr' ], # Add sn=Barr
148 delete => [ faxNumber => []], # Delete all fax numbers
149 delete => [ telephoneNumber => ['911']], # delete phone number 911
150 replace => [ email => 'gbarr@pobox.com'] # change email address
151 ]
152 );
153
154 }
155
156
157 sub prepareEntry {
158
159 my $basedn = shift;
160 my $mapiEntry = shift;
161
162 my $ldapEntry;
163
164 my $mapfile = '../etc/' . $config->get("fields_mapfile");
165 readFieldMapping($mapfile);
166
167 # dump mapi-entry - don't do that! this is large!
168 #print Dumper($mapiEntry);
169 #exit;
170
171 # utf8-conversion of unmapped mapi-entry - don't do that! this is large!
172 #var2utf8($mapiEntry);
173
174 # map entry
175 foreach my $mapiKey (keys %{$mapiEntry}) {
176 my $ldapKey = $map->{ldap}{$mapiKey};
177 my $ldapValue = $mapiEntry->{$mapiKey};
178 next if (!$ldapKey);
179
180 # utf8-conversion
181 #$ldapKey = toUTF8($ldapKey);
182 #$ldapValue = toUTF8($ldapValue);
183
184 $ldapEntry->{$ldapKey} = $ldapValue;
185 }
186
187 # utf8-conversion of mapped ldap-entry
188 var2utf8($ldapEntry);
189
190 # dump ldap-entry - this is okay
191 #print "ldap-entry before adding:", "\n";
192 #print STDOUT Dumper($ldapEntry);
193
194 # build dn here
195 my $entryIdentifier = buildSnCn( {
196 #sn => $sn,
197 #givenname => $ldapEntry->{givenname},
198 LastName => $mapiEntry->{LastName},
199 FirstName => $mapiEntry->{FirstName},
200 FileAs => $mapiEntry->{FileAs},
201 CompanyName => $mapiEntry->{CompanyName},
202 EntryID => $mapiEntry->{EntryID},
203 } );
204
205 my $sn = $entryIdentifier->{sn};
206 my $cn = $entryIdentifier->{cn};
207
208 if (!$cn) {
209 rememberCriticalEntry("mapi", $mapiEntry->{EntryID});
210 logError('App', "Couldn't build required ldap-attribute \"cn\". LastName/FirstName/FileAs/CompanyName were empty.");
211 return;
212 }
213
214 # remember all already used "cn"s
215 addDnCache($cn);
216
217 my $must = {
218 cn => $cn,
219 sn => $sn,
220 member => $ldapEntry->{member},
221 objectClass => $cfg_objectclasses,
222 };
223 # utf8-conversion of must-have fields
224 var2utf8($must);
225
226 my $dn = buildDn( { basedn => $basedn, cn => $must->{cn} } );
227 rememberMessage($dn, $entryIdentifier->{info});
228
229 return { identifier => $dn, must => $must, may => $ldapEntry };
230
231 }
232
233 sub addEntry {
234
235 my $entry_raw = shift;
236
237 #print Dumper($entry_raw);
238 #return;
239
240 my $identifier = $entry_raw->{identifier};
241 my $must = $entry_raw->{must};
242 my $may = $entry_raw->{may};
243
244 my $dn = $identifier;
245
246 # delete entry (dn) first
247 if (existsEntry('cn', $dn)) {
248 print "entry exists (dn='$dn') deleting", "\n" if $DEBUGLEVEL > 3;
249 $ldap->delete($dn);
250 }
251
252 #print "dn: $dn", "\n";
253 #exit;
254
255 my $entry = Net::LDAP::Entry->new;
256 $entry->dn($dn);
257
258 $entry->add(
259 cn => $must->{cn},
260 sn => $must->{sn},
261 member => $must->{dn},
262 objectClass => $must->{objectClass},
263 );
264 #sn => 'Nachname',
265 #cn => 'Vorname Nachname',
266 my $result1 = $entry->add(%{$may});
267
268 #print Dumper($result1), "\n";
269 #print Dumper($entry);
270
271 my $result = $entry->update($ldap);
272 #print Dumper($ldap->sync());
273
274 #print "result:", "\n";
275 #print Dumper($result);
276 #exit;
277
278 #print "trace-level > 0: ", ($config->get("trace_level") > 0), "\n";
279 #exit;
280
281 #traceEntry($mapiEntry, $entry, { error => $error, prefix => $result->error }) if $TRACELEVEL >= 2;
282 #return 1 if !$error;
283
284 return $result;
285
286 }
287
288 sub logError {
289 my $type = shift;
290 my $message = shift;
291 my $options = shift;
292 if ($DEBUGLEVEL >= 1) {
293 print STDOUT "\n" if $DEBUGLEVEL <= 1;
294 print STDOUT "ERROR ($type): $message", "\n" ;
295 my $buffer = '';
296 foreach (keys %$options) {
297 $buffer .= " $_: $options->{$_}\n" if $options->{$_};
298 }
299 #print STDOUT Dumper($options), "\n";
300 print STDOUT $buffer;
301 }
302 }
303
304 sub logInfo {
305 my $type = shift;
306 my $message = shift;
307 if ($DEBUGLEVEL >= 1) {
308 print STDOUT "\n" if $DEBUGLEVEL <= 1;
309 print STDOUT "INFO ($type): $message", "\n" ;
310 }
311 }
312
313 sub traceEntry {
314 my $entry_source = shift;
315 my $entry_target = shift;
316 my $options = shift;
317
318 my $logfile = '../log/transfer.log';
319 if ($options->{error}) {
320 $logfile = '../log/errors.log';
321 }
322
323 my $dump_source = Dumper($entry_source);
324
325 #my $dump_target = Dumper($entry_target);
326 my $dump_target = "\n";
327 foreach my $attr ($entry_target->attributes) {
328 my $subentry = $attr . ": ";
329 my @value = $entry_target->get_value($attr);
330 if ($#value > 0) {
331 #push @value, "\n ";
332 $subentry .= "\n ";
333 }
334 $subentry .= join("\n ", @value);
335 $dump_target .= $subentry . "\n";
336 }
337
338 my $dump = <<EOD;
339 $hr
340 MAPI-Object:
341 $dump_source
342 $hr
343 Net::LDAP::Entry:
344 $dump_target
345 $hr
346 EOD
347
348 $dump = $hr . $options->{prefix} . $dump if $options->{prefix};
349
350 a2f($logfile, $dump);
351
352 }
353
354 sub toUTF8 {
355 my $string = shift;
356 #return to_utf8( -string => $string, -charset => 'ISO-8859-1');
357 print "before: $string", "\n";
358 var2utf8(\$string);
359 print "after: $string", "\n";
360 return $string;
361 }
362
363 sub readFieldMapping {
364 my $mapfile = shift;
365 if (! -e $mapfile) {
366 print "mapfile \"$mapfile\" does not exist.", "\n";
367 exit;
368 }
369 open(FH, '<', $mapfile);
370 while(<FH>) {
371 s/\r\n/\n/g;
372 chomp();
373 next if (m/^#/);
374 my @entry = split(';', $_);
375 my $key = $entry[0];
376 my $key_ldap = $entry[1];
377 $key_ldap ||= '';
378 $map->{mapi}{$key} = 1;
379 $map->{ldap}{$key} = $key_ldap;
380 }
381 close(FH);
382 }
383
384 sub createOuSafe {
385
386 #my $basedn = shift;
387 #my $cn = shift;
388
389 #my $dn = 'cn=' . $cn . ', ' . $basedn;
390 my $dn = shift;
391
392 #print "orig-dn: $dn", "\n";
393
394 $dn =~ m/ou=([\w|\.]+?)[,|].*/;
395 my $ou = $1;
396 #print "ou: '$ou'", "\n";
397
398 my $basedn = $dn;
399 $basedn =~ s/ou=$ou, //;
400 #print "basedn: $basedn", "\n";
401 #exit;
402
403 print "checking ou: (basedn='$basedn', ou='$ou')", "\n" if $DEBUGLEVEL > 3;
404 # if (checkOu($basedn, $ou)) {
405 if (existsEntry('ou', $dn)) {
406 return $dn;
407 }
408
409 if (my @ou_address = split(/\./, $ou)) {
410 return createDeepOu($basedn, \@ou_address);
411 } else {
412 return createOu($dn, $ou);
413 }
414
415 }
416
417 sub createDeepOu {
418 my $basedn = shift;
419 my $ou_address = shift;
420 my $dn = $basedn;
421 print "creating deep ou (basedn='$basedn')", "\n" if $DEBUGLEVEL > 3;
422 foreach (@$ou_address) {
423 my $ou = $_;
424 #print "ou: $ou", "\n";
425 $dn = "ou=$ou, " . $dn;
426 #print "dn: $dn", "\n";
427 createOu($dn, $ou);
428 }
429 return $dn;
430 }
431
432 sub createOu {
433 my $dn = shift;
434 my $ou = shift;
435 print "creating ou (DN='$dn', OU='$ou')", "\n" if $DEBUGLEVEL > 3;
436 my $entry = Net::LDAP::Entry->new;
437 $entry->dn($dn);
438 $entry->add(
439 #cn => $cn,
440 ou => $ou,
441 objectClass => 'organizationalUnit',
442 );
443 my $result = $entry->update($ldap);
444 #return $result;
445 return $dn;
446 }
447
448 sub checkOu {
449 my $basedn = shift;
450 my $ou = shift;
451
452 my $mesg = $ldap->search ( # perform a search
453 base => $basedn,
454 filter => "(&(ou=$ou))"
455 );
456
457 #print "search-result-code: ", $mesg->code, "\n";
458 #print "search-result-error: ", $mesg->error, "\n";
459
460 #return;
461 #print Dumper($mesg);
462 #exit;
463
464 #$mesg->code && die $mesg->error;
465
466 return 1 if exists $mesg->{entries};
467
468 }
469
470 sub existsEntry {
471
472 my $ldapType = shift;
473 my $dn = shift;
474
475 my $filter = "$ldapType =*";
476 #print "using filter: '$filter'", "\n";
477
478 # get part from full LDAP-address
479 #print "dn: $dn", "\n";
480 #$dn =~ m/$ldapType=([\w|\.| ]+?)[,|].*/;
481 #my $part = $1;
482
483 # cut part
484 #print "ldapType: $ldapType", "\n";
485 #print "part: $part", "\n";
486 #$dn =~ s/$ldapType=$part, //;
487
488 # TODO:
489 # - split 'ou' or 'cn' from 'dn' here to create a search-attribute (one) and a $basedn (a parent node)
490 # - run search with $filter = 'ou|cn =*' (<part-from-regex>) and $basedn=(<rest-from-regex>)
491
492 $filter = "(objectClass=*)";
493 #print "searching for dn='$dn' with filter='$filter'", "\n";
494 my $mesg = $ldap->search ( # perform a search
495 base => $dn,
496 filter => $filter,
497 # filter => "(&(ou=*))"
498 );
499
500 #print "search-result-code: ", $mesg->code, "\n";
501 #print "search-result-error: ", $mesg->error, "\n";
502
503 #return;
504 #print Dumper($mesg);
505 #exit;
506
507 #$mesg->code && die $mesg->error;
508
509 return 1 if exists $mesg->{entries};
510 return 0;
511
512 }
513
514 sub buildSnCn {
515 my $parts = shift;
516
517 my $logmsg;
518
519 my $cn;
520
521 if ($parts->{LastName}) {
522 # use pure "sn" first!
523 $cn = $parts->{LastName} ;
524
525 # add "givenname" to "cn" if exists
526 $cn = $parts->{FirstName} . ' ' . $cn if $parts->{FirstName};
527 }
528
529 # check if "cn" is already filled, else provide some fallback-mechanism(s) here
530 # use "FileAs"
531 if (!$cn && $parts->{FileAs}) {
532 $cn = $parts->{FileAs};
533 $logmsg = "using \"FileAs\" for \"cn\": $cn";
534 }
535 # use "FirstName"
536 if (!$cn && $parts->{FirstName}) {
537 $cn = $parts->{FirstName};
538 $logmsg = "using \"FirstName\" for \"cn\": $cn";
539 }
540 # use "CompanyName"
541 if (!$cn && $parts->{CompanyName}) {
542 $cn = $parts->{CompanyName};
543 $logmsg = "using \"CompanyName\" for \"cn\": $cn";
544 }
545
546 # handle "must-have"-field "sn"
547 my $sn = $parts->{LastName};
548
549 # additional (last) rule: use unique identifier (EntryID) as "cn" if it's still empty
550 #$cn = 'ident - ' . $parts->{EntryID} if !$cn;
551 if (!$cn) {
552 $cn = $parts->{EntryID};
553 $logmsg = "\"cn\" was empty, using EntryID.\n - cn: $cn";
554 }
555
556 # fallback to "cn" if "sn" is empty
557 $sn = $cn if !$sn;
558
559 # check for collisions in "cn"s
560 if (dnAlreadyUsed($cn)) {
561 #logError('App', "Couldn't use cn='$cn' - already exists!");
562 #return;
563 $cn .= '-' . $parts->{EntryID};
564 $logmsg .= "\n - modified \"cn\" to prevent name-collision.\n - cn: $cn";
565 }
566
567 #$msgCache->{} = $logmsg;
568 logInfo('App::buildCn', $logmsg) if $logmsg;
569
570 return { sn => $sn, cn => $cn, info => $logmsg };
571
572 }
573
574 sub buildDn {
575 my $parts = shift;
576
577 # build "dn"
578 my $dn = join(', ', "cn=" . $parts->{cn}, $parts->{basedn});
579
580 # patch dn (remove forbidden characters)
581 $dn =~ s/\+/&/g;
582
583 return $dn;
584 }
585
586 sub clearDnCache {
587 $dnCache = {};
588 $msgCache = {};
589 }
590
591 sub addDnCache {
592 my $key = shift;
593 $dnCache->{$key}++;
594 }
595
596 sub dnAlreadyUsed {
597 my $key = shift;
598 return exists $dnCache->{$key};
599 }
600
601 sub rememberCriticalEntry {
602 my $type = shift;
603 my $identifier = shift;
604 push @$criticalCache, { type => $type, identifier => $identifier };
605 }
606
607 sub showCriticalEntries {
608 return if !$criticalCache;
609 my @criticals = @$criticalCache;
610 if ($#criticals != -1) {
611 print "=" x 60, "\n";
612 print "Some errors occoured while processing data, show details? (y|n) ";
613 my $answer = <STDIN>;
614 print "\n";
615 if ($answer =~ m/^y/i) {
616 foreach (@criticals) {
617 #print join("\n", @criticals);
618 #print Dumper($_);
619 print $_->{type}, ": ", $_->{identifier}, "\n";
620 print " ", $msgCache->{$_->{identifier}}, "\n";
621 delete $msgCache->{$_->{identifier}};
622 }
623 }
624 }
625 }
626
627 sub showGoodEntries {
628 print "=" x 60, "\n";
629 print "Show good entries? (y|n) ";
630 my $answer = <STDIN>;
631 print "\n";
632 if ($answer =~ m/^y/i) {
633 foreach (keys %$msgCache) {
634 print $_, ": ", $msgCache->{$_}, "\n" if $msgCache->{$_};
635 }
636 }
637 }
638
639 sub processResult {
640
641 my $dn = shift;
642 my $result = shift;
643
644 #print "dn: $dn", "\n";
645 #print Dumper($result);
646
647 my $error = 0;
648 my $error_code;
649 my $error_message;
650 if ($result) {
651 $error_code = $result->code;
652 $error_message = $result->error;
653 } else {
654 $error_code = 'n/a';
655 $error_message = "NO RESULT FROM LDAP-SERVER";
656 }
657
658 if ($error_code) {
659 rememberCriticalEntry("ldap", $dn);
660 my $errmesg = "(error=$error_message, code=$error_code)";
661 rememberMessage($dn, $errmesg);
662 logError("LDAP", $errmesg, { dn => $dn } );
663 $error = 1;
664 } else {
665 print "SUCCESS for (dn='$dn')", "\n" if $DEBUGLEVEL > 1;
666 }
667
668 undef $result;
669 }
670
671 sub rememberMessage {
672 my $ident = shift;
673 my $mesg = shift;
674 $msgCache->{$ident} = $mesg;
675 }
676
677 1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed