From a188f3c0daad17b078803b2cfde17e6353d1df0d Mon Sep 17 00:00:00 2001
From: William Brown <firstyear@redhat.com>
Date: Mon, 16 May 2016 08:31:24 +1000
Subject: [PATCH] Ticket 48336 - setup-ds should detect if port is already
 defined

Bug Description:  Previously setup-ds.pl could not detect if a port was defined
in selinux policy or not.

Fix Description:  This adds a set of selinux helpers that can query selinux port
in policy. Using these, setup-ds.pl can now make better decisions about whether
to label ports, or if they should be removed from policy during removal.

https://fedorahosted.org/389/ticket/48336

Author: wibrown

Review by: ???
---
 Makefile.am                                     |   2 +
 Makefile.in                                     |  18 +++-
 configure                                       |  40 +++++---
 ldap/admin/src/scripts/DSCreate.pm.in           | 121 ++++++++++++++----------
 ldap/admin/src/scripts/ds_selinux_enabled.in    |  14 +++
 ldap/admin/src/scripts/ds_selinux_port_query.in |  60 ++++++++++++
 rpm/389-ds-base.spec.in                         |   4 +-
 7 files changed, 187 insertions(+), 72 deletions(-)
 create mode 100755 ldap/admin/src/scripts/ds_selinux_enabled.in
 create mode 100644 ldap/admin/src/scripts/ds_selinux_port_query.in

diff --git a/Makefile.am b/Makefile.am
index e715fba..90b8d78 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -617,6 +617,8 @@ sbin_SCRIPTS = ldap/admin/src/scripts/setup-ds.pl \
 	ldap/admin/src/scripts/dbverify \
 	ldap/admin/src/scripts/upgradedb \
 	ldap/admin/src/scripts/dbmon.sh \
+	ldap/admin/src/scripts/ds_selinux_enabled \
+	ldap/admin/src/scripts/ds_selinux_port_query \
     wrappers/ldap-agent
 
 bin_SCRIPTS = ldap/servers/slapd/tools/rsearch/scripts/dbgen.pl \
diff --git a/Makefile.in b/Makefile.in
index 0752ff1..3832880 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1523,6 +1523,8 @@ DS_DEFINES = -DBUILD_NUM=$(BUILDNUM) -DVENDOR="\"$(vendor)\"" -DBRAND="\"$(brand
 @enable_nunc_stans_FALSE@NUNC_STANS_ON = 0
 @enable_nunc_stans_TRUE@NUNC_STANS_ON = 1
 DS_INCLUDES = -I$(srcdir)/ldap/include -I$(srcdir)/ldap/servers/slapd -I$(srcdir)/include -I. $(NUNC_STANS_INCLUDES)
+@enable_asan_FALSE@ASAN_ON = 0
+@enable_asan_TRUE@ASAN_ON = 1
 
 # these paths are dependent on the settings of prefix and exec_prefix which may be specified
 # at make time.  So we cannot use AC_DEFINE in the configure.ac because that would set the
@@ -2027,6 +2029,8 @@ sbin_SCRIPTS = ldap/admin/src/scripts/setup-ds.pl \
 	ldap/admin/src/scripts/dbverify \
 	ldap/admin/src/scripts/upgradedb \
 	ldap/admin/src/scripts/dbmon.sh \
+	ldap/admin/src/scripts/ds_selinux_enabled \
+	ldap/admin/src/scripts/ds_selinux_port_query \
     wrappers/ldap-agent
 
 bin_SCRIPTS = ldap/servers/slapd/tools/rsearch/scripts/dbgen.pl \
