- Writing an Authentication Plug-in for a Sun· ONE Directory Server
- Deciding Whether to Write a Plug-in
- Types of Plug-ins
- Working in the Plug-in Application Program Interface
- Authentication in the Directory Server
- UNIX Authentication Plug-in
- Testing the Plug-in
- About the Author
- Related Resources
- Ordering Sun Documents
- Accessing Sun Documentation Online
UNIX Authentication Plug-in
Our sample plug-in is expected to perform the following tasks:
Accept a parameter in the form of a distinguished name that indicates the directory under which it should override the directory server authentication.
Perform a /etc/passwd-based authentication (in the same way as Telnet, for example).
With this in mind, it is very easy to identify the kind of plug-in we need. We need a preoperation plug-in to intercept the bind data, and then to change the bind mechanism for users authenticating with UNIX login names and passwords.
At this point, all we need to do is plan the development and go straight to the code.
To Plan the Creation of a Plug-in
Before you start to write a plug-in, write a good makefile that, at a minimum, will perform the following tasks:
Compile the plug-in or compile the debug version of the plug-in (if any).
Install the plug-in in the directory server.
Uninstall the plug-in.
Clean up compilations of intermediate files.
In addition, before you create a plug-in, you must prepare the installation/uninstallation LDAP data interchange format (LDIF) file.
cnThe name of the plug-in.
nsslapd-pluginpathThe location of your plug-in library (in this case, it points to a subdirectory of my home directory).
nsslapd-plugininitfuncSpecifies which function in the plug-in library the directory server must use to initialize the plug-in.
nsslapd-pluginargX, where X=0,1,2,...Your plug-in arguments. Use this attribute as your needs dictate. In our case, the first argument indicates whether the plug-in runs in debug mode and the second argument identifies where to perform the special authentication.
The following version of a makefile works with a GNU makefile and GNU Compiler Collection (GCC). I provide it for those who like to use GNU tools. Use alternative settings as suggested at the beginning of the makefile to use the Sun™ ONE studio compiler:
# Makefile for compiling the UnixAuth plugin # Hint for compiling libraries with gcc on SPARC(actually on # every platform supporting PIC code): # # gcc -fPIC -c *.c # generate PIC obj files # # for SunONE Studio tools suite, which is the default on Solaris OE, use this vars: # # CC=/opt/SUNWspro/bin/CC (check your install dir) # SOFLAGS=-G # CFLAGS= -KPIC # LD=/opt/SUNWspro/bin/CC (check your install dir) # # change the following props to reflect your installation / environment # (make sure LDAP cmd line utils are in PATH... :) # LDAP_HOST=localhost LDAP_PORT=7075 LDAP_ROOT_DN="dc=iplanet,dc=italy,dc=sun,dc=com" LDAP_DMANAGER_DN="cn=Directory Manager" LDAP_DMANAGER_PASSWD=iplanet0 PLUGIN_INSTALL_LDIF=./etc/unixauth-install.ldif PLUGIN_UNINSTALL_LDIF=./etc/unixauth-uninstall.ldif PLUGIN_HEADERS=/opt/local/iplanet/directory50/plugins/slapd/slapi/include PLUGIN_NAME=unixauth SONAME=$(PLUGIN_NAME).so MAJOR=1 MINOR=0 CC=gcc RM=rm -rf CFLAGS=-fPIC -I $(PLUGIN_HEADERS) -D_REENTRANT LDFLAGS=-shared lib: $(SONAME).$(MAJOR).$(MINOR) $(SONAME).$(MAJOR).$(MINOR) : $(PLUGIN_NAME).o $(CC) $(LDFLAGS) $< -o $(SONAME).$(MAJOR).$(MINOR) $(PLUGIN_NAME).o: $(PLUGIN_NAME).c $(PLUGIN_NAME).h $(CC) -c $(CFLAGS) $< -o $@ clean: $(RM) *.o cleanall: clean $(RM) *.so.* install: ldapmodify -p $(LDAP_PORT) -h $(LDAP_HOST) -D $(LDAP_DMANAGER_DN) -w $(LDAP_DMANAGER_PASSWD) -f $(PLUGIN_INSTALL_LDIF) uninstall: ldapdelete -p $(LDAP_PORT) -h $(LDAP_HOST) -D $(LDAP_DMANAGER_DN)-w $(LDAP_DMANAGER_PASSWD)
´cat $(PLUGIN_INSTALL_LDIF) | grep "^dn"| sed "s/dn: //g"´
As you can see, this makefile expects the Sun ONE Directory Server tool ldapmodify, libraries, and include files to be on the same box where you are developing. This is usually the case.
From a directory server point of view, a plug-in is two things. It is an entry in the directory information tree (DIT) and a shared object (*.so) to load that contains the functions to execute. The latter part is just what you write, the plug-in code; the former part is an ordinary LDIF modification in the following form:
dn: cn=unixauth,cn=plugins,cn=config changetype: add cn: unixauth objectclass: top objectclass: nsSlapdPlugin objectclass: extensibleObject nsslapd-pluginpath: /export/home/nico/devel/ids/unixauth_pg/ unixauth.so.1.0 nsslapd-plugininitfunc: unixauth_preop_init nsslapd-plugintype: preoperation nsslapd-pluginarg0: 1 nsslapd-pluginarg1: ou=unix,dc=iplanet,dc=italy,dc=sun,dc=com nsslapd-pluginenabled: on nsslapd-pluginid: Unix authentication plugin nsslapd-pluginversion: 1.0 nsslapd-pluginvendor: Sun Microsystems Italy nsslapd-plugindescription: This plug-in performs standard Unix passwd authentication
The preceding LDIF file adds a new entry in the cn=plugins,cn=config directory, which is where all plug-ins are registered. With a little trick in the makefile target uninstall, we perform the uninstallation of the plug-in without an uninstallation LDIF file.
The following attributes are relevant in the LDIF installation file:
The other attributes of the entry are pretty intuitive. Not included in the preceding list, the attributes nsslapd-depends-on-name or nsslapd-depends-on-type specifies plug-in names or plug-in types on which your plug-in depends. Refer to the product documentation for the details.
To Code a Preoperation Plug-in
Like many other types of plug-ins, a preoperation plug-in is essentially made up of the following pieces of code:
An initialization/registration function
An operation function (the function you specify to do the work)
Other helper routines for your own use
Write an initialization function that is responsible for the following tasks, at a minimum:
Specifying server version compatibility.
Registering the operation it intends to handle and the related routine. Basically, tell the directory server that you want to hook a certain operation and provide the function it should call when this operation, for example a SEARCH or DELETE, takes place. The function also registers the plug-in description structure, which the server uses later to show plug-in information in the directory console.
Collecting arguments passed through the nsslapd-pluginargX attribute.
Returning a valid result code (for example, 0 if nothing goes wrong).
Ensure that the registration function accepts a parameter called Slapi_PBlock, which is common for plug-in API functions, and ensure that it returns an error code to report registration result.
Use the registration of the information structure unixauthDesc to enable you to pass textual information you see when managing plug-ins through the directory server console.
-
Before returning control to the directory server, read arguments passed to the plug-in (arg0 and arg1), using the slapi_pblock_get() API routines, and then store them for later use.
passwd_bind_fn, which is responsible for collecting binding information (user name, password, and authentication method) and for doing mandatory actions.
authenticate_unix_user, which is responsible for the pure authentication. It does the dirty work, taking the user name/password pairs and checking the user in /etc/passwd and /etc/shadow.
The system call getspnam(3C) is used to read the shadow passwords from /etc/shadow. Note that to call this function and read the shadow file, you must have root privileges and, therefore, the directory server daemon must have root privileges.
The system function crypt(3C) is called to encode the clear text password entered by the user, before comparing it with the password stored in /etc/passwd.
Write an operation function.
Here are some relevant details of our specific registration function, unixauth_preop_init.
The slapi_pblock_(set|get) functions are used to manipulate the Slapi_PBlock structure. An integer constant (for example, SLAPI_PLUGIN_VERSION) specifies the field of the structure you want to access. Don't forget that this structure is used by other functions, before and after your functions are called.
To register our preoperation function, we set SLAPI_PLUGIN_PRE_BIND_FN with a pointer to the function passwd_bind_fn. There are similar constants to register pre and post operation for search, modify, and delete. Our authentication plug-in example needs to handle only binding operations.
We set this argument in the LDIF installation file. However, you can change it through the administration console, and even add other new arguments.
The following is the operation function for the unixauth authentication plug-in:
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ROUTINE: unixauth_preop_init * * DESCR: plug-in initialization and registration routine. Called directly * by iDS at startup * * PARAMS: pb=parameter block * * RETURNS: 0 if ok, -1 on error (as plug-in specs require...) * *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ int unixauth_preop_init(Slapi_PBlock *pb) { int argc; char **argv; /* args var */ slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Registering plugin functions...\n" ); /* * set plugin about info */ if(slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)passwd_bind_fn) != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Error while registering plugin function\n" ); return ERR; } /* * specify server compatibility */ if(slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) == -1) { slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Error while setting comp. version" ); return ERR; } /* * set plugin about info */ if(slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, &unixauthDesc) == -1) { slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Error while setting plugin info\n" ); return ERR; } /* * get plugin args */ if(slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc) != 0 || slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) { slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Error while getting plugin args\n" ); return ERR; } /* setup args */ unix_dn_realm = argv[0]; debug = atoi(argv[1]); if(debug) { slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Args:\n" ); slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "\tunix_dn_realm=%s\n", unix_dn_realm); slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "\tdebug=%d\n",debug); } slapi_log_error( SLAPI_LOG_PLUGIN, "unixauth_preop_init", "Registration...done!\n" ); return OK; /* ok! */ }
At this point, the directory server calls the passwd_bind_fn() function whenever a user binds. To manage binding through UNIX password authentication, we split the code into two functions, as follows:
To completely take over the bind operation, passwd_bind_fn must cooperate with the directory server. This requires you to set some pointers in the Slapi_PBlock structure. In particular, set the bind DN and the authentication method (for example, the former is required by other operations for access control list [ACL] evaluation purposes). Some of the information to set is available from the Slapi_PBlock through slapi_pblock_get().
After calling authenticate_unix_user, there is a simple switch that evaluates the authentication result code. Note that the slapi_send_ldap_result() is used to send results to the client. The slapi_send_ldap_result() function sends you the "No such object" or "Invalid credentials" messages when authentication goes wrong, either because of a bad user name or a bad password. You must use this function to send meaningful messages back to the client (for example the message "Unable to read /etc/passwd or /etc/shadow file").
The authenticate_unix_user() function is pretty simple to understand if you have some UNIX programming experience.
The operation function contains the code that performs your logic and is the area where you must pay the most attention and devote your best effort. The following is an example operation function:
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ROUTINE: passwd_bind_fn * * DESCR: plug-in preoperation function * * PARAMS: pb=parameter block * * RETURNS: 0 if ok, -1 on error (as plug-in specs require...) * *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ int passwd_bind_fn(Slapi_PBlock *pb) { char *bind_dn = NULL; struct berval *bind_cred = NULL; int bind_method; char username[MAX_USERNAME]; char passwd[MAX_PASSWD]; int res; char *p, *q; if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Entering...\n" ); /* get bind data */ if( slapi_pblock_get( pb, SLAPI_BIND_TARGET, &bind_dn ) != 0 || slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &bind_cred ) != 0 || slapi_pblock_get( pb, SLAPI_BIND_METHOD, &bind_method ) != 0 ) { if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Error occurred on slapi_pblock_get...\n" ); slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR,NULL, "Internal error", 0, NULL); return ERR; }; /* * Let the server handle SASL */ if (bind_method == LDAP_AUTH_SASL) return 0; /* * plugin mandatory actions */ if(slapi_pblock_set( pb, SLAPI_CONN_DN, slapi_ch_strdup(bind_dn) ) != 0 || slapi_pblock_set( pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_SIMPLE ) != 0) { if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Error occurred on slapi_pblock_set...\n" ); slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR,NULL, "Internal error", 0, NULL); return ERR; } if(debug) /* debug stuff :) */ { slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Bind DN: %s\n", bind_dn); slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Bind cred(ln): %d\n",bind_cred->bv_len); slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Bind cred(pw): %s\n",bind_cred->bv_val); slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Bind method: %d\n", bind_method); } /* * performs Unix authentication if the bind DN * is under the unix_dn_realm */ if(!slapi_dn_issuffix(slapi_dn_normalize_case(bind_dn), unix_dn_realm)) { if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Not in Unix realm, go on...\n" ); /* * Performs standard directory authentication */ res = do_standard_authentication(bind_dn, bind_cred,bind_method); slapi_send_ldap_result(pb, res,NULL, NULL, 0, NULL); return 1; } /* extracts username part of uid=nick,ou=.... */ p = bind_dn + 4; /* skip "uid=": we suppose an inetOrgPerson entry, of coarse ;) */ q = &username[0]; while(',' != *p)*q++=*p++; *q='\0'; res = authenticate_unix_user(username, bind_cred->bv_val); switch(res) { case UNIXAUTH_ERROR: if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Error in authenticate_unix_user\n" ); slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR,NULL, "Internal error", 0, NULL); return ERR; break; case UNIXAUTH_USER_NOT_FOUND: if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "User not found in /etc/passwd.\n" ); slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT,NULL, "User not found in /etc/passwd", 0, NULL); return ERR; break; case UNIXAUTH_INVALID_CREDENTIALS: if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", "Wrong credentials\n" ); slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS,NULL, "Wrong credentials", 0, NULL); return ERR; break; } assert(res == UNIXAUTH_AUTHENTICATED); /* sends back auth result */ slapi_send_ldap_result(pb, LDAP_SUCCESS,NULL, "Unix user authenticated ", 0, NULL); if(debug)slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_bind_fn", " User authenticated\n" ); return 1; /* ok! must be >0 in any case */ }