1#!/usr/bin/env perl
2#
3# Program: Generate LDAP Statistics Reports <ldap-stats.pl>
4#
5# Source code home: http://prefetch.net/code/ldap-stats.pl
6#
7# Author: Matty < matty91 @ gmail dot com >
8#
9# Current Version: 5.2
10#
11# Revision History:
12#
13#  Version 5.2
14#  Perl::Tidy and Perl::Critic -- Gavin Henry, Suretec Systems Ltd.
15#
16#  Version 5.1
17#  - Changed the location of the uc() statement -- Quanah Gibson-Mount
18#
19#  Version 5.0
20#  - Changed reporting structure to be dynamic -- Quanah Gibson-Mount
21#  - Fixed a bug with name resolution -- Quanah Gibson-Mount
22#  - Added the URL to the script -- Quanah Gibson-Mount
23#
24#  Version 4.2
25#  - Utilize strict mode --  Peter Schober
26#
27#  Version 4.1
28#  - Fixed a typo in the length() function -- Peter Schober
29#
30#  Version 4.0
31#  - Added "-d" option to print all days
32#  - Fixed day sort order
33#  - Added "-m" option to print all months
34#  - Fixed month sort order
35#  - Correct spelling. -- Dave Horsfall
36#  - Align headings. -- Dave Horsfall
37#  - Support ldapi:// connections ("LOCAL-SOCKET"). -- Dave Horsfall
38#  - Only do lookup if numeric IP. -- Dave Horsfall
39#
40#  Version 3.0 - 3.4
41#  - Added ability to resolve IP addresses to hostnames with "-n" option
42#  - Adjusted print() routines to limit lines to 80-characters -- Dave Horsfall
43#  - Clean up unnecessary (..) in regexes -- Peter Marschall
44#  - Split attributes found in searches (controlled by new option -s) -- Peter Marschall
45#  - Added report to print which filters are used
46#  - Added report to print explicit attributes requested -- Francis Swasey
47#  - Fix usage: correct line break, all lines < 80 chars -- Peter Marschall
48#  - Replace unnecessary printf() by print -- Peter Marschall
49#  - Concatenate arguments into one call to print instead of multiple calls -- Peter Marschall
50#  - Adapt underlining of some headers to length of logfile / date -- Peter Marschall
51#  - Added additional checks to address missing entries during logfile rotation
52#  - Fixed "uninitialized value in hash element" -- Todd Lyons
53#  - Added additional comments to code
54#  - Added report for operations by time of day
55#  - Added report for operations per day
56#  - Added report for operations per month
57#  - Removed debug statements to speedup logfile processing
58#  - Changed printf() format specifiers to match column definitions
59#
60#  Version 2.0 - 2.2
61#  - Adjusted the Search base comparison to catch ""
62#  - Translate "" to RootDSE in the search base results
63#  - Only print "Unindexed attribute" if unindexed attributes exist
64#  - Normalize the bind DN and search base to avoid duplicates
65#  - Fix typo with binddn array
66#  - Improved filter for anonymous and authenticated binds -- Peter Marschall
67#  - Logfiles are now passed as arguments to ldap-stats.pl
68#    (e.g, ldap-stats.pl openldap1 openldap2 openldap3 old* ) -- Peter Marschall
69#  - Cleaned up and combined filters for ADDs, MODs, DELs -- Peter Marschall
70#  - Added support for CMPs & MODRDNs -- Peter Marschall
71#  - Reduced number of regular expressions to one per filter -- Peter Marschall
72#  - Removed head and tail program requirements, as dates are read on the fly from the
73#    decoded logfile -- Peter Marschall
74#  - Support for gzip and bzip2 compressed files  -- Peter Marschall
75#  - Optimized some expressions -- Peter Marschall
76#  - Removed several Perl warnings, and added "-w" to default runtime options -- Peter Marschall
77#  - Support for regular expressions in logfile names (e.g., ldap-stats.pl /var/log/openldap* ) -- Peter Marschall
78#  - Changed default Perl interpreter to /usr/bin/perl
79#  - Changed to OpenLDAP license
80#
81#  Version 1.1 - 1.9
82#  - Updated the bind, binddn, search, search base, and unindexed search regexs to
83#    match a wider array of characters -- added by Peter Marschall
84#  - Shortened several regular expressions by replacing "[0-9]" with "\d" -- added by Peter Marschall
85#  - Fixed a divide by zero bug when logfiles contain 0 connections  -- added by  Dave Horsfall
86#  - Removed unnecessary file open(s)
87#  - Removed end of line ($) character from anonymous BIND regular expressions
88#  - Added "-l" option to print lines as they are processed from a logfile
89#  - Updated documentation
90#  - Updated formatting of search dn report
91#  - Updated formatting of search base report
92#  - Added an additional report with the number of binds per DN
93#  - Updated examples
94#  - Added additional debug messages to connection setup
95#  - Fixed documentation issues
96#  - Added debugging flag (-d) to give detailed information on logfile processing
97#  - Added "usage" subroutine to ease option maintenance
98#  - Fixed a bug in the BIND calculations -- found and fixed by Quanah Gibson-Mount
99#  - Fixed a bug in the MOD calculations -- found and fixed by Quanah Gibson-Mount
100#  - Fixed a bug in the SRCH calculations -- found and fixed by Quanah Gibson-Mount
101#  - Added a connection associative array to coorelate conn identifiers w/hosts -- Quanah Gibson-Mount
102#  - Updated the usage message with information on "-c" option
103#  - The "-f" option now accepts multiple logfiles
104#  - Changed the headers to include information on all logfiles processed
105#  - Added the day the report was run to the report headers
106#
107#  Version 1.0
108#   Original release
109#
110# Last Updated: 13-11-2006
111#
112# Purpose:
113#   Produces numerous reports from OpenLDAP 2.1, 2.2 and 2.3 logfiles.
114#
115# License:
116#
117#  Redistribution and use in source and binary forms, with or without
118#  modification, are permitted only as authorized by the OpenLDAP
119#  Public License.
120#
121#  A copy of this license is available in the file LICENSE in the
122#  top-level directory of the distribution or, alternatively, at
123#  <http://www.OpenLDAP.org/license.html>.
124#
125# Installation:
126#   1. Enable a minimum of 'loglevel 256' in the slapd.conf configuration file.
127#   2. Copy the shell script to a suitable location.
128#   3. Refer to the usage section for options and examples.
129#
130# Usage:
131#   Refer to the usage subroutine,
132#
133# Example:
134#   Refer to http://prefetch.net/code/ldap-stats.pl.txt to see sample output
135
136use strict;
137use warnings;
138use Getopt::Long;
139use Socket;
140use Carp;
141use 5.006;    # As returned by Perl::MinimumVersion
142
143#######################
144### usage subroutine
145### Parameters: None
146#######################
147sub usage {
148    print
149"Usage: ldap-stats.pl [ -s ] [ -c <count> ] [ -l <count> ] [ -h ] <logfile> ...\n"
150      . "   -c <count>             Number of lines to display for each report [25]\n"
151      . "   -d                     Display all available days in the day of month report\n"
152      . "   -h                     Display a usage help screen\n"
153      . "   -l <count>             Print status message after processing <count> lines [0]\n"
154      . "   -m                     Display all available months in the month of year report\n"
155      . "   -n                     Resolve IP addresses to hostnames\n"
156      . "   -o <ops> -o <ops> ...  Operations to print in the reports [ALL]\n"
157      . "                          Valid operations are: CONNECT, FAILURES, BIND, UNBIND,\n"
158      . "                          SRCH, CMP, ADD, MOD, MODRDN, DEL\n"
159      . "                          Predefined reports are: ALL, READ, WRITE\n"
160      . "   -s                     Split attributes found used in searches\n";
161    return;
162}
163
164### Declare lexical variables
165my ( $logfile, $i, $counter, $help );
166my ( %unindexed, %search, @operations );
167
168### Allow the number of entries displayed to be variable
169my $count = 25;
170
171### Figure out if we need to print "Processing X lines"
172my $increment = 0;
173
174## tell whether to split attributes in searches
175my $splitattrs = 0;
176
177# Tell whether to lookup names
178my $resolvename = 0;
179
180# Print all months
181my $printmonths = 0;
182
183# Print all days
184my $printdays = 0;
185
186###################################
187#### Get some options from the user
188###################################
189#getopts("o:l:c:nhsmd", \%options);
190
191GetOptions(
192    'count|c=i'      => \$count,
193    'days|d'         => \$printdays,
194    'help|h'         => \$help,
195    'length|l=i'     => \$increment,
196    'months|m'       => \$printmonths,
197    'network|n'      => \$resolvename,
198    'operations|o=s' => \@operations,
199    'split|s'        => \$splitattrs
200);
201
202### print a nice usage message
203if ($help) {
204    usage;
205    exit 1;
206}
207
208### Make sure there is at least one logfile
209if ( !@ARGV ) {
210    usage;
211    exit 1;
212}
213
214############################
215### Define various variables
216############################
217my $date = localtime time;
218
219if ( !@operations ) {
220    @operations = ('ALL');
221}
222
223my %stats = (
224    TOTAL_CONNECT      => 0,
225    TOTAL_BIND         => 0,
226    TOTAL_UNBIND       => 0,
227    TOTAL_SRCH         => 0,
228    TOTAL_DEL          => 0,
229    TOTAL_ADD          => 0,
230    TOTAL_CMP          => 0,
231    TOTAL_MOD          => 0,
232    TOTAL_MODRDN       => 0,
233    TOTAL_UNINDEXED    => 0,
234    TOTAL_AUTHFAILURES => 0,
235);
236
237my %hours;               # Hash to store the time of day (e.g., 21st of August)
238my %days;                # Hash to store the days of each month (e.g., 21st)
239my %months;              # Hash to store the day of the month (e.g., Dec)
240my %hosts;               # Hash to store client IP addresses
241my %conns;               # Hash to store connection identifiers
242my %binddns;             # Hash to store bind DNs
243my %logarray;            # Hash to store logfiles
244my %filters;             # Hash to store search filters
245my %searchattributes;    # Hash to store specific attributes that are requested
246my %operations;          # Hash to store operations information
247
248$operations{CONNECT} = {
249    DATA    => 0,
250    STRING  => '  Connect',
251    SPACING => ' --------',
252    FIELD   => '%8s',
253};
254
255$operations{FAILURES} = {
256    DATA    => 0,
257    STRING  => ' Failed',
258    SPACING => ' ------',
259    FIELD   => '%6s',
260};
261
262$operations{BIND} = {
263    DATA    => 0,
264    STRING  => '    Bind',
265    SPACING => ' -------',
266    FIELD   => '%7s',
267};
268
269$operations{UNBIND} = {
270    DATA    => 0,
271    STRING  => '  Unbind',
272    SPACING => ' -------',
273    FIELD   => '%7s',
274};
275
276$operations{SRCH} = {
277    DATA    => 0,
278    STRING  => '   Search',
279    SPACING => ' --------',
280    FIELD   => '%8s',
281};
282
283$operations{ADD} = {
284    DATA    => 0,
285    STRING  => '   Add',
286    SPACING => ' -----',
287    FIELD   => '%5s',
288};
289
290$operations{CMP} = {
291    DATA    => 0,
292    STRING  => '   Cmp',
293    SPACING => ' -----',
294    FIELD   => '%5s',
295};
296
297$operations{MOD} = {
298    DATA    => 0,
299    STRING  => '   Mod',
300    SPACING => ' -----',
301    FIELD   => '%5s',
302};
303
304$operations{MODRDN} = {
305    DATA    => 0,
306    STRING  => ' ModRDN',
307    SPACING => ' ------',
308    FIELD   => '%6s',
309};
310
311$operations{DEL} = {
312    DATA    => 0,
313    STRING  => '  Del',
314    SPACING => ' ----',
315    FIELD   => '%4s',
316};
317
318###################################################
319### Open the logfile and process all of the entries
320###################################################
321for my $file (@ARGV) {
322    $logfile = $file;
323    my $lines = 0;
324
325    ### find open filter to use
326    my $openfilter = '<' . $logfile . q{};
327
328    ### decode gzipped / bzip2-compressed files
329    if ( $logfile =~ /\.bz2$/mx ) {
330        $openfilter = q{bzip2 -dc "} . $logfile . q{"|}
331          or carp "Problem decompressing!: $!\n";
332    }
333
334    if ( $logfile =~ /\.(gz|Z)$/mx ) {
335        $openfilter = q{gzip -dc "} . $logfile . q{"|}
336          or carp "Problem decompressing!: $!\n";
337    }
338
339    ### If the logfile isn't valid, move on to the next one
340    if ( !open LOGFILE, $openfilter ) {
341        print "ERROR: unable to open '$logfile': $!\n";
342        next;
343    }
344
345    ### setup the arrray to hold the start/stop times
346    $logarray{$logfile} = {
347        SDATE => q{},
348        EDATE => q{},
349    };
350
351    ### Only print banner if requested
352    if ( $increment > 0 ) {
353        ### Print a banner and initialize the $counter variable
354        print "\nProcessing file \"$logfile\"\n"
355          . q{-} x ( 18 + length ${$logfile} ) . "\n";
356        $counter = 0;
357        $lines   = $increment;
358    }
359
360    while ( my $line = <LOGFILE> ) {
361
362        ### check start and end dates
363        if ( $line =~ /^(\w+\s+\d+\s+\d+:\d+:\d+)/mx ) {
364            if ( !$logarray{$logfile}{SDATE} ) {
365                $logarray{$logfile}{SDATE} = $1;
366            }
367            $logarray{$logfile}{EDATE} = $1;
368        }
369
370        ### Check to see if we have processed $lines lines
371        if ( ( $lines > 0 ) && ( $counter == $lines ) ) {
372            print "  Processed $lines lines in \"$logfile\"\n";
373            $lines += $increment;
374        }
375
376        ### Check for a new connection
377        if ( $line =~
378/^(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+).*conn=(\d+) [ ] fd=\d+ [ ] (?:ACCEPT|connection) [ ] from/mx
379          )
380        {
381            my $month = $1;
382            my $day   = $2;
383            my $hour  = $3;
384            my $conn  = $6;
385            my $host;
386
387            if ( $line =~ /IP=(\d+\.\d+\.\d+\.\d+):/mx ) {
388                $host = $1;
389            }
390            elsif ( $line =~ /PATH=(\S+)/mx ) {
391                $host = 'LOCAL-SOCKET';
392            }
393            else {
394                $host = 'UNKNOWN';
395            }
396
397            ### Create an array to store the list of hosts
398            if ( !( defined $hosts{$host} ) ) {
399                $hosts{$host} = {
400                    CONNECT      => 1,
401                    AUTHFAILURES => 0,
402                    BIND         => 0,
403                    UNBIND       => 0,
404                    SRCH         => 0,
405                    ADD          => 0,
406                    CMP          => 0,
407                    MOD          => 0,
408                    MODRDN       => 0,
409                    DEL          => 0,
410                };
411            }
412            else {
413                ### Entry exists, increment the CONNECT value
414                $hosts{$host}{CONNECT}++;
415            }
416
417            ### Create an array to store the hours
418            if ( !( defined $hours{$hour} ) ) {
419                $hours{$hour} = {
420                    CONNECT      => 1,
421                    AUTHFAILURES => 0,
422                    BIND         => 0,
423                    UNBIND       => 0,
424                    SRCH         => 0,
425                    ADD          => 0,
426                    CMP          => 0,
427                    MOD          => 0,
428                    MODRDN       => 0,
429                    DEL          => 0,
430                };
431            }
432            else {
433                ### Entry exists, increment the CONNECT value
434                $hours{$hour}{CONNECT}++;
435            }
436
437            ### Create an array to store the months
438            if ( !( defined $months{$month} ) ) {
439                $months{$month} = {
440                    CONNECT      => 1,
441                    AUTHFAILURES => 0,
442                    BIND         => 0,
443                    UNBIND       => 0,
444                    SRCH         => 0,
445                    ADD          => 0,
446                    CMP          => 0,
447                    MOD          => 0,
448                    MODRDN       => 0,
449                    DEL          => 0,
450                };
451            }
452            else {
453                ### Entry exists, increment the CONNECT value
454                $months{$month}{CONNECT}++;
455            }
456
457            ### Create an array to store the days
458            if ( !( defined $days{$day} ) ) {
459                $days{$day} = {
460                    CONNECT      => 1,
461                    AUTHFAILURES => 0,
462                    BIND         => 0,
463                    UNBIND       => 0,
464                    SRCH         => 0,
465                    ADD          => 0,
466                    CMP          => 0,
467                    MOD          => 0,
468                    MODRDN       => 0,
469                    DEL          => 0,
470                };
471            }
472            else {
473                ### Entry exists, increment the CONNECT value
474                $days{$day}{CONNECT}++;
475            }
476
477            ### Add the host to the connection table
478            $conns{$conn} = $host;
479
480            ### Increment the total number of connections
481            $stats{TOTAL_CONNECT}++;
482
483            ### Check for anonymous binds
484        }
485        elsif ( $line =~
486/^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+)  [ ] op=\d+ [ ] BIND [ ] dn="" [ ] method=128/mx
487          )
488        {
489            my $month = $1;
490            my $day   = $2;
491            my $hour  = $3;
492            my $conn  = $4;
493
494            ### Increment the counters
495            if (   defined $conns{$conn}
496                && defined $hosts{ $conns{$conn} } )
497            {
498                $hosts{ $conns{$conn} }{BIND}++;
499                $hours{$hour}{BIND}++;
500                $days{$day}{BIND}++;
501                $months{$month}{BIND}++;
502                $stats{TOTAL_BIND}++;
503            }
504
505            ### Add the binddn to the binddns array
506            $binddns{anonymous}++;
507
508            ### Check for non-anonymous binds
509        }
510        elsif ( $line =~
511/^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+) [ ] op=\d+ [ ] BIND [ ] dn="([^"]+)" [ ] mech=/mx
512          )
513        {
514            my $month  = $1;
515            my $day    = $2;
516            my $hour   = $3;
517            my $conn   = $4;
518            my $binddn = lc $5;
519
520            ### Increment the counters
521            if (   defined $conns{$conn}
522                && defined $hosts{ $conns{$conn} } )
523            {
524                $hosts{ $conns{$conn} }{BIND}++;
525                $hours{$hour}{BIND}++;
526                $days{$day}{BIND}++;
527                $months{$month}{BIND}++;
528                $stats{TOTAL_BIND}++;
529            }
530
531            ### Add the binddn to the binddns array
532            $binddns{$binddn}++;
533
534            ### Check the search base
535        }
536        elsif ( $line =~
537/\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] base="([^"]*?)" [ ] .*filter="([^"]*?)"/mx
538          )
539        {
540            my $base   = lc $1;
541            my $filter = $2;
542
543            ### Stuff the search base into an array
544            if ( defined $base ) {
545                $search{$base}++;
546            }
547
548            if ( defined $filter ) {
549                $filters{$filter}++;
550            }
551
552            ### Check for search attributes
553        }
554        elsif ( $line =~ /\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] attr=(.+)/mx ) {
555            my $attrs = lc $1;
556
557            if ($splitattrs) {
558                for my $attr ( split q{ }, $attrs ) {
559                    $searchattributes{$attr}++;
560                }
561            }
562            else {
563                $searchattributes{$attrs}++;
564            }
565
566            ### Check for SEARCHES
567        }
568        elsif ( $line =~
569/^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+) [ ] op=\d+ [ ] SEARCH [ ] RESULT/mx
570          )
571        {
572            my $month = $1;
573            my $day   = $2;
574            my $hour  = $3;
575            my $conn  = $4;
576
577            ### Increment the counters
578            if (   defined $conns{$conn}
579                && defined $hosts{ $conns{$conn} } )
580            {
581                $hosts{ $conns{$conn} }{SRCH}++;
582                $hours{$hour}{SRCH}++;
583                $days{$day}{SRCH}++;
584                $months{$month}{SRCH}++;
585                $stats{TOTAL_SRCH}++;
586            }
587
588            ### Check for unbinds
589        }
590        elsif ( $line =~
591            /^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+) [ ] op=\d+ [ ] UNBIND/mx
592          )
593        {
594            my $month = $1;
595            my $day   = $2;
596            my $hour  = $3;
597            my $conn  = $4;
598
599            ### Increment the counters
600            if (   defined $conns{$conn}
601                && defined $hosts{ $conns{$conn} } )
602            {
603                $hosts{ $conns{$conn} }{UNBIND}++;
604                $hours{$hour}{UNBIND}++;
605                $days{$day}{UNBIND}++;
606                $months{$month}{UNBIND}++;
607                $stats{TOTAL_UNBIND}++;
608            }
609
610            ### Check the result of the last operation
611            ### TODO: Add other err=X values from contrib/ldapc++/src/LDAPResult.h
612        }
613        elsif ( $line =~
614/^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+) [ ] op=\d+(?: SEARCH)? [ ] RESULT [ ]/mx
615          )
616        {
617            my $month = $1;
618            my $day   = $2;
619            my $hour  = $3;
620            my $conn  = $4;
621
622            if ( $line =~ /\berr=49\b/mx ) {
623                ### Increment the counters
624                if (   defined $conns{$conn}
625                    && defined $hosts{ $conns{$conn} } )
626                {
627                    $hosts{ $conns{$conn} }{AUTHFAILURES}++;
628                    $hours{$hour}{AUTHFAILURES}++;
629                    $days{$day}{AUTHFAILURES}++;
630                    $months{$month}{AUTHFAILURES}++;
631                    $stats{TOTAL_AUTHFAILURES}++;
632                }
633            }
634
635            ### Check for entry changes: add, modify modrdn, delete
636        }
637        elsif ( $line =~
638/^(\w+)\s+(\d+)\s+(\d+):\d+:\d+.*conn=(\d+) [ ] op=\d+ [ ] (ADD|CMP|MOD|MODRDN|DEL) [ ] dn=/mx
639          )
640        {
641            my $month = $1;
642            my $day   = $2;
643            my $hour  = $3;
644            my $conn  = $4;
645            my $type  = $5;
646
647            ### Increment the counters
648            if (   defined $conns{$conn}
649                && defined $hosts{ $conns{$conn} } )
650            {
651                $hosts{ $conns{$conn} }{$type}++;
652                $hours{$hour}{$type}++;
653                $days{$day}{$type}++;
654                $months{$month}{$type}++;
655                $stats{ 'TOTAL_' . $type }++;
656            }
657
658            ### Check for unindexed searches
659        }
660        elsif ( $line =~
661            /: [ ] \(([a-zA-Z0-9\;\-]+)\) [ ] index_param [ ] failed/mx )
662        {
663            my $attr = $1;
664
665            $unindexed{$attr}++;
666            $stats{TOTAL_UNINDEXED}++;
667        }
668        $counter++;
669    }
670    close LOGFILE;
671}
672
673###################################################################
674### Print a nice header with the logfiles and date ranges processed
675###################################################################
676## Please see file perltidy.ERR
677print "\n\n"
678  . "Report Generated on $date\n"
679  . q{-} x ( 20 + length $date ) . "\n";
680
681for my $logfile ( sort keys %logarray ) {
682    if ( !-z $logfile ) {
683        printf "Processed \"$logfile\":  %s - %s\n", $logarray{$logfile}{SDATE},
684          $logarray{$logfile}{EDATE};
685    }
686    else {
687        printf "Processed \"$logfile\":  no data\n";
688    }
689}
690
691#######################################
692### Print an overall report with totals
693#######################################
694
695my $total_operations =
696  $stats{TOTAL_BIND} + $stats{TOTAL_UNBIND} + $stats{TOTAL_SRCH} +
697  $stats{TOTAL_MOD} + $stats{TOTAL_ADD} + $stats{TOTAL_MODRDN} +
698  $stats{TOTAL_DEL};
699
700print "\n\n" . "Operation totals\n" . "----------------\n";
701printf "Total operations              : %d\n", $total_operations;
702printf "Total connections             : %d\n", $stats{TOTAL_CONNECT};
703printf "Total authentication failures : %d\n", $stats{TOTAL_AUTHFAILURES};
704printf "Total binds                   : %d\n", $stats{TOTAL_BIND};
705printf "Total unbinds                 : %d\n", $stats{TOTAL_UNBIND};
706printf "Total searches                : %d\n", $stats{TOTAL_SRCH};
707printf "Total compares                : %d\n", $stats{TOTAL_CMP};
708printf "Total modifications           : %d\n", $stats{TOTAL_MOD};
709printf "Total modrdns                 : %d\n", $stats{TOTAL_MODRDN};
710printf "Total additions               : %d\n", $stats{TOTAL_ADD};
711printf "Total deletions               : %d\n", $stats{TOTAL_DEL};
712printf "Unindexed attribute requests  : %d\n", $stats{TOTAL_UNINDEXED};
713printf "Operations per connection     : %.2f\n",
714  $stats{TOTAL_CONNECT} ? $total_operations / $stats{TOTAL_CONNECT} : 0;
715
716###################################################
717### Process the host information and print a report
718###################################################
719for my $selected (@operations) {
720    $selected = uc $selected;
721
722    my $ops_ref = {
723        CONNECT  => sub { $operations{CONNECT}{DATA}  = 1 },
724        FAILURES => sub { $operations{FAILURES}{DATA} = 1 },
725        BIND     => sub { $operations{BIND}{DATA}     = 1 },
726        UNBIND   => sub { $operations{UNBIND}{DATA}   = 1 },
727        SRCH     => sub { $operations{SRCH}{DATA}     = 1 },
728        CMP      => sub { $operations{CMP}{DATA}      = 1 },
729        ADD      => sub { $operations{ADD}{DATA}      = 1 },
730        MOD      => sub { $operations{MOD}{DATA}      = 1 },
731        MODRDN   => sub { $operations{MODRDN}{DATA}   = 1 },
732        DEL      => sub { $operations{DEL}{DATA}      = 1 },
733        ALL => sub {
734            $operations{CONNECT}{DATA}  = 1;
735            $operations{FAILURES}{DATA} = 1;
736            $operations{BIND}{DATA}     = 1;
737            $operations{UNBIND}{DATA}   = 1;
738            $operations{SRCH}{DATA}     = 1;
739            $operations{CMP}{DATA}      = 1;
740            $operations{ADD}{DATA}      = 1;
741            $operations{MOD}{DATA}      = 1;
742            $operations{MODRDN}{DATA}   = 1;
743            $operations{DEL}{DATA}      = 1;
744        },
745        READ => sub {
746            $operations{CONNECT}{DATA} = 1;
747            $operations{BIND}{DATA}    = 1;
748            $operations{UNBIND}{DATA}  = 1;
749            $operations{SRCH}{DATA}    = 1;
750            $operations{CMP}{DATA}     = 1;
751        },
752        WRITE => sub {
753            $operations{CONNECT}{DATA} = 1;
754            $operations{BIND}{DATA}    = 1;
755            $operations{UNBIND}{DATA}  = 1;
756            $operations{ADD}{DATA}     = 1;
757            $operations{MOD}{DATA}     = 1;
758            $operations{MODRDN}{DATA}  = 1;
759            $operations{DEL}{DATA}     = 1;
760        },
761    };
762    if   ( $ops_ref->{$selected} ) { $ops_ref->{$selected}->() }
763    else                           { croak "Unknown operation: '$selected';\n" }
764}
765
766print "\n\n";
767my $printstr = 'Hostname       ';
768$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{STRING}  : q{};
769$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
770$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{STRING}     : q{};
771$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{STRING}   : q{};
772$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{STRING}     : q{};
773$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{STRING}      : q{};
774$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{STRING}      : q{};
775$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{STRING}      : q{};
776$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{STRING}   : q{};
777$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{STRING}      : q{};
778$printstr .= "\n";
779print $printstr;
780$printstr = '---------------';
781$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{SPACING}  : q{};
782$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
783$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{SPACING}     : q{};
784$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{SPACING}   : q{};
785$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{SPACING}     : q{};
786$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{SPACING}      : q{};
787$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{SPACING}      : q{};
788$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{SPACING}      : q{};
789$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{SPACING}   : q{};
790$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{SPACING}      : q{};
791print "$printstr\n";
792
793for my $index ( sort keys %hosts ) {
794
795    ### Resolve IP addresses to names if requested
796    my $host = $index;
797
798    ### Convert the IP address to an Internet address, and resolve with gethostbyaddr()
799    if ( $resolvename && ( $index =~ /\d+\.\d+\.\d+\.\d+/mx ) ) {
800        my $ipaddr = inet_aton($index);
801        $host = gethostbyaddr $ipaddr, AF_INET;
802        if ( !defined $host ) {
803            $host = $index;
804        }
805    }
806    printf '%-15.15s', $host;
807    if ( $operations{CONNECT}{DATA} ) {
808        printf " $operations{CONNECT}{FIELD}",
809          $hosts{$index}{CONNECT} ? $hosts{$index}{CONNECT} : 0;
810    }
811    if ( $operations{FAILURES}{DATA} ) {
812        printf " $operations{FAILURES}{FIELD}",
813          $hosts{$index}{AUTHFAILURES} ? $hosts{$index}{AUTHFAILURES} : 0;
814    }
815    if ( $operations{BIND}{DATA} ) {
816        printf " $operations{BIND}{FIELD}",
817          $hosts{$index}{BIND} ? $hosts{$index}{BIND} : 0;
818    }
819    if ( $operations{UNBIND}{DATA} ) {
820        printf " $operations{UNBIND}{FIELD}",
821          $hosts{$index}{UNBIND} ? $hosts{$index}{UNBIND} : 0;
822    }
823    if ( $operations{SRCH}{DATA} ) {
824        printf " $operations{SRCH}{FIELD}",
825          $hosts{$index}{SRCH} ? $hosts{$index}{SRCH} : 0;
826    }
827    if ( $operations{CMP}{DATA} ) {
828        printf " $operations{CMP}{FIELD}",
829          $hosts{$index}{CMP} ? $hosts{$index}{CMP} : 0;
830    }
831    if ( $operations{ADD}{DATA} ) {
832        printf " $operations{ADD}{FIELD}",
833          $hosts{$index}{ADD} ? $hosts{$index}{ADD} : 0;
834    }
835    if ( $operations{MOD}{DATA} ) {
836        printf " $operations{MOD}{FIELD}",
837          $hosts{$index}{MOD} ? $hosts{$index}{MOD} : 0;
838    }
839    if ( $operations{MODRDN}{DATA} ) {
840        printf " $operations{MODRDN}{FIELD}",
841          $hosts{$index}{MODRDN} ? $hosts{$index}{MODRDN} : 0;
842    }
843    if ( $operations{DEL}{DATA} ) {
844        printf " $operations{DEL}{FIELD}",
845          $hosts{$index}{DEL} ? $hosts{$index}{DEL} : 0;
846    }
847    print "\n";
848}
849
850#######################################################
851### Process the hours information and print a report
852########################################################
853print "\n\n";
854$printstr = 'Hour of Day  ';
855$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{STRING}  : q{};
856$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
857$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{STRING}     : q{};
858$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{STRING}   : q{};
859$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{STRING}     : q{};
860$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{STRING}      : q{};
861$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{STRING}      : q{};
862$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{STRING}      : q{};
863$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{STRING}   : q{};
864$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{STRING}      : q{};
865$printstr .= "\n";
866print $printstr;
867$printstr = '-------------';
868$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{SPACING}  : q{};
869$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
870$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{SPACING}     : q{};
871$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{SPACING}   : q{};
872$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{SPACING}     : q{};
873$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{SPACING}      : q{};
874$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{SPACING}      : q{};
875$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{SPACING}      : q{};
876$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{SPACING}   : q{};
877$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{SPACING}      : q{};
878print "$printstr\n";
879
880for my $index ( sort keys %hours ) {
881    printf '%-2s:00 - %2s:59', $index, $index;
882    if ( $operations{CONNECT}{DATA} ) {
883        printf " $operations{CONNECT}{FIELD}",
884          $hours{$index}{CONNECT} ? $hours{$index}{CONNECT} : 0;
885    }
886    if ( $operations{FAILURES}{DATA} ) {
887        printf " $operations{FAILURES}{FIELD}",
888          $hours{$index}{AUTHFAILURES} ? $hours{$index}{AUTHFAILURES} : 0;
889    }
890    if ( $operations{BIND}{DATA} ) {
891        printf " $operations{BIND}{FIELD}",
892          $hours{$index}{BIND} ? $hours{$index}{BIND} : 0;
893    }
894    if ( $operations{UNBIND}{DATA} ) {
895        printf " $operations{UNBIND}{FIELD}",
896          $hours{$index}{UNBIND} ? $hours{$index}{UNBIND} : 0;
897    }
898    if ( $operations{SRCH}{DATA} ) {
899        printf " $operations{SRCH}{FIELD}",
900          $hours{$index}{SRCH} ? $hours{$index}{SRCH} : 0;
901    }
902    if ( $operations{CMP}{DATA} ) {
903        printf " $operations{CMP}{FIELD}",
904          $hours{$index}{CMP} ? $hours{$index}{CMP} : 0;
905    }
906    if ( $operations{ADD}{DATA} ) {
907        printf " $operations{ADD}{FIELD}",
908          $hours{$index}{ADD} ? $hours{$index}{ADD} : 0;
909    }
910    if ( $operations{MOD}{DATA} ) {
911        printf " $operations{MOD}{FIELD}",
912          $hours{$index}{MOD} ? $hours{$index}{MOD} : 0;
913    }
914    if ( $operations{MODRDN}{DATA} ) {
915        printf " $operations{MODRDN}{FIELD}",
916          $hours{$index}{MODRDN} ? $hours{$index}{MODRDN} : 0;
917    }
918    if ( $operations{DEL}{DATA} ) {
919        printf " $operations{DEL}{FIELD}",
920          $hours{$index}{DEL} ? $hours{$index}{DEL} : 0;
921    }
922    print "\n";
923}
924
925#######################################################
926### Process the month information and print a report
927########################################################
928print "\n\n";
929$printstr = 'Day of Month ';
930$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{STRING}  : q{};
931$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
932$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{STRING}     : q{};
933$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{STRING}   : q{};
934$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{STRING}     : q{};
935$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{STRING}      : q{};
936$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{STRING}      : q{};
937$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{STRING}      : q{};
938$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{STRING}   : q{};
939$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{STRING}      : q{};
940$printstr .= "\n";
941print $printstr;
942$printstr = '-------------';
943$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{SPACING}  : q{};
944$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
945$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{SPACING}     : q{};
946$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{SPACING}   : q{};
947$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{SPACING}     : q{};
948$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{SPACING}      : q{};
949$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{SPACING}      : q{};
950$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{SPACING}      : q{};
951$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{SPACING}   : q{};
952$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{SPACING}      : q{};
953print "$printstr\n";
954
955for ( 1 .. 31 ) {
956    if ( defined $days{$_} || $printdays ) {
957        printf '  %-11s', $_;
958        if ( $operations{CONNECT}{DATA} ) {
959            printf " $operations{CONNECT}{FIELD}",
960              $days{$_}{CONNECT} ? $days{$_}{CONNECT} : 0;
961        }
962        if ( $operations{FAILURES}{DATA} ) {
963            printf " $operations{FAILURES}{FIELD}",
964              $days{$_}{AUTHFAILURES} ? $days{$_}{AUTHFAILURES} : 0;
965        }
966        if ( $operations{BIND}{DATA} ) {
967            printf " $operations{BIND}{FIELD}",
968              $days{$_}{BIND} ? $days{$_}{BIND} : 0;
969        }
970        if ( $operations{UNBIND}{DATA} ) {
971            printf " $operations{UNBIND}{FIELD}",
972              $days{$_}{UNBIND} ? $days{$_}{UNBIND} : 0;
973        }
974        if ( $operations{SRCH}{DATA} ) {
975            printf " $operations{SRCH}{FIELD}",
976              $days{$_}{SRCH} ? $days{$_}{SRCH} : 0;
977        }
978        if ( $operations{CMP}{DATA} ) {
979            printf " $operations{CMP}{FIELD}",
980              $days{$_}{CMP} ? $days{$_}{CMP} : 0;
981        }
982        if ( $operations{ADD}{DATA} ) {
983            printf " $operations{ADD}{FIELD}",
984              $days{$_}{ADD} ? $days{$_}{ADD} : 0;
985        }
986        if ( $operations{MOD}{DATA} ) {
987            printf " $operations{MOD}{FIELD}",
988              $days{$_}{MOD} ? $days{$_}{MOD} : 0;
989        }
990        if ( $operations{MODRDN}{DATA} ) {
991            printf " $operations{MODRDN}{FIELD}",
992              $days{$_}{MODRDN} ? $days{$_}{MODRDN} : 0;
993        }
994        if ( $operations{DEL}{DATA} ) {
995            printf " $operations{DEL}{FIELD}",
996              $days{$_}{DEL} ? $days{$_}{DEL} : 0;
997        }
998        print "\n";
999    }
1000}
1001#######################################################
1002### Process the month information and print a report
1003########################################################
1004print "\n\n";
1005$printstr = ' Month       ';
1006$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{STRING}  : q{};
1007$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
1008$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{STRING}     : q{};
1009$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{STRING}   : q{};
1010$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{STRING}     : q{};
1011$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{STRING}      : q{};
1012$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{STRING}      : q{};
1013$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{STRING}      : q{};
1014$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{STRING}   : q{};
1015$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{STRING}      : q{};
1016$printstr .= "\n";
1017print $printstr;
1018$printstr = '-------------';
1019$printstr .= $operations{CONNECT}{DATA}  ? $operations{CONNECT}{SPACING}  : q{};
1020$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
1021$printstr .= $operations{BIND}{DATA}     ? $operations{BIND}{SPACING}     : q{};
1022$printstr .= $operations{UNBIND}{DATA}   ? $operations{UNBIND}{SPACING}   : q{};
1023$printstr .= $operations{SRCH}{DATA}     ? $operations{SRCH}{SPACING}     : q{};
1024$printstr .= $operations{CMP}{DATA}      ? $operations{CMP}{SPACING}      : q{};
1025$printstr .= $operations{ADD}{DATA}      ? $operations{ADD}{SPACING}      : q{};
1026$printstr .= $operations{MOD}{DATA}      ? $operations{MOD}{SPACING}      : q{};
1027$printstr .= $operations{MODRDN}{DATA}   ? $operations{MODRDN}{SPACING}   : q{};
1028$printstr .= $operations{DEL}{DATA}      ? $operations{DEL}{SPACING}      : q{};
1029print "$printstr\n";
1030
1031for my $index qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) {
1032    if ( defined $months{$index} || $printmonths ) {
1033        printf '  %-11s', $index;
1034        if ( $operations{CONNECT}{DATA} ) {
1035            printf " $operations{CONNECT}{FIELD}",
1036              $months{$index}{CONNECT} ? $months{$index}{CONNECT} : 0;
1037        }
1038        if ( $operations{FAILURES}{DATA} ) {
1039            printf " $operations{FAILURES}{FIELD}",
1040              $months{$index}{AUTHFAILURES}
1041              ? $months{$index}{AUTHFAILURES}
1042              : 0;
1043        }
1044        if ( $operations{BIND}{DATA} ) {
1045            printf " $operations{BIND}{FIELD}",
1046              $months{$index}{BIND} ? $months{$index}{BIND} : 0;
1047        }
1048        if ( $operations{UNBIND}{DATA} ) {
1049            printf " $operations{UNBIND}{FIELD}",
1050              $months{$index}{UNBIND} ? $months{$index}{UNBIND} : 0;
1051        }
1052        if ( $operations{SRCH}{DATA} ) {
1053            printf " $operations{SRCH}{FIELD}",
1054              $months{$index}{SRCH} ? $months{$index}{SRCH} : 0;
1055        }
1056        if ( $operations{CMP}{DATA} ) {
1057            printf " $operations{CMP}{FIELD}",
1058              $months{$index}{CMP} ? $months{$index}{CMP} : 0;
1059        }
1060        if ( $operations{ADD}{DATA} ) {
1061            printf " $operations{ADD}{FIELD}",
1062              $months{$index}{ADD} ? $months{$index}{ADD} : 0;
1063        }
1064        if ( $operations{MOD}{DATA} ) {
1065            printf " $operations{MOD}{FIELD}",
1066              $months{$index}{MOD} ? $months{$index}{MOD} : 0;
1067        }
1068        if ( $operations{MODRDN}{DATA} ) {
1069            printf " $operations{MODRDN}{FIELD}",
1070              $months{$index}{MODRDN} ? $months{$index}{MODRDN} : 0;
1071        }
1072        if ( $operations{DEL}{DATA} ) {
1073            printf " $operations{DEL}{FIELD}",
1074              $months{$index}{DEL} ? $months{$index}{DEL} : 0;
1075        }
1076        print "\n";
1077    }
1078}
1079
1080####################################################
1081### Process the unindexed searches and print a report
1082####################################################
1083my @sarray;    # sort array
1084if ( $stats{TOTAL_UNINDEXED} > 0 ) {
1085
1086    print "\n\n"
1087      . "# Uses        Unindexed attribute\n"
1088      . "----------    -----------------------------------------------------------\n";
1089
1090    @sarray =
1091      reverse sort { $unindexed{$a} <=> $unindexed{$b} } keys %unindexed;
1092  UNINDEXED:
1093    for my $num ( 0 .. $#sarray ) {
1094        if ( $num > $count ) {
1095            last UNINDEXED;
1096        }
1097        printf "  %-8d    %-60s\n", $unindexed{ $sarray[$num] }, $sarray[$num];
1098    }
1099}
1100
1101######################################################
1102### Process the stored search bases and print a report
1103######################################################
1104print "\n\n"
1105  . "# Searches    Search base\n"
1106  . "----------    -----------------------------------------------------------\n";
1107
1108@sarray = reverse sort { $search{$a} <=> $search{$b} } keys %search;
1109SEARCH:
1110for my $num ( 0 .. $#sarray ) {
1111    if ( $num > $count ) {
1112        last SEARCH;
1113    }
1114    printf "  %-8d    %-60s\n", $search{ $sarray[$num] },
1115      $sarray[$num] || 'RootDSE';
1116}
1117
1118######################################################
1119### Process the stored search filters
1120######################################################
1121print "\n\n"
1122  . "# Uses        Filter\n"
1123  . "----------    -----------------------------------------------------------\n";
1124
1125@sarray = reverse sort { $filters{$a} <=> $filters{$b} } keys %filters;
1126FILTER:
1127for my $num ( 0 .. $#sarray ) {
1128    if ( $num > $count ) {
1129        last FILTER;
1130    }
1131    printf "  %-8d    %-60s\n", $filters{ $sarray[$num] }, $sarray[$num];
1132}
1133
1134######################################################
1135### Process the stored attribute array
1136######################################################
1137print "\n\n"
1138  . "# Uses        Attributes explicitly requested in search string\n"
1139  . "----------    -------------------------------------------------\n";
1140
1141@sarray =
1142  reverse sort { $searchattributes{$a} <=> $searchattributes{$b} }
1143  keys %searchattributes;
1144SEARCHATTR:
1145for my $num ( 0 .. $#sarray ) {
1146    if ( $num > $count ) {
1147        last SEARCHATTR;
1148    }
1149    printf "  %-8d    %-60s\n", $searchattributes{ $sarray[$num] },
1150      $sarray[$num];
1151}
1152
1153######################################################
1154### Process the stored binddns and print a report
1155######################################################
1156print "\n\n"
1157  . "# Binds       Bind DN\n"
1158  . "----------    --------------------------------------------------------------\n";
1159
1160@sarray = reverse sort { $binddns{$a} <=> $binddns{$b} } keys %binddns;
1161BINDDN:
1162for my $num ( 0 .. $#sarray ) {
1163    if ( $num > $count ) {
1164        last BINDDN;
1165    }
1166    printf "  %-8d    %-60s\n", $binddns{ $sarray[$num] }, $sarray[$num];
1167}
1168
1169print "\n\n";
1170
1171# EOF
1172