@@ -3108,6 +3112,7 @@ rsearch_bin_LDADD = $(NSPR_LINK) $(NSS_LINK) $(LDAPSDK_LINK) $(SASL_LINK) $(LIBS
 @BUNDLE_FALSE@	-e 's,@enable_autobind\@,$(enable_autobind),g' \
 @BUNDLE_FALSE@	-e 's,@enable_auto_dn_suffix\@,$(enable_auto_dn_suffix),g' \
 @BUNDLE_FALSE@	-e 's,@enable_presence\@,$(enable_presence),g' \
+@BUNDLE_FALSE@	-e 's,@enable_asan\@,$(ASAN_ON),g' \
 @BUNDLE_FALSE@	-e 's,@ECHO_N\@,$(ECHO_N),g' \
 @BUNDLE_FALSE@	-e 's,@ECHO_C\@,$(ECHO_C),g' \
 @BUNDLE_FALSE@	-e 's,@brand\@,$(brand),g' \
@@ -3185,6 +3190,7 @@ rsearch_bin_LDADD = $(NSPR_LINK) $(NSS_LINK) $(LDAPSDK_LINK) $(SASL_LINK) $(LIBS
 @BUNDLE_TRUE@	-e 's,@enable_autobind\@,$(enable_autobind),g' \
 @BUNDLE_TRUE@	-e 's,@enable_auto_dn_suffix\@,$(enable_auto_dn_suffix),g' \
 @BUNDLE_TRUE@	-e 's,@enable_presence\@,$(enable_presence),g' \
+@BUNDLE_TRUE@	-e 's,@enable_asan\@,$(ASAN_ON),g' \
 @BUNDLE_TRUE@	-e 's,@ECHO_N\@,$(ECHO_N),g' \
 @BUNDLE_TRUE@	-e 's,@ECHO_C\@,$(ECHO_C),g' \
 @BUNDLE_TRUE@	-e 's,@brand\@,$(brand),g' \
@@ -10774,9 +10780,13 @@ ns-slapd.properties: makstrdb
 	$(fixupcmd) $^ > $@
 
 # yes, that is an @ in the filename . . .
-%/$(PACKAGE_NAME)@.service: %/systemd.template.service.in
-	if [ ! -d $(dir $@) ] ; then mkdir -p $(dir $@) ; fi
-	$(fixupcmd) $^ > $@
+@enable_asan_TRUE@%/$(PACKAGE_NAME)@.service: %/systemd.template.asan.service.in
+@enable_asan_TRUE@	if [ ! -d $(dir $@) ] ; then mkdir -p $(dir $@) ; fi
+@enable_asan_TRUE@	$(fixupcmd) $^ > $@
+# yes, that is an @ in the filename . . .
+@enable_asan_FALSE@%/$(PACKAGE_NAME)@.service: %/systemd.template.service.in
+@enable_asan_FALSE@	if [ ! -d $(dir $@) ] ; then mkdir -p $(dir $@) ; fi
+@enable_asan_FALSE@	$(fixupcmd) $^ > $@
 
 %/$(PACKAGE_NAME).systemd: %/systemd.template.sysconfig
 	if [ ! -d $(dir $@) ] ; then mkdir -p $(dir $@) ; fi
@@ -10821,7 +10831,7 @@ rpmbrprep: dist-bzip2 rpmroot
 	cp $(distdir).tar.bz2 $(RPMBUILD)/SOURCES
 	cp $(srcdir)/rpm/389-ds-base-git.sh $(RPMBUILD)/SOURCES
 	cp $(srcdir)/rpm/389-ds-base-devel.README $(RPMBUILD)/SOURCES
-	sed -e "s/__VERSION__/$(RPM_VERSION)/" -e "s/__RELEASE__/$(RPM_RELEASE)/" -e "s/__NUNC_STANS_ON__/$(NUNC_STANS_ON)/" < $(abs_builddir)/rpm/389-ds-base.spec > $(RPMBUILD)/SPECS/389-ds-base.spec
+	sed -e "s/__VERSION__/$(RPM_VERSION)/" -e "s/__RELEASE__/$(RPM_RELEASE)/" -e "s/__NUNC_STANS_ON__/$(NUNC_STANS_ON)/" -e "s/__ASAN_ON__/$(ASAN_ON)/"  < $(abs_builddir)/rpm/389-ds-base.spec > $(RPMBUILD)/SPECS/389-ds-base.spec
 
 # Requires rpmdevtools. Consider making this a dependancy of rpms.
 rpmsources: rpmbrprep
diff --git a/configure b/configure
index 7dc8c3e..bdf133d 100755
--- a/configure
+++ b/configure
@@ -771,6 +771,8 @@ BUNDLE_TRUE
 gccsec_defs
 RPM_HARDEND_CC_FALSE
 RPM_HARDEND_CC_TRUE
+enable_asan_FALSE
+enable_asan_TRUE
 asan_defs
 debug_defs
 LIBOBJS
@@ -17694,7 +17696,7 @@ if test "${enable_debug+set}" = set; then :
   enableval=$enable_debug;
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-  debug_defs="-g3 -DDEBUG -DMCC_DEBUG"
+  debug_defs="-g3 -DDEBUG -DMCC_DEBUG -O0"
 
 else
 
@@ -17724,6 +17726,14 @@ $as_echo "no" >&6; }
 fi
 
 
+ if test "$enable_asan" = "yes"; then
+  enable_asan_TRUE=
+  enable_asan_FALSE='#'
+else
+  enable_asan_TRUE='#'
+  enable_asan_FALSE=
+fi
+
 
  if test -f /usr/lib/rpm/redhat/redhat-hardened-cc1; then
   RPM_HARDEND_CC_TRUE=
@@ -21333,11 +21343,10 @@ $as_echo "$with_systemdsystemconfdir" >&6; }
     fi
 
 
