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 <signal.h>
 #include <term.h>
 #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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _LUTIL_LDAP_H
+#define _LUTIL_LDAP_H 1
+
+#include <ldap_cdefs.h>
+#include <lber_types.h>
+
+/*
+ * 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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/*
+ * #include "portable.h"
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sasl/sasl.h>
+#include <ldap.h>
+/*
+ * #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;
+}
