Programmatic EXTERNAL SASL connection to OpenLDAP

The documentation on the OpenLDAP site discusses modifying the ldif files used to start up the server.  If you try to do this on a Fedora or Debian based install,  you will find that the server does not start up.  The HASH of the files is stored and compared with the contents at start up time.  There is a better way.

On my OpenLDAP install,  there are three databases served by SLAPD.  The first two  are for  configuration and  monitoring.  The third is the one that acts as the backing store for authentication and other data that is publicly served.  When the server starts up,  it is configured with a common name of   cn=example,cn=com,  which is obviously sample data.  To modify this,  requires changing the values in the config database.

Below is an ldif file that would change the Common name, as well as set the userid and password for managing the directory

dn:  olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=younglogic,dc=com
-
replace: olcRootDN
olcRootDN: dc=Manager,dc=younglogic,dc=com
-
add: olcRootPW
olcRootPW: {SSHA}lBDIdfwvZkITal0k9tdhiCUolxpf6anu

I generated the HASH using the slappasswd command line tool.
To modify the config, you then execute ldapmodify.

sudo ldapmodify -Y EXTERNAL -H ldapi:///  -f /home/ayoung/etc/managerbase.ldif

What is this EXTERNAL? It is a SASL mechanism that uses the underlying system configured authentication. This is the NSS value that you would get back from getent. The default install protects the configuration database using the root credentials. The URL ldapi:/// is a Unix socket connection to /var/run/ldapi.

What happens if you want to modify the configuration pragmatically? The steps to make a SASL External connection are not to clear.  In order to figure out how to do it, I’ve started poking inside of the ldapmodify code from the openldap source. The CLI tools are under openldap/clients/tools/. The connection is created in the file common.c. From what I’ve seen elsewhere on the web, I know it needs to resolve to one of the ldap*bind calls. In my file, inside the function tool_bind, I see it on line 1469

	rc = ldap_sasl_interactive_bind_s(
          ld, binddn, sasl_mech,
	   sctrlsp, NULL, sasl_flags, lutil_sasl_interact, defaults )

Now, I’ve run this through ltrace and I’ve seen

