>From f9bf0b690a212d52347045a1682a08f46b8c8b01 Mon Sep 17 00:00:00 2001
From: Nathan Kinder <nkinder@redhat.com>
Date: Wed, 30 Sep 2009 09:33:29 -0700
Subject: [PATCH] Add minimum SSF setting

This adds a new configuration setting to the cn=config entry named
nsslapd-minssf. This can be set to a non-negative integer representing
the minimum key strength required to process operations. The default
setting will be 0.

The SSF for a particular connection will be determined by the key
strength cipher used to protect the connection. If the SSF used for a
connection does not meet the minimum requirement, the operation will be
rejected with an error code of LDAP_UNWILLING_TO_PERFORM (53) along
with a message stating that the minimum SSF was not met. Notable
exceptions to this are operations that attempt to protect a connection.
These operations are:

    * SASL BIND
    * startTLS

These operations will be allowed to occur on a connection with a SSF
less than the minimum. If the results of these operations end up with
a SSF smaller than the minimum, they will be rejected.  Additionally,
we allow UNBIND and ABANDON operations to go through.

I also corrected a few issues with the anonymous access switch code
that I noticed while testing.  We need to allow the startTLS extended
operation to go through when sent by an anonymous user since it is
common to send startTLS prior to a BIND to protect the credentials.
I also noticed that we were using the authtype from the operation
struct to determine is a user was anonymous when we really should
have been using the DN.  This was causing anonymous operations to
get through on SSL/TLS connections.
---
 ldap/admin/src/scripts/DSMigration.pm.in |    1 +
 ldap/ldif/template-dse.ldif.in           |    1 +
 ldap/servers/slapd/bind.c                |   12 ++++++
 ldap/servers/slapd/connection.c          |   52 +++++++++++++++++++++++++--
 ldap/servers/slapd/extendop.c            |   20 ++++++++++
 ldap/servers/slapd/libglobs.c            |   59 +++++++++++++++++++++++++++++-
 ldap/servers/slapd/pblock.c              |   10 +++++
 ldap/servers/slapd/proto-slap.h          |    2 +
 ldap/servers/slapd/saslbind.c            |    1 +
 ldap/servers/slapd/slap.h                |    4 ++
 ldap/servers/slapd/slapi-plugin.h        |    1 +
 ldap/servers/slapd/start_tls_extop.c     |    3 ++
 12 files changed, 162 insertions(+), 4 deletions(-)

diff --git a/ldap/admin/src/scripts/DSMigration.pm.in b/ldap/admin/src/scripts/DSMigration.pm.in
index 64e066b..2f5641c 100644
--- a/ldap/admin/src/scripts/DSMigration.pm.in
+++ b/ldap/admin/src/scripts/DSMigration.pm.in
@@ -102,6 +102,7 @@ my %ignoreOld =
 # these are new attrs that we should just pass through
  'nsslapd-allow-unauthenticated-binds' => 'nsslapd-allow-unauthenticated-binds',
  'nsslapd-allow-anonymous-access'  => 'nsslapd-allow-anonymous-access',
+ 'nsslapd-minssf'                  => 'nsslapd-minssf',
  'nsslapd-saslpath'                => 'nsslapd-saslpath',
  'nsslapd-rundir'                  => 'nsslapd-rundir',
  'nsslapd-schemadir'               => 'nsslapd-schemadir',
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
index a047538..72708a0 100644
--- a/ldap/ldif/template-dse.ldif.in
+++ b/ldap/ldif/template-dse.ldif.in
@@ -32,6 +32,7 @@ nsslapd-ssl-check-hostname: on
 nsslapd-allow-unauthenticated-binds: off
 nsslapd-require-secure-binds: off
 nsslapd-allow-anonymous-access: on
+nsslapd-minssf: 0
 nsslapd-port: %ds_port%
 nsslapd-localuser: %ds_user%
 nsslapd-errorlog-logging-enabled: on
diff --git a/ldap/servers/slapd/bind.c b/ldap/servers/slapd/bind.c
index bf54d3c..c6b0092 100644
--- a/ldap/servers/slapd/bind.c
+++ b/ldap/servers/slapd/bind.c
@@ -127,6 +127,7 @@ do_bind( Slapi_PBlock *pb )
     char authtypebuf[256]; /* >26 (strlen(SLAPD_AUTH_SASL)+SASL_MECHNAMEMAX+1) */
     Slapi_Entry *bind_target_entry = NULL;
     int auto_bind = 0;
+    int minssf = 0;
 
     LDAPDebug( LDAP_DEBUG_TRACE, "do_bind\n", 0, 0, 0 );
 
