1# -*- mode: cperl; eval: (follow-mode); -*- 2# 3 4package App::Regather; 5 6use strict; 7use warnings; 8use diagnostics; 9 10use Carp; 11use File::Basename; 12use Getopt::Long qw(:config no_ignore_case gnu_getopt auto_version); 13use IPC::Open2; 14use List::Util qw(uniqstr); 15 16use Net::LDAP; 17use Net::LDAP::LDIF; 18use Net::LDAP::Constant qw( LDAP_SYNC_REFRESH_ONLY 19 LDAP_SYNC_REFRESH_AND_PERSIST 20 LDAP_SUCCESS 21 LDAP_SYNC_PRESENT 22 LDAP_SYNC_ADD 23 LDAP_SYNC_MODIFY 24 LDAP_SYNC_DELETE 25 LDAP_CONNECT_ERROR 26 LDAP_OPERATIONS_ERROR 27 LDAP_LOCAL_ERROR ); 28use Net::LDAP::Control::SyncRequest; 29use Net::LDAP::Util qw(generalizedTime_to_time); 30 31use POSIX; 32use Pod::Usage qw(pod2usage); 33use Sys::Syslog qw(:standard :macros); 34use Template; 35 36use App::Regather::Config; 37use App::Regather::Logg; 38use App::Regather::Plugin; 39 40use constant SYNST => [ qw( LDAP_SYNC_PRESENT LDAP_SYNC_ADD LDAP_SYNC_MODIFY LDAP_SYNC_DELETE ) ]; 41 42# my @DAEMONARGS = ($0, @ARGV); 43our $VERSION = '0.81.03'; 44 45sub new { 46 my $class = shift; 47 my $self = 48 bless { 49 _progname => fileparse($0), 50 _daemonargs => [$0, @ARGV], 51 _opt => { 52 ch => undef, 53 cli => undef, 54 colors => 0, 55 config => '/usr/local/etc/regather.conf', 56 fg => 0, 57 force => 0, 58 plugin_list => 0, 59 strict => 0, 60 ts_fmt => "%a %F %T %Z (%z)", 61 v => 0, 62 } 63 }, $class; 64 65 GetOptions( 66 'f|foreground' => \$self->{_opt}{fg}, 67 'F|force' => \$self->{_opt}{force}, 68 'c|config=s' => \$self->{_opt}{config}, 69 'colors' => \$self->{_opt}{colors}, 70 'C|cli=s%' => \$self->{_opt}{cli}, 71 'S|strict' => \$self->{_opt}{strict}, 72 'config-help' => \$self->{_opt}{ch}, 73 'plugin-list' => \$self->{_opt}{plugin_list}, 74 'v+' => \$self->{_opt}{v}, 75 'h' => sub { pod2usage(-exitval => 0, -verbose => 2); exit 0 }, 76 ); 77 78 $self->{_opt}{l} = new 79 App::Regather::Logg( prognam => $self->{_progname}, 80 foreground => $self->{_opt}{fg}, 81 colors => $self->{_opt}{colors} ); 82 83 if ( $self->{_opt}{plugin_list} ) { 84 App::Regather::Plugin->new( 'list' )->run; 85 exit 0; 86 } 87 88 $self->{_opt}{cf} = new 89 App::Regather::Config ( filename => $self->{_opt}{config}, 90 logger => $self->{_opt}{l}, 91 cli => $self->{_opt}{cli}, 92 verbose => $self->{_opt}{v} ); 93 94 my $cf_mode = (stat($self->{_opt}{config}))[2] & 0777; 95 my $fm_msg; 96 if ( $cf_mode & 002 || $cf_mode & 006 ) { 97 $fm_msg = 'world'; 98 } elsif ( $cf_mode & 020 || $cf_mode & 060) { 99 $fm_msg = 'group'; 100 } 101 if ( defined $fm_msg ) { 102 $self->{_opt}{l}->cc( pr => 'err', fm => 'config file is accessible by ' . $fm_msg); 103 pod2usage(-exitval => 2, -sections => [ qw(USAGE) ]); 104 exit 1; 105 } 106 107 $self->{_opt}{last_forever} = 1; 108 109 # !!! TO CORRECT 110 if ( ! defined $self->{_opt}{cf} ) { 111 $self->l->cc( pr => 'err', fm => "do fix config file ..." ); 112 pod2usage(-exitval => 2, -sections => [ qw(USAGE) ]); 113 exit 1; 114 } 115 116 if ( $self->{_opt}{ch} ) { 117 $self->{_opt}{cf}->config_help; 118 exit 1; 119 } 120 121 return $self; 122} 123 124sub progname { shift->{_progname} } 125 126sub progargs { return join(' ', @{shift->{_daemonargs}}); } 127 128sub cf { shift->{_opt}{cf} } 129 130sub l { shift->{_opt}{l} } 131 132sub o { 133 my ($self,$opt) = @_; 134 croak "unknown/undefined variable" 135 if ! exists $self->{_opt}{$opt}; 136 return $self->{_opt}{$opt}; 137} 138 139sub run { 140 my $self = shift; 141 142 $self->l->cc( pr => 'info', fm => "App::Regather::Logg initialized ..." ) if $self->o('v') > 1; 143 144 $self->l->cc( pr => 'info', fm => "%s: options provided from CLI:\n%s", ls => [ __PACKAGE__, $self->o('cli') ] ) 145 if defined $self->o('cli') && keys( %{$self->o('cli')} ) && $self->o('v') > 1; 146 147 $self->l->set_m( $self->cf->getnode('log')->as_hash ); 148 $self->l->set( notify => [ $self->cf->get('core', 'notify') ] ); 149 $self->l->set( notify_email => [ $self->cf->get('core', 'notify_email') ] ); 150 151 $self->l->cc( pr => 'info', fm => "%s: Dry Run is set on, no file is to be changed\n" ) 152 if $self->cf->get(qw(core dryrun)); 153 $self->l->cc( pr => 'info', fm => "%s: Config::Parse object as hash:\n%s", 154 ls => [ __PACKAGE__, $self->cf->as_hash ] ) if $self->o('v') > 3; 155 $self->l->cc( pr => 'info', fm => "%s: %s", 156 ls => [ __PACKAGE__, $self->progargs ] ); 157 $self->l->cc( pr => 'info', fm => "%s: %s v.%s is starting ...", 158 ls => [ __PACKAGE__, $self->progname, $VERSION, ] ); 159 160 @{$self->{_opt}{svc}} = grep { $self->cf->get('service', $_, 'skip') != 1 } $self->cf->names_of('service'); 161 162 $self->daemonize if ! $self->o('fg'); 163 164 our $s; 165 my $tmp; 166 my $cfgattrs = []; 167 my $mesg; 168 my @svc_map; 169 170 foreach my $i ( @{$self->{_opt}{svc}} ) { 171 foreach ( qw( s m ) ) { 172 if ( $self->cf->is_section('service', $i, 'map', $_) ) { 173 @svc_map = values( %{ $self->cf->getnode('service', $i, 'map', $_)->as_hash } ); 174 # push @svc_map, $self->cf->getnode('service', $i, 'ctrl_attr'); 175 $cfgattrs = [ @{$cfgattrs}, @svc_map, @{$self->cf->get('service', $i, 'ctrl_attr')} ]; 176 } else { 177 @svc_map = (); 178 } 179 } 180 181 push @{$cfgattrs}, '*' 182 if $self->cf->get('service', $i, 'all_attr') != 0; 183 184 $self->l->cc( pr => 'warning', ls => [ __PACKAGE__, $i, ], 185 fm => "%s: no LDAP attribute to process is mapped for service `%s`" ) 186 if $self->cf->get('service', $i, 'all_attr') == 0 && scalar @svc_map == 0; 187 188 } 189 190 @{$tmp} = sort @{[ @{$cfgattrs}, qw( associatedDomain 191 authorizedService 192 description 193 entryUUID 194 entryCSN 195 createTimestamp 196 creatorsName 197 modifiersName 198 modifyTimestamp ) ]}; 199 @{$cfgattrs} = uniqstr @{$tmp}; 200 201 # 202 ## -=== MAIN LOOP =====================================================- 203 # 204 205 my $ldap_opt = $self->cf->getnode(qw(ldap opt))->as_hash; 206 my $uri = delete $ldap_opt->{uri}; 207 while ( $self->o('last_forever') ) { 208 if ( $self->cf->is_set(qw(core altroot)) ) { 209 chdir($self->cf->get(qw(core altroot))) || do { 210 $self->l->cc( pr => 'err', fm => "%s: main: unable to chdir to %s", 211 ls => [ __PACKAGE__, $self->cf->get(qw(core altroot)) ] ); 212 exit 1; 213 }; 214 } 215 216 $self->{_opt}{ldap} = Net::LDAP->new( $uri, 217 @{[ map { $_ => $ldap_opt->{$_} } %$ldap_opt ]} ) 218 || do { 219 $self->l->cc( pr => 'err', fm => "%s: Unable to connect to %s; error: %s", 220 ls => [ __PACKAGE__, $uri, $! ] ); 221 if ( $self->o('strict') ) { 222 exit LDAP_CONNECT_ERROR; 223 } else { 224 next; 225 } 226 }; 227 228 my $start_tls_options = $self->cf->getnode(qw(ldap ssl))->as_hash if $self->cf->is_section(qw(ldap ssl)); 229 if ( exists $start_tls_options->{ssl} && $start_tls_options->{ssl} eq 'start_tls' ) { 230 delete $start_tls_options->{ssl}; 231 eval { 232 $mesg = 233 $self->o('ldap')->start_tls( @{[ map { $_ => $start_tls_options->{$_} } %$start_tls_options ]} ); 234 }; 235 if ( $@ ) { 236 $self->l->cc( pr => 'err', fm => "%s: TLS negotiation failed: %s", ls => [ __PACKAGE__, $! ] ); 237 if ( $self->o('strict') ) { 238 exit LDAP_CONNECT_ERROR; 239 } else { 240 next; 241 } 242 } else { 243 $self->l->cc( pr => 'info', fm => "%s: TLS negotiation succeeded" ) if $self->o('v') > 1; 244 } 245 } 246 247 my $bind = $self->cf->getnode(qw(ldap bnd))->as_hash if $self->cf->is_section(qw(ldap bnd)); 248 if ( ref($bind) eq 'HASH' ) { 249 if ( exists $bind->{dn} ) { 250 my @bind_options; 251 push @bind_options, delete $bind->{dn}; 252 while ( my($k, $v) = each %{$bind} ) { 253 push @bind_options, $k => $v; 254 } 255 $mesg = $self->o('ldap')->bind( @bind_options ); 256 if ( $mesg->code ) { 257 ####### !!!!!!! TODO: to implement exponential delay on error sending to awoid log file/notify 258 ####### !!!!!!! queue overflow 259 $self->l->cc( pr => 'err', fm => "%s: bind error: %s", ls => [ __PACKAGE__, $mesg->error ] ); 260 if ( $self->o('strict') ) { 261 exit $mesg->code; 262 } else { 263 next; 264 } 265 } 266 } 267 } 268 269 $self->{_opt}{req} = Net::LDAP::Control::SyncRequest->new( mode => LDAP_SYNC_REFRESH_AND_PERSIST, 270 critical => 1, 271 cookie => undef, ); 272 273 $mesg = $self->o('ldap')->search( base => $self->cf->get(qw(ldap srch base)), 274 scope => $self->cf->get(qw(ldap srch scope)), 275 control => [ $self->o('req') ], 276 callback => sub {$self->ldap_search_callback(@_)}, 277 filter => $self->cf->get(qw(ldap srch filter)), 278 attrs => $cfgattrs, 279 sizelimit=> $self->cf->get(qw(ldap srch sizelimit)), 280 timelimit=> $self->cf->get(qw(ldap srch timelimit)), 281 ); 282 if ( $mesg->code ) { 283 $self->l->cc( pr => 'err', 284 fm => "%s: LDAP search ERROR...\n% 13s%s\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 285 ls => [ __PACKAGE__, 286 'base: ', $self->cf->get(qw(ldap srch base)), 287 'scope: ', $self->cf->get(qw(ldap srch scope)), 288 'filter: ', $self->cf->get(qw(ldap srch filter)), 289 'attrs: ', join("\n", @{$cfgattrs}) ] ); 290 $self->l->cc_ldap_err( mesg => $mesg ); 291 exit $mesg->code if $self->o('strict'); 292 } else { 293 $self->l->cc( pr => 'info', 294 fm => "%s: LDAP search:\n% 13s%s\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 295 ls => [ __PACKAGE__, 296 'base: ', $self->cf->get(qw(ldap srch base)), 297 'scope: ', $self->cf->get(qw(ldap srch scope)), 298 'filter: ', $self->cf->get(qw(ldap srch filter)), 299 'attrs: ', join("\n", @{$cfgattrs}) ] ) if $self->o('v') > 2; 300 } 301 } 302 303 $mesg = $self->o('ldap')->unbind; 304 if ( $mesg->code ) { 305 $self->l->cc_ldap_err( mesg => $mesg ); 306 exit $mesg->code; 307 } 308 309 closelog(); 310 311} 312 313# 314## =================================================================== 315# 316 317sub daemonize { 318 my $self = shift; 319 320 my ( $pid, $fh, $pp, $orphaned_pid_mtime ); 321 if ( -e $self->cf->get(qw(core pid_file)) ) { 322 open( $fh, "<", $self->cf->get(qw(core pid_file))) || do { 323 die "Can't open $self->cf->get(qw(core pid_file)) for reading: $!"; 324 exit 1; 325 }; 326 $pid = <$fh>; 327 close($fh) || do { 328 print "close $self->cf->get(qw(core pid_file)) (opened for reading) failed: $!\n\n"; 329 exit 1; 330 }; 331 332 if ( kill(0, $pid) ) { 333 print "Doing nothing\npidfile $self->cf->get(qw(core pid_file)) of the proces with pid $pid, exists and the very process is alive\n\n"; 334 exit 1; 335 } 336 337 $orphaned_pid_mtime = strftime( $self->o('ts_fmt'), localtime( (stat( $self->cf->get(qw(core pid_file)) ))[9] )); 338 if ( unlink $self->cf->get(qw(core pid_file)) ) { 339 $self->l->cc( pr => 'debug', fm => "%s: orphaned %s was removed", 340 ls => [ __PACKAGE__, $self->cf->get(qw(core pid_file)) ] ) 341 if $self->o('v') > 0; 342 } else { 343 $self->l->cc( pr => 'err', fm => "%s: orphaned %s (mtime: %s) was not removed: %s", 344 ls => [ __PACKAGE__, $self->cf->get(qw(core pid_file)), $orphaned_pid_mtime, $! ] ); 345 exit 2; 346 } 347 348 undef $pid; 349 } 350 351 $pid = fork(); 352 die "fork went wrong: $!\n\n" unless defined $pid; 353 exit(0) if $pid != 0; 354 355 setsid || do { print "setsid went wrong: $!\n\n"; exit 1; }; 356 357 open( $pp, ">", $self->cf->get(qw(core pid_file))) || do { 358 print "Can't open $self->cf->get(qw(core pid_file)) for writing: $!"; exit 1; }; 359 print $pp "$$"; 360 close( $pp ) || do { 361 print "close $self->cf->get(qw(core pid_file)) (opened for writing), failed: $!\n\n"; exit 1; }; 362 363 if ( $self->o('v') > 1 ) { 364 open (STDIN, "</dev/null") || do { print "Can't redirect /dev/null to STDIN\n\n"; exit 1; }; 365 open (STDOUT, ">/dev/null") || do { print "Can't redirect STDOUT to /dev/null\n\n"; exit 1; }; 366 open (STDERR, ">&STDOUT") || do { print "Can't redirect STDERR to STDOUT\n\n"; exit 1; }; 367 } 368 369 $SIG{HUP} = 370 sub { my $sig = @_; 371 $self->l->cc( pr => 'warning', fm => "%s: SIG %s received, restarting", ls => [ __PACKAGE__, $sig ] ); 372 exec('perl', @{$self->o('_daemonargs')}); }; 373 $SIG{INT} = $SIG{QUIT} = $SIG{ABRT} = $SIG{TERM} = 374 sub { my $sig = @_; 375 $self->l->cc( pr => 'warning', fm => "%s: SIG %s received, exiting", ls => [ __PACKAGE__, $sig ] ); 376 $self->{_opt}{last_forever} = 0; 377 }; 378 $SIG{PIPE} = 'ignore'; 379 $SIG{USR1} = 380 sub { my $sig = @_; 381 $self->l->cc( pr => 'warning', fm => "%s: SIG %s received, doing nothing" ), ls => [ __PACKAGE__, $sig ] }; 382 383 if ( $self->cf->is_set(qw(core uid)) && $self->cf->is_set(qw(core gid)) ) { 384 setgid ( $self->cf->get(qw(core gid_number)) ) || do { print "setgid went wrong: $!\n\n"; exit 1; }; 385 setuid ( $self->cf->get(qw(core uid_number)) ) || do { print "setuid went wrong: $!\n\n"; exit 1; }; 386 } 387 388 $self->l->cc( pr => 'info', fm => "%s: %s v.%s is started.", ls => [ __PACKAGE__, $self->progname, $VERSION ] ); 389} 390 391sub ldap_search_callback { 392 my ( $self, $msg, $obj ) = @_; 393 394 395 my @controls = $msg->control; 396 my $syncstate = scalar @controls ? $controls[0] : undef; 397 398 my ( $s, $st, $mesg, $entry, @entries, $ldif, $map, 399 $out_file_pfx_old, 400 $tmp_debug_msg, 401 $rdn, $rdn_old, $rdn_re, 402 $pp, $chin, $chou, $chst, $cher, $email, $email_body ); 403 404 ######## !! not needed ? 405 my $out_file_old; 406 407 $self->l->cc( pr => 'debug', fm => "%s: syncstate: %s", ls => [ __PACKAGE__, $syncstate ] ) 408 if $self->o('v') > 5; 409 $self->l->cc( pr => 'debug', fm => "%s: object: %s", ls => [ __PACKAGE__, $obj ] ) if $self->o('v') > 5; 410 411 if ( defined $obj && $obj->isa('Net::LDAP::Entry') ) { 412 $rdn = ( split(/=/, ( split(/,/, $obj->dn) )[0]) )[0]; 413 if ( defined $syncstate && $syncstate->isa('Net::LDAP::Control::SyncState') ) { 414 $self->l->cc( pr => 'debug', fm => "%s: SYNCSTATE:\n%s:", ls => [ __PACKAGE__, $syncstate ] ) 415 if $self->o('v') > 4; 416 $st = $syncstate->state; 417 my %reqmod; 418 $self->l->cc( fm => "%s: received control %s: dn: %s", ls => [ __PACKAGE__, SYNST->[$st], $obj->dn ] ); 419 420 ####################################################################### 421 ####### --- PRELIMINARY STUFF ----------------------------->>>>>>>>> 0 422 ####################################################################### 423 424 ### LDAP_SYNC_DELETE arrives for both cases, object deletetion and attribute 425 ### deletion and in both cases Net::LDAP::Entry provided contains only DN, 426 ### so, we need to "re-construct" it for further processing 427 if ( $st == LDAP_SYNC_DELETE ) { 428 $mesg = $self->o('ldap')->search( base => $self->cf->get(qw(ldap srch log_base)), 429 scope => 'sub', 430 sizelimit=> $self->cf->get(qw(ldap srch sizelimit)), 431 timelimit=> $self->cf->get(qw(ldap srch timelimit)), 432 filter => '(reqDN=' . $obj->dn . ')', ); 433 if ( $mesg->code ) { 434 $self->l->cc( pr => 'err', nt => 1, 435 fm => "%s: LDAP accesslog search on %s, error:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 436 ls => [ __PACKAGE__, SYNST->[$st], 437 'base: ', $self->cf->get(qw(ldap srch log_base)), 438 'scope: ', 'sub', 439 'filter: ', '(reqDN=' . $obj->dn . ')' ] ); 440 $self->l->cc_ldap_err( mesg => $mesg ); 441 # exit $mesg->code; # !!! NEED TO DECIDE WHAT TO DO 442 } else { 443 $entry = pop @{[$mesg->sorted]}; 444 445 if ( ! $entry->isa('Net::LDAP::Entry') ) { 446 $self->l->cc( pr => 'err', nt => 1, 447 fm => "%s: LDAP accesslog search on %s, returned no result:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 448 ls => [ __PACKAGE__, SYNST->[$st], 449 'base: ', $self->cf->get(qw(ldap srch log_base)), 450 'scope: ', 'sub', 451 'filter: ', '(reqDN=' . $obj->dn . ')' ] ); 452 return; 453 } elsif ( $entry->get_value('reqType') eq 'delete' ) { 454 my $reqold = 'dn: ' . $obj->dn; 455 foreach ( @{$entry->get_value('reqOld', asref => 1)} ) { 456 s/^(.*;binary:) .*$/$1: c3R1Yg==/agis; 457 $reqold .= "\n" . $_; 458 } 459 my ( $file, @err ); 460 open( $file, "<", \$reqold) || 461 $self->l->cc( pr => 'err', 462 fm => "%s: Cannot open data from variable to read ldif: %s", 463 ls => [ __PACKAGE__, $! ] ); 464 $ldif = Net::LDAP::LDIF->new( $file, "r", onerror => 'warn' ); 465 while ( not $ldif->eof ) { 466 $entry = $ldif->read_entry; 467 $self->l->cc( pr => 'err', fm => "%s: Reading LDIF error: %s", 468 ls => [ __PACKAGE__, $ldif->error ] ) if $ldif->error; 469 } 470 $obj = $entry; 471 $ldif->done; 472 } elsif ( $entry->get_value('reqType') eq 'modify' ) { 473 ### here we re-assemble $obj to have all attributes before deletion and since 474 ### after that it'll has ctrl_attr but reqType=delete, it'll go to $st == LDAP_SYNC_DELETE 475 %reqmod = map { substr($_, 0, -2) => 1 } grep { /^(.*):-$/g } 476 @{$entry->get_value('reqMod', asref => 1)}; 477 478 $mesg = $self->o('ldap')->search( base => $obj->dn, 479 scope => 'base', 480 filter => '(objectClass=*)', ); 481 if ( $mesg->code ) { 482 $self->l->cc( pr => 'err', nt => 1, 483 fm => "%s: LDAP search %s %s error:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 484 ls => [ __PACKAGE__, SYNST->[$st], 'reqType=modify', 485 'base: ', $self->cf->get(qw(ldap srch log_base)), 486 'scope: ', 'sub', 487 'filter: ', '(reqDN=' . $obj->dn . ')' ] ); 488 $self->l->cc_ldap_err( mesg => $mesg ); 489 # exit $mesg->code; # !!! NEED TO DECIDE WHAT TO DO 490 } else { 491 $obj = $mesg->entry(0); 492 $obj->add( map { $_ => $reqmod{$_} } keys %reqmod ); 493 # $obj->add( $_ => $reqmod{$_} ) foreach ( keys %reqmod ); 494 } 495 $self->l->cc( pr => 'debug', fm => "%s: %s reqType=modify reqMod: %s", 496 ls => [ __PACKAGE__, SYNST->[$st], \%reqmod ] ) if $self->o('v') > 3; 497 } 498 } 499 } elsif ( $st == LDAP_SYNC_MODIFY ) { 500 $mesg = $self->o('ldap')->search( base => $self->cf->get(qw(ldap srch log_base)), 501 scope => 'sub', 502 sizelimit=> $self->cf->get(qw(ldap srch sizelimit)), 503 timelimit=> $self->cf->get(qw(ldap srch timelimit)), 504 filter => '(reqDN=' . $obj->dn . ')', ); 505 if ( $mesg->code ) { 506 $self->l->cc( pr => 'err', nt => 1, 507 fm => "%s: LDAP accesslog search on %s, error:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 508 ls => [ __PACKAGE__, SYNST->[$st], nt => 1, 509 'base: ', $self->cf->get(qw(ldap srch log_base)), 510 'scope: ', 'sub', 511 'filter: ', '(reqDN=' . $obj->dn . ')' ] ); 512 $self->l->cc_ldap_err( mesg => $mesg ); 513 } else { 514 if ( $mesg->count > 0 ) { 515 ### modified object has accesslog records when it was add/modify/delete 516 ### before, as well as ModRDN ... so, we need to be sure, there is no accesslog 517 ### object with reqNewRDN=<$obj->dn RDN> close to the processing time of this $obj 518 519 ### NEED TO BE FINISHED 520 521 } elsif ( $mesg->count == 0 ) { 522 ### modified object has no accesslog records when it was ModRDN-ed so, we search 523 ### for accesslog object with reqNewRDN=<$obj->dn RDN> to know old object RDN to use 524 ### it further for $out_file 525 $mesg = $self->o('ldap')->search( base => $self->cf->get(qw(ldap srch log_base)), 526 scope => 'sub', 527 sizelimit=> $self->cf->get(qw(ldap srch sizelimit)), 528 timelimit=> $self->cf->get(qw(ldap srch timelimit)), 529 filter => '(reqNewRDN=' . (split(/,/, $obj->dn))[0] . ')', ); 530 if ( $mesg->code ) { 531 $self->l->cc( pr => 'err', nt => 1, 532 fm => "%s: LDAP accesslog search on %s, error:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 533 ls => [ __PACKAGE__, SYNST->[$st], 534 'base: ', $self->cf->get(qw(ldap srch log_base)), 535 'scope: ', 'sub', 536 'filter: ', '(reqNewRDN=' . (split(/,/, $obj->dn))[0] . ')' ] ); 537 $self->l->cc_ldap_err( mesg => $mesg ); 538 # exit $mesg->code; # !!! NEED TO DECIDE WHAT TO DO 539 } else { 540 ### here we pick last reqNewRDN entry up, to find the latest UUID for entries 541 ### with same DN if the object was added/deleted/ModRDN-ed several times 542 @entries = $mesg->sorted; 543 $entry = pop @entries; 544 if ( defined $entry ) { 545 $rdn_re = qr/^$rdn: .*$/; 546 ###### !!! NEDD FIX 547 ### slapd.log-20210510-regather-fails-on-rdn 548 ### 549 ### here we're searching master db log, and the record is absent there, while 550 ### it is still present (have no idea why) in local db log ... 551 ### 552 ### Can't use an undefined value as an ARRAY reference at /usr/local/lib/perl5/site_perl/App/Regather.pm line 546. 553 ### BEGIN failed--compilation aborted at /usr/local/bin/regather line 10 (#1) 554 ### (F) A value used as either a hard reference or a symbolic reference must 555 ### be a defined value. This helps to delurk some insidious errors. 556 ### Uncaught exception from user code: 557 ### Can't use an undefined value as an ARRAY reference at /usr/local/lib/perl5/site_perl/App/Regather.pm line 546. 558 ### BEGIN failed--compilation aborted at /usr/local/bin/regather line 10. 559 ###### !!! NEED FIX 560 if ( $entry->exists('reqOld') { 561 foreach ( @{$entry->get_value('reqOld', asref => 1)} ) { 562 $rdn_old = (split(/: /, $_))[1] if /$rdn_re/; 563 } 564 } 565 ### now we reconstruct original object 566 $mesg = $self->o('ldap')->search( base => $self->cf->get(qw(ldap srch log_base)), 567 scope => 'sub', 568 sizelimit=> $self->cf->get(qw(ldap srch sizelimit)), 569 timelimit=> $self->cf->get(qw(ldap srch timelimit)), 570 filter => sprintf("(reqEntryUUID=%s)", 571 $entry->get_value('reqEntryUUID')) ); 572 if ( $mesg->code ) { 573 $self->l->cc( pr => 'err', nt => 1, 574 fm => "%s: LDAP accesslog search on %s, error:\n% 13s%s\n% 13s%s\n% 13s%s\n\n", 575 ls => [ __PACKAGE__, SYNST->[$st], 576 'base: ', $self->cf->get(qw(ldap srch log_base)), 577 'scope: ', 'sub', 578 'filter: ', sprintf("(reqEntryUUID=%s)", 579 $entry->get_value('reqEntryUUID') ) ] ); 580 $self->l->cc_ldap_err( mesg => $mesg ); 581 # exit $mesg->code; # !!! NEED TO DECIDE WHAT TO DO 582 } else { 583 @entries = $mesg->sorted; 584 if ( $entries[0]->get_value('reqType') eq 'add' ) { 585 ### here we re-assemble $obj to have all attributes on its creation except RDN, 586 ### which we'll set from next to the last element and since after that it'll has 587 ### ctrl_attr but reqType=add, it'll go to $st == LDAP_SYNC_DELETE 588 $obj->add( map { /^(.*):\+ (.*)$/g } @{$entry->get_value('reqMod', asref => 1)} ); 589 $obj->replace( $rdn => $entries[scalar(@entries) - 2]->get_value($rdn) ); 590 } else { 591 $self->l->cc( pr => 'err', nt => 1, 592 fm => "%s: %s object (before ModRDN) to delete not found! accesslog reqType=add object nod found\n\nobject reqEntryUUID=%s should be processed manually", 593 ls => [ __PACKAGE__, SYNST->[$st], $entry->get_value('reqEntryUUID') ] ); 594 } 595 } 596 } else { 597 $self->l->cc( pr => 'err', nt => 1, ls => [ __PACKAGE__, SYNST->[$st] ], 598 fm => "%s: LDAP accesslog search on %s object returned no result\n\n" ); 599 } 600 } 601 } 602 } 603 } 604 605 ### picking up a service, the $obj relates to 606 my $is_ctrl_attr; 607 my $ctrl_srv_re; 608 foreach ( @{$self->{_opt}{svc}} ) { 609 $is_ctrl_attr = 0; 610 foreach my $ctrl_attr ( @{$self->cf->get('service', $_, 'ctrl_attr')} ) { 611 if ( $obj->exists( $ctrl_attr ) ) { 612 $is_ctrl_attr++; 613 } else { 614 $is_ctrl_attr--; 615 } 616 } 617 $ctrl_srv_re = $self->cf->get('service', $_, 'ctrl_srv_re'); 618 if ( $is_ctrl_attr > 0 && $obj->dn =~ qr/$ctrl_srv_re/ && 619 $is_ctrl_attr == scalar( @{$self->cf->get('service', $_, 'ctrl_attr')} ) ) { 620 $s = $_; 621 } 622 } 623 624 if ( ! defined $s ) { 625 $self->l->cc( pr => 'warning', ls => [ __PACKAGE__, $obj->dn, SYNST->[$st] ], 626 fm => "%s: dn: %s is not configured to be processed on control: %s" ) 627 if $self->o('v') > 2; 628 return; 629 } 630 631 ####################################################################### 632 ####### --------------------------------------------------->>>>>>>>> 1 633 ####################################################################### 634 if ( $st == LDAP_SYNC_ADD || $st == LDAP_SYNC_MODIFY ) { 635 636 # App::Regather::Plugin->new( 'args', { log => $self->log, 637 # params => [ 1, 2, 3]} )->run; 638 foreach my $svc ( @{$self->cf->get('service', $s, 'plugin')} ) { 639 App::Regather::Plugin->new( $svc, { 640 cf => $self->cf, 641 force => $self->o('force'), 642 log => $self->l, 643 obj => $obj, 644 out_file_old => $out_file_old, 645 prog => sprintf("%s v.%s", $self->progname, $VERSION), 646 rdn => $rdn, 647 s => $s, 648 st => $st, 649 ts_fmt => $self->o('ts_fmt'), 650 v => $self->o('v'), 651 } )->ldap_sync_add_modify; 652 } 653 654 ####################################################################### 655 ####### --------------------------------------------------->>>>>>>>> 2 656 ####################################################################### 657 } elsif ( $st == LDAP_SYNC_DELETE ) { 658 659 foreach my $svc ( @{$self->cf->get('service', $s, 'plugin')} ) { 660 App::Regather::Plugin->new( $svc, { 661 cf => $self->cf, 662 force => $self->o('force'), 663 log => $self->l, 664 obj => $obj, 665 out_file_old => $out_file_old, 666 prog => sprintf("%s v.%s", $self->progname, $VERSION), 667 rdn => $rdn, 668 s => $s, 669 st => $st, 670 ts_fmt => $self->o('ts_fmt'), 671 v => $self->o('v'), 672 } )->ldap_sync_delete; 673 } 674 675 } 676 } elsif ( defined $syncstate && $syncstate->isa('Net::LDAP::Control::SyncDone') ) { 677 $self->l->cc( pr => 'debug', fm => "%s: Received SYNC DONE CONTROL" ) if $self->o('v') > 1; 678 } elsif ( ! defined $syncstate ) { 679 $self->l->cc( pr => 'warning', fm => "%s: LDAP entry without Sync State control" ) if $self->o('v') > 1; 680 } 681 682 $self->o('req')->cookie($syncstate->cookie) if $syncstate->cookie; 683 684 } elsif ( defined $obj && $obj->isa('Net::LDAP::Intermediate') ) { 685 $self->l->cc( pr => 'debug', fm => "%s: Received Net::LDAP::Intermediate\n%s", ls => [ __PACKAGE__, $obj ] ) 686 if $self->o('v') > 3; 687 $self->o('req')->cookie($obj->{'asn'}->{'refreshDelete'}->{'cookie'}); 688 } elsif ( defined $obj && $obj->isa('Net::LDAP::Reference') ) { 689 $self->l->cc( pr => 'debug', fm => "%s: Received Net::LDAP::Reference\n%s", ls => [ __PACKAGE__, $obj ] ) 690 if $self->o('v') > 3; 691 return; 692 } else { 693 return; 694 } 695} 696 6971; 698