-    if test -n "$with_systemdsystemunitdir" -o -n "$with_systemdsystemconfdir" ; then
-       if test -z "$with_systemdgroupname" ; then
-          with_systemdgroupname=$PACKAGE_NAME.target
-       fi
-       { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-systemdgroupname" >&5
+    if test -z "$with_systemdgroupname" ; then
+       with_systemdgroupname=$PACKAGE_NAME.target
+    fi
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-systemdgroupname" >&5
 $as_echo_n "checking for --with-systemdgroupname... " >&6; }
 
 # Check whether --with-systemdgroupname was given.
@@ -21345,18 +21354,17 @@ if test "${with_systemdgroupname+set}" = set; then :
   withval=$with_systemdgroupname;
 fi
 
-       if test "$with_systemdgroupname" = yes ; then
-          as_fn_error $? "You must specify --with-systemdgroupname=name.of.group" "$LINENO" 5
-       elif test "$with_systemdgroupname" = no ; then
-          as_fn_error $? "You must specify --with-systemdgroupname=name.of.group" "$LINENO" 5
-       else
-          { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_systemdgroupname" >&5
+    if test "$with_systemdgroupname" = yes ; then
+       as_fn_error $? "You must specify --with-systemdgroupname=name.of.group" "$LINENO" 5
+    elif test "$with_systemdgroupname" = no ; then
+       as_fn_error $? "You must specify --with-systemdgroupname=name.of.group" "$LINENO" 5
+    else
+       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_systemdgroupname" >&5
 $as_echo "$with_systemdgroupname" >&6; }
-       fi
-
     fi
 
 
+
 fi
 # End of with_systemd
 
@@ -21653,6 +21661,10 @@ if test -z "${am__fastdepCCAS_TRUE}" && test -z "${am__fastdepCCAS_FALSE}"; then
   as_fn_error $? "conditional \"am__fastdepCCAS\" was never defined.
 Usually this means the macro was only invoked conditionally." "$LINENO" 5
 fi
+if test -z "${enable_asan_TRUE}" && test -z "${enable_asan_FALSE}"; then
+  as_fn_error $? "conditional \"enable_asan\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
 if test -z "${RPM_HARDEND_CC_TRUE}" && test -z "${RPM_HARDEND_CC_FALSE}"; then
   as_fn_error $? "conditional \"RPM_HARDEND_CC\" was never defined.
 Usually this means the macro was only invoked conditionally." "$LINENO" 5
