ldapscanner 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. #!/usr/bin/env perl
  2. use strict;
  3. use warnings;
  4. use Config::Tiny ();
  5. use File::Slurper ();
  6. use Net::LDAP ();
  7. use JSON::XS ();
  8. die "No config file!" if !-f 'ldapscanner.cfg';
  9. my $cfg = Config::Tiny->read('ldapscanner.cfg');
  10. my @servers = split( ',', $cfg->{'server'}{'hosts'} );
  11. my $proto = 'ldap';
  12. my %extra_args = (
  13. 'onerror' => ( $cfg->{'prefs'}{'loglevel'} eq 'warn' ) ? 'warn' : 'die',
  14. 'debug' => $cfg->{'prefs'}{'loglevel'} eq 'debug',
  15. 'port' => $cfg->{'server'}{'port'},
  16. 'timeout' => $cfg->{'server'}{'timeout'},
  17. );
  18. if( $cfg->{'server'}{'ldaps'} ) {
  19. $proto = 'ldaps';
  20. $extra_args{'verify'} = 'no';
  21. }
  22. $extra_args{'scheme'} = $proto;
  23. _log( "info", "Connecting..." );
  24. my $conn = Net::LDAP->new( \@servers, %extra_args ) or die $@;
  25. my @bind_args;
  26. my $user = 'anonymous';
  27. if( $cfg->{'creds'}{'dn'} && $cfg->{'creds'}{'pass'} ) {
  28. push @bind_args, $cfg->{'creds'}{'dn'}, 'password', $cfg->{'creds'}{'pass'};
  29. $user = "DN: $cfg->{'creds'}{'dn'}";
  30. }
  31. _log( "info", "Binding as $user..." );
  32. my $result = $conn->bind(@bind_args);
  33. $result->code and die $result->error;
  34. _log( "info", "Beginning search..." );
  35. $result = $conn->search(
  36. base => $cfg->{'search'}{'base'},
  37. filter => $cfg->{'search'}{'filter'},
  38. );
  39. $result->code and die $result->error;
  40. my $old_entries = {};
  41. if( -f '.last.json' ) {
  42. _log( "info", "Stored run data found, will compare against previous run." );
  43. my $raw = File::Slurper::read_text('.last.json');
  44. $old_entries = JSON::XS->new->decode($raw);
  45. } else {
  46. _log( "info", "No run data found. Storing current data and exiting." );
  47. }
  48. my $new_entries = {};
  49. foreach my $entry ($result->entries) {
  50. my $hr = {};
  51. foreach my $attr ($entry->attributes( 'nooptions' => 1 )) {
  52. $hr->{$attr} = $entry->get_value( $attr, 'nooptions' => 1 );
  53. }
  54. $new_entries->{$entry->dn} = $hr;
  55. }
  56. _log( "info", "Found " . scalar(keys(%$new_entries)) . " records total" );
  57. my $diff = {
  58. 'added' => { map { $_ => $new_entries->{$_} } grep { !exists($old_entries->{$_}) } keys(%$new_entries) },
  59. 'removed' => { map { $_ => $old_entries->{$_} } grep { !exists($new_entries->{$_}) } keys(%$old_entries) },
  60. };
  61. File::Slurper::write_text( '.current_diff.json', JSON::XS->new->pretty->encode($diff) );
  62. File::Slurper::write_text( '.last.json', JSON::XS->new->pretty->encode($new_entries) );
  63. my @skip = split( ',', $cfg->{'display'}{'ignore_attrs'} );
  64. _log( "warn", "Added records : " . scalar(keys(%{$diff->{'added'}})) );
  65. _print_records( $diff->{'added'}, @skip );
  66. _log( "warn", "Removed records: " . scalar(keys(%{$diff->{'removed'}})) );
  67. _print_records( $diff->{'removed'}, @skip );
  68. sub _print_records {
  69. my ( $hr, @skip ) = @_;
  70. foreach my $key ( keys(%$hr) ) {
  71. _log( "warn", "------------------------------------------------" );
  72. _log( "warn", "DN: " . $key );
  73. foreach my $attr ( keys(%{$hr->{$key}}) ) {
  74. next if grep { $_ eq $attr } @skip;
  75. _log( "warn", " $attr: $hr->{$key}{$attr}" );
  76. }
  77. }
  78. return;
  79. }
  80. sub _log {
  81. my ( $level, $msg ) = @_;
  82. my %levels = (
  83. 'warn' => 1,
  84. 'error' => 2,
  85. 'info' => 3,
  86. 'debug' => 4,
  87. );
  88. my $loglevel = $levels{$cfg->{'prefs'}{'loglevel'}};
  89. die "Bad log level" if !$loglevel;
  90. print "$msg\n" if $levels{$level} <= $loglevel;
  91. return;
  92. }
  93. 0;