This patch takes care of /most/ of the normal SASL use cases. I have not tested rebinds or any reconnection feature. As well, I do not know what the patch's effect on simple bind is. Simple binds should work as well, but I don't use simple binds. The SASL implementation is borrowed from OpenLDAP's client tools and seems to work well with any command-line based application. diff -urN ldapvi-1.5/arguments.c arguments.c --- ldapvi-1.5/arguments.c 2005-12-11 09:25:30.000000000 -0800 +++ arguments.c 2005-12-22 12:06:53.000000000 -0800 @@ -48,6 +48,13 @@ " -q, --quiet Disable progress output.\n" \ " -v, --verbose Note every update.\n" \ " -!, --noquestions Don't ask for confirmation.\n" \ +" -I, --sasl_interactive Use SASL Interactive mode.\n" \ +" -Q, --sasl_quiet use SASL Quiet mode.\n" \ +" -O, --sasl_secprops P SASL security properties.\n" \ +" -r, --sasl_realm R SASL realm.\n" \ +" -U, --sasl_authcid AC SASL authentication identity.\n" \ +" -X, --sasl_authzid AZ SASL authorization identity.\n" \ +" -Y, --sasl_mech MECH SASL Mechanism.\n" \ " -H, --help This help.\n" \ "\n" \ "Environment variables: VISUAL, EDITOR, PAGER.\n" \ @@ -77,6 +84,14 @@ {"scope", 's', POPT_ARG_STRING, 0, 's', 0, 0}, {"base", 'b', POPT_ARG_STRING, 0, 'b', 0, 0}, {"user", 'D', POPT_ARG_STRING, 0, 'D', 0, 0}, + {"sasl_interactive",'I',0, 0, 'I', 0, 0}, + {"sasl_quiet" ,'Q',0, 0, 'Q', 0, 0}, + {"sasl_secprops",'O', POPT_ARG_STRING, 0, 'O', 0, 0}, + {"sasl_realm", 'r', POPT_ARG_STRING, 0, 'r', 0, 0}, + {"sasl_mech", 'Y', POPT_ARG_STRING, 0, 'Y', 0, 0}, + {"sasl_authzid",'X', POPT_ARG_STRING, 0, 'X', 0, 0}, + {"sasl_authcid",'U', POPT_ARG_STRING, 0, 'U', 0, 0}, + {"sasl_realm", 'r', POPT_ARG_STRING, 0, 'r', 0, 0}, {"password", 'w', POPT_ARG_STRING, 0, 'w', 0, 0}, {"chase", 'C', POPT_ARG_STRING, 0, 'C', 0, 0}, {"deref", 'a', POPT_ARG_STRING, 0, 'a', 0, 0}, @@ -107,7 +122,11 @@ } void -parse_arguments(int argc, const char **argv, cmdline *result, GPtrArray *ctrls) +parse_arguments(int argc, + const char **argv, + cmdline *result, + bind_auth_options *auth_opt, + GPtrArray *ctrls) { int c; poptContext ctx; @@ -140,9 +159,11 @@ break; case 'D': result->user = arg; + auth_opt->user = arg; break; case 'w': result->password = arg; + auth_opt->password = arg; break; case 'd': result->discover = 1; @@ -231,6 +252,34 @@ case 'v': result->verbose = 1; break; + case 'I': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_mode = LDAP_SASL_INTERACTIVE; + break; + case 'Q': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_mode = LDAP_SASL_QUIET; + break; + case 'U': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_authcid = arg; + break; + case 'X': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_authzid = arg; + break; + case 'Y': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_mech = arg; + break; + case 'r': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_realm = arg; + break; + case 'O': + auth_opt->authmethod = LDAP_AUTH_SASL; + auth_opt->sasl_secprops = arg; + break; case '!': result->noquestions = 1; break; diff -urN ldapvi-1.5/common.h common.h --- ldapvi-1.5/common.h 2005-12-11 09:25:30.000000000 -0800 +++ common.h 2005-12-22 12:03:16.000000000 -0800 @@ -67,8 +67,23 @@ int config; } cmdline; -void parse_arguments( - int argc, const char **argv, cmdline *result, GPtrArray *ctrls); +typedef struct bind_auth_options { + ber_tag_t authmethod; // LDAP_AUTH_SASL or LDAP_AUTH_NONE + unsigned int sasl_mode; // LDAP_SASL_AUTOMATIC, _QUIET, or _INTERACTIVE + char *user; // DN or user search filter + char *password; // Simple or SASL passwd + char *sasl_authcid; + char *sasl_authzid; + char *sasl_mech; + char *sasl_realm; + char *sasl_secprops; +} bind_auth_options; + +void parse_arguments(int argc, + const char **argv, + cmdline *result, + bind_auth_options *auth_opt, + GPtrArray *ctrls); void usage(int fd, int rc); /* diff -urN ldapvi-1.5/GNUmakefile.in GNUmakefile.in --- ldapvi-1.5/GNUmakefile.in 2005-12-11 09:25:30.000000000 -0800 +++ GNUmakefile.in 2005-12-22 11:06:48.000000000 -0800 @@ -9,7 +9,7 @@ dist: ldapvi ldapvi.1 -ldapvi: ldapvi.o data.o diff.o error.o misc.o parse.o port.o print.o search.o base64.o arguments.o +ldapvi: ldapvi.o data.o diff.o error.o misc.o parse.o port.o print.o search.o base64.o arguments.o sasl.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) %.o: %.c diff -urN ldapvi-1.5/ldapvi.c ldapvi.c --- ldapvi-1.5/ldapvi.c 2005-12-11 09:25:30.000000000 -0800 +++ ldapvi.c 2005-12-22 12:37:21.000000000 -0800 @@ -18,6 +18,7 @@ #include #include #include "common.h" +#include "lutil_ldap.h" static int compare(int (*handler)(tentry *, tentry *, LDAPMod **, void *), @@ -224,21 +225,19 @@ } struct rebind_data { - char *user; - char *password; + bind_auth_options *auth_options; LDAPURLDesc *seen; }; static char *login( - LDAP *ld, char *user, char *password, int register_callback); + LDAP *ld, bind_auth_options *auth_opt, int register_callback); static int rebind_callback( LDAP *ld, const char *url, ber_tag_t request, int msgid, void *args) { struct rebind_data *rebind_data = args; - char *user = rebind_data->user; - char *password = rebind_data->password; + bind_auth_options *auth_opt = rebind_data->auth_options; LDAPURLDesc *urld; printf("Received referral to %s.\n", url); @@ -260,10 +259,10 @@ for (;;) { switch (choose("Rebind?", "y!nqQ?", "(Type '?' for help.)")) { case 'y': - user = password = 0; + auth_opt->user = auth_opt->password = 0; /* fall through */ case '!': - if (login(ld, user, password, 0)) { + if (login(ld, auth_opt, 0)) { if (rebind_data->seen) ldap_free_urldesc(rebind_data->seen); rebind_data->seen = urld; @@ -322,30 +321,57 @@ } static int -rebind(LDAP *ld, char *user, char *password, int register_callback, char **dn) +rebind(LDAP *ld, bind_auth_options *auth_opt, int register_callback, char **dn) { int free_password = 0; int rc = -1; struct rebind_data *rebind_data = xalloc(sizeof(struct rebind_data)); - if (user && !password) { - password = get_password(); + if (auth_opt->user + && auth_opt->authmethod != LDAP_AUTH_SASL + && !auth_opt->password) { + auth_opt->password = get_password(); free_password = 1; } - if (user && user[0] == '(') + if (auth_opt->user && auth_opt->user[0] == '(') /* user is a search filter, not a name */ - if ( !(user = find_user(ld, user))) + if ( !(auth_opt->user = find_user(ld, auth_opt->user))) goto cleanup; - if (ldap_bind_s(ld, user, password, LDAP_AUTH_SIMPLE)) { + + if (!auth_opt->user || auth_opt->authmethod == LDAP_AUTH_SASL) { + void *defaults; + int bind_rc; + struct timeval tv = { 0, 750000 }; + defaults = lutil_sasl_defaults(ld, + auth_opt->sasl_mech, + auth_opt->sasl_realm, + auth_opt->sasl_authcid, + auth_opt->password, + auth_opt->sasl_authzid); + bind_rc = ldap_sasl_interactive_bind_s( ld, auth_opt->user, + auth_opt->sasl_mech, NULL, NULL, + auth_opt->sasl_mode, lutil_sasl_interact, defaults ); + lutil_sasl_freedefs(defaults); + if (bind_rc != LDAP_SUCCESS) { + ldap_perror( ld, "ldap_sasl_interactive_bind_s" ); + rc = -1; + goto cleanup; + } + /* implement a 750ms delay so we can see the SASL info scroll */ + select(0, NULL, NULL, NULL, &tv); + } + if (auth_opt->user && auth_opt->authmethod == LDAP_AUTH_SIMPLE + && ldap_bind_s(ld, auth_opt->user, + auth_opt->password, + auth_opt->authmethod)) { ldap_perror(ld, "ldap_bind"); goto cleanup; } rc = 0; - if (dn) *dn = user; + if (dn) *dn = auth_opt->user; if (register_callback) { - rebind_data->user = user; - rebind_data->password = xdup(password); + rebind_data->auth_options = auth_opt; rebind_data->seen = 0; if (ldap_set_rebind_proc(ld, rebind_callback, rebind_data)) ldaperr(ld, "ldap_set_rebind_proc"); @@ -353,12 +379,12 @@ cleanup: if (free_password) - free(password); + free(auth_opt->password); return rc; } static LDAP * -do_connect(char *server, char *user, char *password, +do_connect(char *server, bind_auth_options *auth_opt, int referrals, int starttls, int tls, int deref) { LDAP *ld = 0; @@ -382,7 +408,7 @@ if (starttls) if (ldap_start_tls_s(ld, 0, 0)) ldaperr(ld, "ldap_start_tls_s"); - if (rebind(ld, user, password, 1, 0) == -1) { + if (rebind(ld, auth_opt, 1, 0) == -1) { ldap_unbind_s(ld); return 0; } @@ -396,15 +422,23 @@ } static char * -login(LDAP *ld, char *user, char *password, int register_callback) +login(LDAP *ld, bind_auth_options *auth_opt, int register_callback) { char *dn; - if (!user) user = getline("Filter or DN: ")->str; - if (rebind(ld, user, password, register_callback, &dn) == 0) - printf("OK, bound as %s.\n", dn); + if (!auth_opt->user) { + if (auth_opt->authmethod == LDAP_AUTH_SASL) + auth_opt->sasl_mode = LDAP_SASL_INTERACTIVE; + else + auth_opt->user = getline("Filter or DN: ")->str; + } + + if (rebind(ld, auth_opt, register_callback, &dn) == 0) + if (auth_opt->authmethod == LDAP_AUTH_SIMPLE) + printf("OK, bound as %s.\n", dn); else - user = 0; - return user; + auth_opt->user = 0; + auth_opt->sasl_mode = LDAP_SASL_AUTOMATIC; + return auth_opt->user; } static int @@ -415,7 +449,7 @@ FILE *s; GString *name = g_string_sized_new(300); - g_string_append(name, ",ldapvi-"); + g_string_append(name, "ldapvi-"); if (gethostname(name->str + name->len, 300 - name->len) == -1) syserr(); name->len = strlen(name->str); @@ -898,6 +932,7 @@ { LDAP *ld; cmdline cmdline; + bind_auth_options auth_opts; GPtrArray *ctrls = g_ptr_array_new(); static char dir[] = "/tmp/ldapvi-XXXXXX"; char *clean; @@ -927,6 +962,16 @@ cmdline.discover = 0; cmdline.config = 0; + auth_opts.authmethod = LDAP_AUTH_SIMPLE; + auth_opts.sasl_mode = LDAP_SASL_AUTOMATIC; + auth_opts.user = 0; + auth_opts.password = 0; + auth_opts.sasl_authcid = 0; + auth_opts.sasl_authzid = 0; + auth_opts.sasl_mech = 0; + auth_opts.sasl_realm = 0; + auth_opts.sasl_secprops = 0; + if (argc >= 2 && !strcmp(argv[1], "--diff")) { if (argc != 4) { fputs("wrong number of arguments to --diff\n", stderr); @@ -936,12 +981,11 @@ exit(0); } - parse_arguments(argc, argv, &cmdline, ctrls); + parse_arguments(argc, argv, &cmdline, &auth_opts, ctrls); target_stream = fixup_streams(); ld = do_connect(cmdline.server, - cmdline.user, - cmdline.password, + &auth_opts, cmdline.referrals, cmdline.starttls, cmdline.tls, @@ -1034,15 +1078,17 @@ changed = 1; break; case 'b': - cmdline.user = login(ld, 0, 0, 1); + auth_opts.user = 0; + auth_opts.password = 0; + cmdline.user = login(ld, &auth_opts, 1); + auth_opts.user = cmdline.user; changed = 1; /* print stats again */ break; case 'r': ldap_unbind_s(ld); ld = do_connect( cmdline.server, - cmdline.user, - cmdline.password, + &auth_opts, cmdline.referrals, cmdline.starttls, cmdline.tls, diff -urN ldapvi-1.5/lutil_ldap.h lutil_ldap.h --- ldapvi-1.5/lutil_ldap.h 1969-12-31 16:00:00.000000000 -0800 +++ lutil_ldap.h 2005-12-22 11:06:48.000000000 -0800 @@ -0,0 +1,47 @@ +/* $OpenLDAP: pkg/ldap/include/lutil_ldap.h,v 1.9.2.1 2005/01/20 18:03:48 kurt Exp $ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2005 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#ifndef _LUTIL_LDAP_H +#define _LUTIL_LDAP_H 1 + +#include +#include + +/* + * Include file for lutil LDAP routines + */ + +LDAP_BEGIN_DECL + +LDAP_LUTIL_F( void ) +lutil_sasl_freedefs LDAP_P(( + void *defaults )); + +LDAP_LUTIL_F( void * ) +lutil_sasl_defaults LDAP_P(( + LDAP *ld, + char *mech, + char *realm, + char *authcid, + char *passwd, + char *authzid )); + +LDAP_LUTIL_F( int ) +lutil_sasl_interact LDAP_P(( + LDAP *ld, unsigned flags, void *defaults, void *p )); + +LDAP_END_DECL + +#endif /* _LUTIL_LDAP_H */ diff -urN ldapvi-1.5/sasl.c sasl.c --- ldapvi-1.5/sasl.c 1969-12-31 16:00:00.000000000 -0800 +++ sasl.c 2005-12-22 11:06:48.000000000 -0800 @@ -0,0 +1,230 @@ +/* $OpenLDAP: pkg/ldap/libraries/liblutil/sasl.c,v 1.20.2.1 2005/01/20 18:03:52 kurt Exp $ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2005 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +/* + * #include "portable.h" + */ + +#include +#include +#include +#include + +#include +#include +/* + * #include "ldap_pvt.h" + */ +#include "lutil_ldap.h" + + +typedef struct lutil_sasl_defaults_s { + char *mech; + char *realm; + char *authcid; + char *passwd; + char *authzid; + char **resps; + int nresps; +} lutilSASLdefaults; + + +void +lutil_sasl_freedefs( + void *defaults ) +{ + lutilSASLdefaults *defs = defaults; + + if (defs->mech) ber_memfree(defs->mech); + if (defs->realm) ber_memfree(defs->realm); + if (defs->authcid) ber_memfree(defs->authcid); + if (defs->passwd) ber_memfree(defs->passwd); + if (defs->authzid) ber_memfree(defs->authzid); + if (defs->resps) ldap_charray_free(defs->resps); + + ber_memfree(defs); +} + +void * +lutil_sasl_defaults( + LDAP *ld, + char *mech, + char *realm, + char *authcid, + char *passwd, + char *authzid ) +{ + lutilSASLdefaults *defaults; + + defaults = ber_memalloc( sizeof( lutilSASLdefaults ) ); + + if( defaults == NULL ) return NULL; + + defaults->mech = mech ? ber_strdup(mech) : NULL; + defaults->realm = realm ? ber_strdup(realm) : NULL; + defaults->authcid = authcid ? ber_strdup(authcid) : NULL; + defaults->passwd = passwd ? ber_strdup(passwd) : NULL; + defaults->authzid = authzid ? ber_strdup(authzid) : NULL; + + if( defaults->mech == NULL ) { + ldap_get_option( ld, LDAP_OPT_X_SASL_MECH, &defaults->mech ); + } + if( defaults->realm == NULL ) { + ldap_get_option( ld, LDAP_OPT_X_SASL_REALM, &defaults->realm ); + } + if( defaults->authcid == NULL ) { + ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHCID, &defaults->authcid ); + } + if( defaults->authzid == NULL ) { + ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHZID, &defaults->authzid ); + } + defaults->resps = NULL; + defaults->nresps = 0; + + return defaults; +} + +static int interaction( + unsigned flags, + sasl_interact_t *interact, + lutilSASLdefaults *defaults ) +{ + const char *dflt = interact->defresult; + char input[1024]; + + int noecho=0; + int challenge=0; + + switch( interact->id ) { + case SASL_CB_GETREALM: + if( defaults ) dflt = defaults->realm; + break; + case SASL_CB_AUTHNAME: + if( defaults ) dflt = defaults->authcid; + break; + case SASL_CB_PASS: + if( defaults ) dflt = defaults->passwd; + noecho = 1; + break; + case SASL_CB_USER: + if( defaults ) dflt = defaults->authzid; + break; + case SASL_CB_NOECHOPROMPT: + noecho = 1; + challenge = 1; + break; + case SASL_CB_ECHOPROMPT: + challenge = 1; + break; + } + + if( dflt && !*dflt ) dflt = NULL; + + if( flags != LDAP_SASL_INTERACTIVE && + ( dflt || interact->id == SASL_CB_USER ) ) + { + goto use_default; + } + + if( flags == LDAP_SASL_QUIET ) { + /* don't prompt */ + return LDAP_OTHER; + } + + if( challenge ) { + if( interact->challenge ) { + fprintf( stderr, "Challenge: %s\n", interact->challenge ); + } + } + + if( dflt ) { + fprintf( stderr, "Default: %s\n", dflt ); + } + + snprintf( input, sizeof input, "%s: ", + interact->prompt ? interact->prompt : "Interact" ); + + if( noecho ) { + /* + interact->result = (char *) getpassphrase( input ); + interact->len = interact->result + ? strlen( interact->result ) : 0; +*/ + + } else { + /* prompt user */ + fputs( input, stderr ); + + /* get input */ + interact->result = fgets( input, sizeof(input), stdin ); + + if( interact->result == NULL ) { + interact->len = 0; + return LDAP_UNAVAILABLE; + } + + /* len of input */ + interact->len = strlen(input); + + if( interact->len > 0 && input[interact->len - 1] == '\n' ) { + /* input includes '\n', trim it */ + interact->len--; + input[interact->len] = '\0'; + } + } + + + if( interact->len > 0 ) { + /* duplicate */ + char *p = (char *)interact->result; + ldap_charray_add(&defaults->resps, interact->result); + interact->result = defaults->resps[defaults->nresps++]; + + /* zap */ + memset( p, '\0', interact->len ); + + } else { +use_default: + /* input must be empty */ + interact->result = (dflt && *dflt) ? dflt : ""; + interact->len = strlen( interact->result ); + } + + return LDAP_SUCCESS; +} + +int lutil_sasl_interact( + LDAP *ld, + unsigned flags, + void *defaults, + void *in ) +{ + sasl_interact_t *interact = in; + + if( ld == NULL ) return LDAP_PARAM_ERROR; + + if( flags == LDAP_SASL_INTERACTIVE ) { + fputs( "SASL Interaction\n", stderr ); + } + + while( interact->id != SASL_CB_LIST_END ) { + int rc = interaction( flags, interact, defaults ); + + if( rc ) return rc; + interact++; + } + + return LDAP_SUCCESS; +}