diff --git a/ldap/admin/src/scripts/DSCreate.pm.in b/ldap/admin/src/scripts/DSCreate.pm.in
index cdbad35..b353333 100644
--- a/ldap/admin/src/scripts/DSCreate.pm.in
+++ b/ldap/admin/src/scripts/DSCreate.pm.in
@@ -1009,8 +1009,10 @@ sub updateSelinuxPolicy {
     my $mydevnull = (-c "/dev/null" ? " /dev/null " : " NUL ");
 
     # if selinux is not available, do nothing
-    if ((getLogin() eq 'root') and "@with_selinux@" and
-        -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
+    # In perl, exit(1) is 256 from system. ds_selinux_enable returns 1 on true, 0 on false.
+    if ((getLogin() eq 'root') and "@with_selinux@" and system("$inf->{slapd}->{sbindir}/ds_selinux_enabled") == 256 ) {
+        debug(1, "Selinux is enabled or permissive, fixing contexts\n");
+        # -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
         my $localstatedir = $inf->{slapd}->{localstatedir};
 
         # run restorecon on all of the parent directories we
@@ -1036,36 +1038,30 @@ sub updateSelinuxPolicy {
         }
 
         # label the selected port as ldap_port_t
+        # We should be doing this for secure port too .....
         if ($inf->{slapd}->{ServerPort} != 0) {
-            my $need_label = 1;
-
-            # check if the port is already labeled properly
-            my $portline = `semanage port -l | grep ldap_port_t | grep tcp`;
-            chomp($portline);
-            $portline =~ s/ldap_port_t\s+tcp\s+//g;
-            my @labeledports = split(/,\s+/, $portline);
-            foreach my $labeledport (@labeledports) {
-                if (index($labeledport, "-") == -1) {
-                        # this is not a range of ports
-                        if ($inf->{slapd}->{ServerPort} == $labeledport) {
-                                $need_label = 0;
-                                last;
-                        }
-                } else {
-                        # this is a range of ports like '<portMin>-<portMax>'
-                        my @range = split(/-/, $labeledport);
-                        if ((@range[0] <= $inf->{slapd}->{ServerPort}) && ($inf->{slapd}->{ServerPort} <= @range[1])) {
-                                $need_label = 0;
-                                last;
-                        }
-                }
+            my $port_query_cmd = ("$inf->{slapd}->{sbindir}/ds_selinux_port_query $inf->{slapd}->{ServerPort} ldap_port_t 2> /dev/null");
+            my $need_label = 0;
+            my $result = system($port_query_cmd);
+
+            # 0 is false, 1 is true. True means 'already in policy'.
+            if ($result == 0) {
+                debug(1, "Port $inf->{slapd}->{ServerPort} must be labeled as ldap_port_t \n");
+                $need_label = 1;
+            } 
+            if ($result == 512) {
+                $need_label == 0;
+                debug(0, "Port $inf->{slapd}->{ServerPort} already belongs to another selinux type.\n");
+                debug(0, " The command below will show you the current type that owns the port.\n");
+                debug(0, "sudo $inf->{slapd}->{sbindir}/ds_selinux_port_query $inf->{slapd}->{ServerPort} ldap_port_t\n");
+                debug(0, " It is highly likely your server will fail to start ... \n");
             }
 
             if ($need_label == 1) {
                 my $semanage_err;
                 my $rc;
                 # 60 is a bit excessive, we should fail faster.
-                my $retry = 5;
+                my $retry = 2;
                 $ENV{LANG} = "C";
                 while (($retry > 0) && ($semanage_err = `semanage port -a -t ldap_port_t -p tcp $inf->{slapd}->{ServerPort} 2>&1`) && ($rc = $?)) {
                     debug(1, "Adding port $inf->{slapd}->{ServerPort} to selinux policy failed - $semanage_err (return code: $rc, $retry attempts remain).\n");
@@ -1461,52 +1457,73 @@ sub removeDSInstance {
 
     # remove the selinux label from the ports if needed
     my $mydevnull = (-c "/dev/null" ? " /dev/null " : " NUL ");
-    if ((getLogin() eq 'root') and "@with_selinux@" and
-        -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
+    if ((getLogin() eq 'root') and "@with_selinux@" and system("@sbindir@/ds_selinux_enabled") == 256 ) {
         foreach my $port (@{$entry->{"nsslapd-port"}}) 
         {
+
+            my $need_remove_label = 0;
+            my $port_query_cmd = ("@sbindir@/ds_selinux_port_query $port ldap_port_t 2> /dev/null");
+            my $result = system($port_query_cmd);
+
+            if ($result == 256) {
+                debug(1, "Port $port may be removed as ldap_port_t \n");
+                $need_remove_label = 1;
+            } 
+
             my $semanage_err;
             my $rc;
             my $retry = 5;
             $ENV{LANG} = "C";
-            while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $port 2>&1`) && ($rc = $?)) {
-                if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
-                    $retry = -1;
-                } else {
-                    debug(1, "Warning: Port $port not removed from selinux policy correctly, $retry attempts remain.  Error: $semanage_err\n");
-                    debug(1, "Retrying in 5 seconds\n");
-                    sleep(5);
-                    $retry--;
+            if ($need_remove_label) {
+                while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $port 2>&1`) && ($rc = $?)) {
+                    if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
+                        $retry = -1;
+                    } else {
+                        debug(1, "Warning: Port $port not removed from selinux policy correctly, $retry attempts remain.  Error: $semanage_err\n");
+                        debug(1, "Retrying in 5 seconds\n");
+                        sleep(5);
+                        $retry--;
+                    }
+                }
+                if (0 == $retry) {
+                    push @errs, [ 'error_removing_port_label', $port, $semanage_err];
+                    debug(1, "Warning: Port $port not removed from selinux policy correctly.  Error: $semanage_err\n");
+                    debug(1, "Reached time limit.\n");
                 }
-            }
-            if (0 == $retry) {
-                push @errs, [ 'error_removing_port_label', $port, $semanage_err];
-                debug(1, "Warning: Port $port not removed from selinux policy correctly.  Error: $semanage_err\n");
-                debug(1, "Reached time limit.\n");
             }
         }
 
         foreach my $secureport (@{$entry->{"nsslapd-secureport"}})
         {
+            my $need_remove_label = 0;
+            my $port_query_cmd = ("@sbindir@/ds_selinux_port_query $secureport ldap_port_t 2> /dev/null");
+            my $result = system($port_query_cmd);
+
+            if ($result == 256) {
+                debug(1, "Port $secureport may be removed as ldap_port_t \n");
+                $need_remove_label = 1;
+            } 
             my $semanage_err;
             my $rc;
             my $retry = 60;
             $ENV{LANG} = "C";
-            while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $secureport 2>&1`) && ($rc = $?)) {
-                if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
-                    $retry = -1;
-                } else {
+            if ($need_remove_label) {
+                while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $secureport 2>&1`) && ($rc = $?)) {
+                    if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
+                        $retry = -1;
+                    } else {
+                        debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
+                        debug(1, "Retrying in 5 seconds\n");
+                        sleep(5);
+                        $retry--;
+                    }
+                }
+                if (0 == $retry) {
+                    push @errs, [ 'error_removing_port_label', $secureport, $semanage_err];
                     debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
-                    debug(1, "Retrying in 5 seconds\n");
-                    sleep(5);
-                    $retry--;
+                    debug(1, "Reached time limit.\n");
                 }
             }
-            if (0 == $retry) {
-                push @errs, [ 'error_removing_port_label', $secureport, $semanage_err];
-                debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
-                debug(1, "Reached time limit.\n");
-            }
         }
     }
 
diff --git a/ldap/admin/src/scripts/ds_selinux_enabled.in b/ldap/admin/src/scripts/ds_selinux_enabled.in
new file mode 100755
index 0000000..1980cfb
--- /dev/null
+++ b/ldap/admin/src/scripts/ds_selinux_enabled.in
@@ -0,0 +1,14 @@
+#!/usr/bin/python2
+
+# These are python 3 capable, but el7 doesn't have libsemanage-python3
+
+
+import sys
+import selinux
+import semanage
+
+# Returns 1 for true, 0 for false.
+
+sys.exit(selinux.is_selinux_enabled())
+
+
diff --git a/ldap/admin/src/scripts/ds_selinux_port_query.in b/ldap/admin/src/scripts/ds_selinux_port_query.in
new file mode 100644
index 0000000..f5cec69
--- /dev/null
+++ b/ldap/admin/src/scripts/ds_selinux_port_query.in
@@ -0,0 +1,60 @@
+#!/usr/bin/python2
+
+import sys
+import selinux
+import semanage
+
+# These are python 3 capable, but el7 doesn't have libsemanage-python3
+
+# Given a port number as the first argument, determine if it's already part of the policy.
+# The second (optional) argument is a label type to check.
+
+# 0 for does not exist in policy. 1 mean exists (with no label)
+# or if a lable is given, exists AND inside of label type.
+# 2 means port exists but belongs to a different type.
+
+if len(sys.argv) <= 1:
+    sys.stderr.write("Must provide port to query\n")
+    sys.exit(512)
+
+port = int(sys.argv[1])
+label = None
+try:
+    label = sys.argv[2]
+except:
+    pass
+
+# Get the arguments
+
+# Fail if they are not set correctly.
+
+# Check the port in policy
+h = semanage.semanage_handle_create()
+semanage.semanage_connect(h)
+# This could check high / low values, but eh.
+(r, k) = semanage.semanage_port_key_create(h, port, port, semanage.SEMANAGE_PROTO_TCP)
+
+# Do I need to check _local too?
+(t, e) = semanage.semanage_port_exists(h, k)
+
+if label is None:
+    sys.exit(e)
+
+# See if it has a specifc label
+
+if (e == 0):
+    # No point checking the label, it doesn't exist
+    sys.exit(e)
+
+(t, sp) = semanage.semanage_port_query(h, k)
+
+# do we need to check if this is none? We already know that the port exists, so it must have a context ...
+r = semanage.semanage_port_get_con(sp)
+
+if label == semanage.semanage_context_get_type(r):
+    sys.exit(1)
+
+else:
+    sys.stderr.write('Port belongs to %s\n' % semanage.semanage_context_get_type(r))
+    sys.exit(2)
+
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 02b188d..013e129 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -87,6 +87,7 @@ BuildRequires:    libasan
 
 # this is needed for using semanage from our setup scripts
 Requires:         policycoreutils-python
+Requires:         libsemanage-python
 
 # the following are needed for some of our scripts
 %if %{use_openldap}
@@ -143,15 +144,14 @@ Source2:          %{name}-devel.README
 Source3:          https://git.fedorahosted.org/cgit/nunc-stans.git/snapshot/nunc-stans-%{nunc_stans_ver}.tar.xz
 %endif
 
-%if %{use_asan}
 %description
+%if %{use_asan}
 389 Directory Server is an LDAPv3 compliant server.  The base package includes
 the LDAP server and command line utilities for server administration.
 WARNING! This build is linked to Address Sanitisation libraries. This probably
 isn't what you want. Please contact support immediately.
 Please see http://seclists.org/oss-sec/2016/q1/363 for more information.
 %else
-%description
 389 Directory Server is an LDAPv3 compliant server.  The base package includes
 the LDAP server and command line utilities for server administration.
 %endif
-- 
2.5.5