@@ -421,6 +422,17 @@ do_bind( Slapi_PBlock *pb )
         break;
     case LDAP_AUTH_SIMPLE:
         slapi_counter_increment(g_get_global_snmp_vars()->ops_tbl.dsSimpleAuthBinds);
+
+        /* Check if the minimum SSF requirement has been met. */
+        minssf = config_get_minssf();
+        if ((pb->pb_conn->c_sasl_ssf < minssf) && (pb->pb_conn->c_ssl_ssf < minssf)) {
+            send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
+                             "Minimum SSF not met.", 0, NULL);
+            /* increment BindSecurityErrorcount */
+            slapi_counter_increment(g_get_global_snmp_vars()->ops_tbl.dsBindSecurityErrors);
+            goto free_and_return;
+        }
+
         /* accept null binds */
         if (dn == NULL || *dn == '\0') {
             slapi_counter_increment(g_get_global_snmp_vars()->ops_tbl.dsAnonymousBinds);
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
index 4dd81f9..70990c6 100644
--- a/ldap/servers/slapd/connection.c
+++ b/ldap/servers/slapd/connection.c
@@ -65,6 +65,7 @@ static Slapi_PBlock *get_pb( void );
 static void connection_add_operation(Connection* conn, Operation *op);
 static void connection_free_private_buffer(Connection *conn);
 static void op_copy_identity(Connection *conn, Operation *op);
+static void connection_set_ssl_ssf(Connection *conn);
 static int is_ber_too_big(const Connection *conn, ber_len_t ber_len);
 static void log_ber_too_big_error(const Connection *conn,
 				ber_len_t ber_len, ber_len_t maxbersize);
@@ -194,6 +195,7 @@ connection_cleanup(Connection *conn)
 	conn->c_next= NULL;
 	conn->c_prev= NULL;
 	conn->c_extension= NULL;
+	conn->c_ssl_ssf = 0; 
 	/* remove any SASL I/O from the connection */
 	sasl_io_cleanup(conn);
 	sasl_dispose((sasl_conn_t**)&conn->c_sasl_conn);
@@ -372,6 +374,9 @@ connection_reset(Connection* conn, int ns, PRNetAddr * from, int fromLen, int is
     conn->c_idlesince = conn->c_starttime;
     conn->c_flags = is_SSL ? CONN_FLAG_SSL : 0;
     conn->c_authtype = slapi_ch_strdup(SLAPD_AUTH_NONE);
+    /* Just initialize the SSL SSF to 0 now since the handshake isn't complete
+     * yet, which prevents us from getting the effective key length. */
+    conn->c_ssl_ssf = 0;
 }
 
 /* Create a pool of threads for handling the operations */ 
@@ -477,15 +482,42 @@ connection_need_new_password(const Connection *conn, const Operation *op, Slapi_
 static void
 connection_dispatch_operation(Connection *conn, Operation *op, Slapi_PBlock *pb)
 {
+	int minssf = config_get_minssf();
+
 	/* Copy the Connection DN into the operation struct */
 	op_copy_identity( conn, op );
 
+	/* Get the effective key length now since the first SSL handshake should be complete */
+	connection_set_ssl_ssf( conn );
+
+	/* If the minimum SSF requirements are not met, only allow
+	 * bind and extended operations through.  The bind and extop
+	 * code will ensure that only SASL binds and startTLS are
+	 * allowed, which gives the connection a chance to meet the
+	 * SSF requirements.  We also allow UNBIND and ABANDON.*/
+	if ((conn->c_sasl_ssf < minssf) && (conn->c_ssl_ssf < minssf) &&
+	    (op->o_tag != LDAP_REQ_BIND) && (op->o_tag != LDAP_REQ_EXTENDED) &&
+	    (op->o_tag != LDAP_REQ_UNBIND) && (op->o_tag != LDAP_REQ_ABANDON)) {
+		slapi_log_access( LDAP_DEBUG_STATS,
+			"conn=%" NSPRIu64 " op=%d UNPROCESSED OPERATION"
+			" - Insufficient SSF (sasl_ssf=%d ssl_ssf=%d)\n",
+			conn->c_connid, op->o_opid, conn->c_sasl_ssf, conn->c_ssl_ssf );
+		send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL,
+			"Minimum SSF not met.", 0, NULL );
+		return;
+	}
+
 	/* If anonymous access is disabled and the connection is
-	 * not authenticated, only allow the BIND operation. */
+	 * not authenticated, only allow bind and extended operations.
+	 * We allow extended operations so one can do a startTLS prior
+	 * to binding to protect their credentials in transit. 
+	 * We also allow UNBIND and ABANDON. */
 	if (!config_get_anon_access_switch() && (op->o_tag != LDAP_REQ_BIND) &&
-            ((op->o_authtype == NULL) || (strcasecmp(op->o_authtype, SLAPD_AUTH_NONE) == 0))) {
+	    (op->o_tag != LDAP_REQ_EXTENDED) && (op->o_tag != LDAP_REQ_UNBIND) &&
+	    (op->o_tag != LDAP_REQ_ABANDON) && (slapi_sdn_get_dn(&(op->o_sdn)) == NULL )) {
 		slapi_log_access( LDAP_DEBUG_STATS,
-			"conn=%" NSPRIu64 " op=%d UNPROCESSED OPERATION\n",
+			"conn=%" NSPRIu64 " op=%d UNPROCESSED OPERATION"
+			" - Anonymous access not allowed\n",
             		conn->c_connid, op->o_opid );
 
 		send_ldap_result( pb, LDAP_INAPPROPRIATE_AUTH, NULL,
@@ -2541,6 +2573,20 @@ op_copy_identity(Connection *conn, Operation *op)
 	PR_Unlock( conn->c_mutex );
 }
 
+/* Sets the SSL SSF in the connection struct. */
+static void
+connection_set_ssl_ssf(Connection *conn)
+{
+	PR_Lock( conn->c_mutex );
+
+	if (conn->c_flags & CONN_FLAG_SSL) {
+		SSL_SecurityStatus(conn->c_prfd, NULL, NULL, NULL, &(conn->c_ssl_ssf), NULL, NULL);
+	} else {
+		conn->c_ssl_ssf = 0;
+	}
+
+	PR_Unlock( conn->c_mutex );
+}
 
 static int
 is_ber_too_big(const Connection *conn, ber_len_t ber_len)
diff --git a/ldap/servers/slapd/extendop.c b/ldap/servers/slapd/extendop.c
index d3e6d5a..003c2ab 100644
--- a/ldap/servers/slapd/extendop.c
+++ b/ldap/servers/slapd/extendop.c
@@ -295,6 +295,26 @@ do_extended( Slapi_PBlock *pb )
 		goto free_and_return;
 	}
 
+	if (strcmp(extoid, START_TLS_OID) != 0) {
+		int minssf = config_get_minssf();
+
+		/* If anonymous access is disabled and we haven't
+		 * authenticated yet, only allow startTLS. */
+		if (!config_get_anon_access_switch() && ((pb->pb_op->o_authtype == NULL) ||
+    		        (strcasecmp(pb->pb_op->o_authtype, SLAPD_AUTH_NONE) == 0))) {
+			send_ldap_result( pb, LDAP_INAPPROPRIATE_AUTH, NULL,
+				"Anonymous access is not allowed.", 0, NULL );
+			goto free_and_return;
+		}
+
+		/* If the minssf is not met, only allow startTLS. */
+		if ((pb->pb_conn->c_sasl_ssf < minssf) && (pb->pb_conn->c_ssl_ssf < minssf)) {
+			send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL,
+				"Minimum SSF not met.", 0, NULL );
+			goto free_and_return;
+		}
+	}
+
 	/* If a password change is required, only allow the password
 	 * modify extended operation */
 	if (!pb->pb_conn->c_isreplication_session &&
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 5eb1afd..cd7bb5d 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -613,7 +613,10 @@ static struct config_get_and_set {
 	{CONFIG_ANON_ACCESS_ATTRIBUTE, config_set_anon_access_switch,
 		NULL, 0,
 		(void**)&global_slapdFrontendConfig.allow_anon_access, CONFIG_ON_OFF,
-		(ConfigGetFunc)config_get_anon_access_switch}
+		(ConfigGetFunc)config_get_anon_access_switch},
+	{CONFIG_MINSSF_ATTRIBUTE, config_set_minssf,
+		NULL, 0,
+		(void**)&global_slapdFrontendConfig.minssf, CONFIG_INT, NULL}
 #ifdef MEMPOOL_EXPERIMENTAL
 	,{CONFIG_MEMPOOL_SWITCH_ATTRIBUTE, config_set_mempool_switch,
 		NULL, 0,
@@ -875,6 +878,7 @@ FrontendConfig_init () {
   cfg->outbound_ldap_io_timeout = SLAPD_DEFAULT_OUTBOUND_LDAP_IO_TIMEOUT;
   cfg->max_filter_nest_level = SLAPD_DEFAULT_MAX_FILTER_NEST_LEVEL;
   cfg->maxsasliosize = SLAPD_DEFAULT_MAX_SASLIO_SIZE;
+  cfg->minssf = SLAPD_DEFAULT_MIN_SSF;
 
 #ifdef _WIN32
   cfg->conntablesize = SLAPD_DEFAULT_CONNTABLESIZE;
@@ -4666,6 +4670,59 @@ config_get_maxsasliosize()
 }
 
 int
+config_set_minssf( const char *attrname, char *value, char *errorbuf, int apply )
+{
+  int retVal =  LDAP_SUCCESS;
+  int minssf;
+  char *endptr;
+  slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+
+  if ( config_value_is_null( attrname, value, errorbuf, 0 )) {
+        return LDAP_OPERATIONS_ERROR;
+  }
+
+  minssf = (int) strtol(value, &endptr, 10);
+
+  /* Check for non-numeric garbage in the value */
+  if (*endptr != '\0') {
+    retVal = LDAP_OPERATIONS_ERROR;
+  }
+
+  /* Check for a value overflow */
+  if (((minssf == INT_MAX) || (minssf == INT_MIN)) && (errno == ERANGE)){
+    retVal = LDAP_OPERATIONS_ERROR;
+  }
+
+  /* Don't allow negative values. */
+  if (minssf < 0) {
+    retVal = LDAP_OPERATIONS_ERROR;
+  }
+
+  if (retVal != LDAP_SUCCESS) {
+    PR_snprintf(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+                 "%s: \"%s\" is invalid. Value must range from 0 to %d",
+                 attrname, value, INT_MAX );
+  } else if (apply) {
+    CFG_LOCK_WRITE(slapdFrontendConfig);
+    slapdFrontendConfig->minssf = minssf;
+    CFG_UNLOCK_WRITE(slapdFrontendConfig);
+  }
+
+  return retVal;
+}
+
+int
+config_get_minssf()
+{
+  int minssf;
+  slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+
+  minssf = slapdFrontendConfig->minssf;
+
+  return minssf;
+}
+
+int
 config_set_max_filter_nest_level( const char *attrname, char *value,
 		char *errorbuf, int apply )
 {
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
index 072b618..d8cd876 100644
--- a/ldap/servers/slapd/pblock.c
+++ b/ldap/servers/slapd/pblock.c
@@ -351,6 +351,16 @@ slapi_pblock_get( Slapi_PBlock *pblock, int arg, void *value )
 		(*(int *)value) = pblock->pb_conn->c_sasl_ssf;
 		PR_Unlock( pblock->pb_conn->c_mutex );
 		break;
+	case SLAPI_CONN_SSL_SSF:
+		if (pblock->pb_conn == NULL) {
+			LDAPDebug( LDAP_DEBUG_ANY,
+			  "Connection is NULL and hence cannot access SLAPI_CONN_SSL_SSF \n", 0, 0, 0 );
+			return (-1);
+		}
+		PR_Lock( pblock->pb_conn->c_mutex );
+		(*(int *)value) = pblock->pb_conn->c_ssl_ssf;
+		PR_Unlock( pblock->pb_conn->c_mutex );
+		break;
 	case SLAPI_CONN_CERT:
 		if (pblock->pb_conn == NULL) {
 			LDAPDebug( LDAP_DEBUG_ANY,
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index c408f69..35e5697 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -345,6 +345,7 @@ int config_set_outbound_ldap_io_timeout( const char *attrname, char *value,
 int config_set_unauth_binds_switch(const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_require_secure_binds(const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_anon_access_switch(const char *attrname, char *value, char *errorbuf, int apply );
+int config_set_minssf(const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_accesslogbuffering(const char *attrname, char *value, char *errorbuf, int apply);
 int config_set_csnlogging(const char *attrname, char *value, char *errorbuf, int apply);
 
@@ -475,6 +476,7 @@ int config_get_outbound_ldap_io_timeout(void);
 int config_get_unauth_binds_switch(void);
 int config_get_require_secure_binds(void);
 int config_get_anon_access_switch(void);
+int config_get_minssf(void);
 int config_get_csnlogging();
 #ifdef MEMPOOL_EXPERIMENTAL
 int config_get_mempool_switch();
diff --git a/ldap/servers/slapd/saslbind.c b/ldap/servers/slapd/saslbind.c
index ce08c5a..376bec1 100644
--- a/ldap/servers/slapd/saslbind.c
+++ b/ldap/servers/slapd/saslbind.c
@@ -636,6 +636,7 @@ void ids_sasl_server_new(Connection *conn)
     /* Enable security for this connection */
     secprops.maxbufsize = 2048; /* DBDB: hack */
     secprops.max_ssf = 0xffffffff;
+    secprops.min_ssf = config_get_minssf();
     rc = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops);
     if (rc != SASL_OK) {
         LDAPDebug(LDAP_DEBUG_ANY, "sasl_setprop: %s\n",
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index ba65781..0184817 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -279,6 +279,7 @@ typedef void	(*VFP0)(void);
 #define SLAPD_DEFAULT_MAX_THREADS			30		/* connection pool threads */
 #define SLAPD_DEFAULT_MAX_THREADS_PER_CONN	5		/* allowed per connection */
 #define SLAPD_DEFAULT_SCHEMA_IGNORE_TRAILING_SPACES	LDAP_OFF
+#define SLAPD_DEFAULT_MIN_SSF			0	/* allow unsecured connections (no privacy or integrity) */
 
 							/* We'd like this number to be prime for 
 							    the hash into the Connection table */
@@ -1277,6 +1278,7 @@ typedef struct conn {
 	void *c_extension; /* plugins are able to extend the Connection object */
     void *c_sasl_conn;   /* sasl library connection sasl_conn_t */
     int				c_sasl_ssf; /* flag to tell us the SASL SSF */
+    int				c_ssl_ssf; /* flag to tell us the SSL/TLS SSF */
     int				c_unix_local; /* flag true for LDAPI */
     int				c_local_valid; /* flag true if the uid/gid are valid */
     uid_t			c_local_uid;  /* uid of connecting process */
@@ -1723,6 +1725,7 @@ typedef struct _slapdEntryPoints {
 #define CONFIG_UNAUTH_BINDS_ATTRIBUTE "nsslapd-allow-unauthenticated-binds"
 #define CONFIG_REQUIRE_SECURE_BINDS_ATTRIBUTE "nsslapd-require-secure-binds"
 #define CONFIG_ANON_ACCESS_ATTRIBUTE "nsslapd-allow-anonymous-access"
+#define CONFIG_MINSSF_ATTRIBUTE "nsslapd-minssf"
 #ifndef _WIN32
 #define CONFIG_LOCALUSER_ATTRIBUTE "nsslapd-localuser"
 #endif /* !_WIN32 */
@@ -2018,6 +2021,7 @@ typedef struct _slapdFrontendConfig {
   int allow_unauth_binds;       /* switch to enable/disable unauthenticated binds */
   int require_secure_binds;	/* switch to require simple binds to use a secure channel */
   int allow_anon_access;	/* switch to enable/disable anonymous access */
+  int minssf;			/* minimum security strength factor (for SASL and SSL/TLS) */
   size_t maxsasliosize;         /* limit incoming SASL IO packet size */
 #ifndef _WIN32
   struct passwd *localuserinfo; /* userinfo of localuser */
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 75a2ddc..5ed054d 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -3066,6 +3066,7 @@ int slapi_reslimit_get_integer_limit( Slapi_Connection *conn, int handle,
 #define SLAPI_CONN_CERT				743
 #define SLAPI_CONN_AUTHMETHOD			746
 #define SLAPI_CONN_SASL_SSF			748
+#define SLAPI_CONN_SSL_SSF			749
 
 /* 
  * Types of authentication for SLAPI_CONN_AUTHMETHOD
diff --git a/ldap/servers/slapd/start_tls_extop.c b/ldap/servers/slapd/start_tls_extop.c
index bb65e5f..def71db 100644
--- a/ldap/servers/slapd/start_tls_extop.c
+++ b/ldap/servers/slapd/start_tls_extop.c
@@ -282,6 +282,8 @@ start_tls( Slapi_PBlock *pb )
 	conn->c_sd = ns;
 	conn->c_prfd = newsocket;
 
+        /* Get the effective key length */
+	SSL_SecurityStatus(conn->c_prfd, NULL, NULL, NULL, &(conn->c_ssl_ssf), NULL, NULL);
 	
 	rv = slapd_ssl_handshakeCallback (conn->c_prfd, (void *)handle_handshake_done, conn);
 
@@ -411,6 +413,7 @@ start_tls_graceful_closure( Connection *c, Slapi_PBlock * pb, int is_initiator )
 	c->c_sd = ns;
         c->c_flags &= ~CONN_FLAG_SSL;
         c->c_flags &= ~CONN_FLAG_START_TLS;
+        c->c_ssl_ssf = 0;
 
 	/*  authentication & authorization credentials must be set to "anonymous". */
 
-- 
1.6.2.5