ldap_sasl_interactive_bind_s(0x1abf290, 0, 0x1abf030, 0, 0

So I know to expect binddn to be null, and sasl_mech needs to have a real value. Look in the argument parsing part of the code for the -Y flag tells that this is a pass through of the string passed on the command line.

    case 'Y':
        /*error chcking ellided here*/
	sasl_mech = ber_strdup( optarg );

So that should be the string EXTERNAL for our purposes.

The next two parameters are 0, sctrlsp and (surprise) NULL.

sasl_flags looks like it is set to the default in the top of the file and never modified in our code path:

unsigned	sasl_flags = LDAP_SASL_AUTOMATIC;

That leaves the last two parameters. One is a callback function lutil_sasl_interact and one is the defaults structure. The function lutil_sasl_interact is defined in the file openldap/libraries/liblutil/sasl.c. Between it and the function interaction which it calls, we are talking about 200 lines of code. For now, I am just going to cut and paste that code into my C file and get all of the headers coorect to run it. Later, I’ll step through it in the debugger to see what it is really doing.

The defaults structure is also defined in the sasl/c file specified above. From the ldap_bind man page:

The interact function uses the provided defaults to handle requests from the SASL library for particular authentication parameters. There is no defined format for the defaults information; it is up to the caller to use whatever format is appropriate for the supplied interact function. The sasl_interact parameter comes from the underlying SASL library. When used with Cyrus SASL this is an array of sasl_interact_t structures. The Cyrus SASL library will prompt for a variety of inputs, including:…

Again, I will cut and paste this code into my file and get it running. In order to compile I need to add the flags

CFLAGS=-lldap -llber -lsasl2 -g -I/usr/include/sasl

Once I can compile and run, I can dump out the contents of the config directory. Stepping through the code I discover a few things. In the code that builds the defaults, the non-zero values are. defaults->authcid = “root” and defaults->mech “EXTERNAL”. When the function lutil_sasl_interact is called, the parameters are: The ldap structure from our init, flags=0, defaults, and in, which is cast to at sasl_interact_t. The only part of this that seems interesting to us is that it requests SASL_CB_USER from interact, which basically checks the user is root. The code then makes the most elegant use of a goto that I’ve seen in a long while, skips a whole load of interactive code, and returns the default:

use_default:
		/* input must be empty */
		interact->result = (dflt && *dflt) ? dflt : "";
		interact->len = strlen( interact->result )

This says to me that the interaction callback function could be reduced to:

int do_interact(
	LDAP *ld,
	unsigned flags,
	void *defaults,
	void *in )
{
	sasl_interact_t *interact = in;
	lutilSASLdefaults * sasl_defaults = (lutilSASLdefaults *)defaults;
	const char *dflt = interact->defresult;
	dflt = sasl_defaults->authzid;
	interact->result = (dflt && *dflt) ? dflt : "";
	interact->len = strlen( interact->result );
	return LDAP_SUCCESS;
}

But now I see that the defaults passed in are defined external to the ldap code. We can drop all of the pointers except the one to the authzid. In fact, we can just make this a char *.

Here’s the code in a functional state. It leaks memory, which would need to be cleaned up for a real application.


#include 
#include 
#include 


int do_interact(
		LDAP *ld,
		unsigned flags,
		void *defaults,
		void *in )
{
  sasl_interact_t *interact = in;
  char * sasl_defaults = (char  *)defaults;
  const char *dflt = interact->defresult;
  dflt = sasl_defaults;
  interact->result = (dflt && *dflt) ? dflt : "";
  interact->len = strlen( interact->result );
  return LDAP_SUCCESS;
}

int main(){
  printf("Start\n");
  LDAP *ldap;
  int rc;
  struct berval *servercredp;
  unsigned long version = LDAP_VERSION3;
  LDAPMessage *res;
  char ** vals;
  int message_count;
  int i,j,k;

  if (( rc = ldap_initialize(&ldap,  "ldapi:///")) != LDAP_SUCCESS)
    {
      perror ( NULL );
      return( 1 );
    }

  rc = ldap_set_option(ldap,
		       LDAP_OPT_PROTOCOL_VERSION,
		       (void*)&version);
  char * defaults;
  char * sasl_mech = "EXTERNAL";
  char * sasl_realm = NULL;
  char * sasl_authc_id = NULL;
  char * sasl_authz_id = NULL;
  struct berval	passwd = { 0, NULL };
  unsigned	sasl_flags = LDAP_SASL_AUTOMATIC;
  LDAPControl	**sctrlsp = NULL;

  ldap_get_option( ldap, LDAP_OPT_X_SASL_AUTHZID, &defaults );

  char *	binddn = NULL;
  rc = ldap_sasl_interactive_bind_s( ldap, binddn, sasl_mech,
				     sctrlsp,
				     NULL, sasl_flags, do_interact, defaults );

  if ((rc =  ldap_search_ext_s
       (
	ldap,
	"cn=config",
	LDAP_SCOPE_SUBTREE,
	"(objectClass=*)",
	NULL,
	0,
	NULL,
	NULL,
	NULL,
	0,
	&res ) != LDAP_SUCCESS))
    {
      printf("ldap_search  failed with 0x%x.\n",rc);
      perror ( NULL );
      return( 1 );
    }
  LDAPMessage *entry = ldap_first_entry( ldap, res );

  int entry_count = ldap_count_entries(ldap, res);
  for (i = 0 ; i < entry_count; i++){
    printf("dn: %s\n",ldap_get_dn(ldap, entry));
    BerElement * ber;
    char * attribute = ldap_first_attribute(ldap,entry, &ber);
    while(attribute){
      printf ("attribute = %s\n",attribute);
      attribute = ldap_next_attribute(ldap,entry, ber);
    }
    ber_free(ber,0);
    entry = ldap_next_entry(ldap, entry);

  }
  return 0;

}

Note: The reason I do SASL using a -I option in the makefile instead of sasl/sasl.h is that the code formatter is messing it up.

4 thoughts on “Programmatic EXTERNAL SASL connection to OpenLDAP

  1. Bro, you are awesome. You made my night. I was trying sasl_bind_s with GSSAPI and it simply did not work. After checking ldapsearch.c code, it was obvious to user sasl_interactive_bind_s, hell knows why. I failed with the function and default pointer. Crappy docs by the way. You reverse enginering made it work!

  2. First of all, thank you so much for this superb article. It really saved my time. After running this program. I am getting LDAP_AUTH_UNKNOWN error though “external” mechanism is supported by the server.

    Below is the list of supported mechanisms and the server is running on windows ActiveDirectory.

    Thanks in advance.

  3. Forgot to provide the list the of mechanisms supported.

    supportedSASLMechanisms : GSSAPI
    supportedSASLMechanisms : GSS-SPNEGO
    supportedSASLMechanisms : EXTERNAL
    supportedSASLMechanisms : DIGEST-MD5

  4. I’d run it through a debugger and see what is happening with your own eyes. Its been long enough for me that I couldn’t give decent advice off the top of my head.

Leave a Reply

Your email address will not be published. Required fields are marked *