OpenSolaris_b135/lib/libsasl/lib/client.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

/* SASL server API implementation
 * Rob Siemborski
 * Tim Martin
 * $Id: client.c,v 1.61 2003/04/16 19:36:00 rjs3 Exp $
 */
/* 
 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any other legal
 *    details, please contact  
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

/* SASL Headers */
#include "sasl.h"
#include "saslplug.h"
#include "saslutil.h"
#include "saslint.h"

#ifdef _SUN_SDK_
DEFINE_STATIC_MUTEX(init_client_mutex);
DEFINE_STATIC_MUTEX(client_active_mutex);
/*
 * client_plug_mutex ensures only one client plugin is init'ed at a time
 * If a plugin is loaded more than once, the glob_context may be overwritten
 * which may lead to a memory leak. We keep glob_context with each mech
 * to avoid this problem.
 */
DEFINE_STATIC_MUTEX(client_plug_mutex);
#else
static cmech_list_t *cmechlist; /* global var which holds the list */

static sasl_global_callbacks_t global_callbacks;

static int _sasl_client_active = 0;
#endif /* _SUN_SDK_ */

#ifdef _SUN_SDK_
static int init_mechlist(_sasl_global_context_t *gctx)
{
  cmech_list_t *cmechlist = gctx->cmechlist;
#else
static int init_mechlist()
{
#endif /* _SUN_SDK_ */

  cmechlist->mutex = sasl_MUTEX_ALLOC();
  if(!cmechlist->mutex) return SASL_FAIL;
  
#ifdef _SUN_SDK_
  cmechlist->utils=
	_sasl_alloc_utils(gctx, NULL, &gctx->client_global_callbacks);
#else
  cmechlist->utils=_sasl_alloc_utils(NULL, &global_callbacks);
#endif /* _SUN_SDK_ */
  if (cmechlist->utils==NULL)
    return SASL_NOMEM;

  cmechlist->mech_list=NULL;
  cmechlist->mech_length=0;

  return SASL_OK;
}

#ifdef _SUN_SDK_
static int client_done(_sasl_global_context_t *gctx) {
  cmech_list_t *cmechlist = gctx->cmechlist;
  _sasl_path_info_t *path_info, *p;
#else
static int client_done(void) {
#endif /* _SUN_SDK_ */
  cmechanism_t *cm;
  cmechanism_t *cprevm;

#ifdef _SUN_SDK_
  if(!gctx->sasl_client_active)
      return SASL_NOTINIT;
  if (LOCK_MUTEX(&client_active_mutex) < 0) {
	return (SASL_FAIL);
  }
  gctx->sasl_client_active--;

  if(gctx->sasl_client_active) {
      /* Don't de-init yet! Our refcount is nonzero. */
      UNLOCK_MUTEX(&client_active_mutex);
      return SASL_CONTINUE;
  }
#else
  if(!_sasl_client_active)
      return SASL_NOTINIT;
  else
      _sasl_client_active--;
  
  if(_sasl_client_active) {
      /* Don't de-init yet! Our refcount is nonzero. */
      return SASL_CONTINUE;
  }
#endif /* _SUN_SDK_ */
  
  cm=cmechlist->mech_list; /* m point to begging of the list */
  while (cm!=NULL)
  {
    cprevm=cm;
    cm=cm->next;

    if (cprevm->plug->mech_free) {
#ifdef _SUN_SDK_
	cprevm->plug->mech_free(cprevm->glob_context, cmechlist->utils);
#else
	cprevm->plug->mech_free(cprevm->plug->glob_context,
				cmechlist->utils);
#endif /* _SUN_SDK_ */
    }

    sasl_FREE(cprevm->plugname);
    sasl_FREE(cprevm);    
  }
  sasl_MUTEX_FREE(cmechlist->mutex);
  _sasl_free_utils(&cmechlist->utils);
  sasl_FREE(cmechlist);

#ifdef _SUN_SDK_
  gctx->cmechlist = NULL;
  p = gctx->cplug_path_info;
  while((path_info = p) != NULL) {
    sasl_FREE(path_info->path);
    p = path_info->next;
    sasl_FREE(path_info);
  }
  gctx->cplug_path_info = NULL;
  UNLOCK_MUTEX(&client_active_mutex);
#else
  cmechlist = NULL;
#endif /* _SUN_SDK_ */

  return SASL_OK;
}

int sasl_client_add_plugin(const char *plugname,
			   sasl_client_plug_init_t *entry_point)
{
#ifdef _SUN_SDK_
    return (_sasl_client_add_plugin(_sasl_gbl_ctx(), plugname, entry_point));
}

int _sasl_client_add_plugin(void *ctx,
                            const char *plugname,
                            sasl_client_plug_init_t *entry_point)
{
  cmech_list_t *cmechlist;
#ifdef _INTEGRATED_SOLARIS_
  _sasl_global_context_t *gctx = ctx == NULL ? _sasl_gbl_ctx() : ctx;
  /* EXPORT DELETE START */
  /* CRYPT DELETE START */
  int sun_reg;
  /* CRYPT DELETE END */
  /* EXPORT DELETE END */
#endif /* _INTEGRATED_SOLARIS_ */
  int i;
  cmechanism_t *m;
#endif /* _SUN_SDK_ */
  int plugcount;
  sasl_client_plug_t *pluglist;
  cmechanism_t *mech;
  int result;
  int version;
  int lupe;

  if(!plugname || !entry_point) return SASL_BADPARAM;
  
#ifdef _SUN_SDK_
  cmechlist = gctx->cmechlist;

  if (cmechlist == NULL) return SASL_BADPARAM;

  /* Check to see if this plugin has already been registered */
  m = cmechlist->mech_list;
  for (i = 0; i < cmechlist->mech_length; i++) {
    if (strcmp(plugname, m->plugname) == 0) {
	return SASL_OK;
    }
    m = m->next;
  }

  result = LOCK_MUTEX(&client_plug_mutex);
  if (result != SASL_OK)
	return result;

#endif /* _SUN_SDK_ */

  result = entry_point(cmechlist->utils, SASL_CLIENT_PLUG_VERSION, &version,
		       &pluglist, &plugcount);

  /* EXPORT DELETE START */
  /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
  sun_reg = _is_sun_reg(pluglist);
#endif /* _INTEGRATED_SOLARIS_ */
  /* CRYPT DELETE END */
  /* EXPORT DELETE END */
  if (result != SASL_OK)
  {
#ifdef _SUN_SDK_
    UNLOCK_MUTEX(&client_plug_mutex);
    __sasl_log(gctx, gctx->client_global_callbacks.callbacks, SASL_LOG_WARN,
	      "entry_point failed in sasl_client_add_plugin for %s",
	      plugname);
#else
    _sasl_log(NULL, SASL_LOG_WARN,
	      "entry_point failed in sasl_client_add_plugin for %s",
	      plugname);
#endif /* _SUN_SDK_ */
    return result;
  }

  if (version != SASL_CLIENT_PLUG_VERSION)
  {
#ifdef _SUN_SDK_
    UNLOCK_MUTEX(&client_plug_mutex);
    __sasl_log(gctx, gctx->client_global_callbacks.callbacks, SASL_LOG_WARN,
	      "version conflict in sasl_client_add_plugin for %s", plugname);
#else
    _sasl_log(NULL, SASL_LOG_WARN,
	      "version conflict in sasl_client_add_plugin for %s", plugname);
#endif /* _SUN_SDK_ */
    return SASL_BADVERS;
  }

#ifdef _SUN_SDK_
    /* Check plugins to make sure mech_name is non-NULL */
    for (lupe=0;lupe < plugcount ;lupe++) {
	if (pluglist[lupe].mech_name == NULL)
	     break;
    }
    if (lupe < plugcount) {
	UNLOCK_MUTEX(&client_plug_mutex);
	__sasl_log(gctx, gctx->client_global_callbacks.callbacks,
		SASL_LOG_ERR, "invalid client plugin %s", plugname);
	return SASL_BADPROT;
    }
#endif /* _SUN_SDK_ */

  for (lupe=0;lupe< plugcount ;lupe++)
    {
      mech = sasl_ALLOC(sizeof(cmechanism_t));
#ifdef _SUN_SDK_
      if (! mech) {
	UNLOCK_MUTEX(&client_plug_mutex);
	return SASL_NOMEM;
      }
      mech->glob_context = pluglist->glob_context;
#else
      if (! mech) return SASL_NOMEM;
#endif /* _SUN_SDK_ */

      mech->plug=pluglist++;
      if(_sasl_strdup(plugname, &mech->plugname, NULL) != SASL_OK) {
#ifdef _SUN_SDK_
	UNLOCK_MUTEX(&client_plug_mutex);
#endif /* _SUN_SDK_ */
	sasl_FREE(mech);
	return SASL_NOMEM;
      }
      /* EXPORT DELETE START */
      /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
      mech->sun_reg = sun_reg;
#endif /* _INTEGRATED_SOLARIS_ */
     /* CRYPT DELETE END */
     /* EXPORT DELETE END */
      mech->version = version;
      mech->next = cmechlist->mech_list;
      cmechlist->mech_list = mech;
      cmechlist->mech_length++;
    }
#ifdef _SUN_SDK_
    UNLOCK_MUTEX(&client_plug_mutex);
#endif /* _SUN_SDK_ */

  return SASL_OK;
}

static int
client_idle(sasl_conn_t *conn)
{
  cmechanism_t *m;
#ifdef _SUN_SDK_
  _sasl_global_context_t *gctx = conn == NULL ? _sasl_gbl_ctx() : conn->gctx;
   cmech_list_t *cmechlist = gctx->cmechlist;
#endif /* _SUN_SDK_ */

  if (! cmechlist)
    return 0;

  for (m = cmechlist->mech_list;
       m;
       m = m->next)
    if (m->plug->idle
#ifdef _SUN_SDK_
	&&  m->plug->idle(m->glob_context,
#else
	&&  m->plug->idle(m->plug->glob_context,
#endif /* _SUN_SDK_ */
			  conn,
			  conn ? ((sasl_client_conn_t *)conn)->cparams : NULL))
      return 1;
  return 0;
}

#ifdef _SUN_SDK_
static int _load_client_plugins(_sasl_global_context_t *gctx)
{
    int ret;
    const add_plugin_list_t _ep_list[] = {
      { "sasl_client_plug_init", (add_plugin_t *)_sasl_client_add_plugin },
      { "sasl_canonuser_init", (add_plugin_t *)_sasl_canonuser_add_plugin },
      { NULL, NULL }
    };
    const sasl_callback_t *callbacks = gctx->client_global_callbacks.callbacks;

    ret = _sasl_load_plugins(gctx, 0, _ep_list,
			     _sasl_find_getpath_callback(callbacks),
			     _sasl_find_verifyfile_callback(callbacks));
    return (ret);
}
#endif /* _SUN_SDK_ */

/* initialize the SASL client drivers
 *  callbacks      -- base callbacks for all client connections
 * returns:
 *  SASL_OK        -- Success
 *  SASL_NOMEM     -- Not enough memory
 *  SASL_BADVERS   -- Mechanism version mismatch
 *  SASL_BADPARAM  -- error in config file
 *  SASL_NOMECH    -- No mechanisms available
 *  ...
 */

int sasl_client_init(const sasl_callback_t *callbacks)
{
#ifdef _SUN_SDK_
	return _sasl_client_init(NULL, callbacks);
}

int _sasl_client_init(void *ctx,
		      const sasl_callback_t *callbacks)
{
  int ret;
  _sasl_global_context_t *gctx = ctx == NULL ? _sasl_gbl_ctx() : ctx;

  if (gctx == NULL)
	gctx = _sasl_gbl_ctx();

  ret = LOCK_MUTEX(&init_client_mutex);
  if (ret < 0) {
	return (SASL_FAIL);
  }
  ret = LOCK_MUTEX(&client_active_mutex);
  if (ret < 0) {
	UNLOCK_MUTEX(&init_client_mutex);
	return (SASL_FAIL);
  }
  if(gctx->sasl_client_active) {
      /* We're already active, just increase our refcount */
      /* xxx do something with the callback structure? */
      gctx->sasl_client_active++;
      UNLOCK_MUTEX(&client_active_mutex);
      UNLOCK_MUTEX(&init_client_mutex);
      return SASL_OK;
  }

  gctx->client_global_callbacks.callbacks = callbacks;
  gctx->client_global_callbacks.appname = NULL;

  gctx->cmechlist=sasl_ALLOC(sizeof(cmech_list_t));
  if (gctx->cmechlist==NULL) {
      UNLOCK_MUTEX(&init_client_mutex);
      UNLOCK_MUTEX(&client_active_mutex);
      return SASL_NOMEM;
  }

  gctx->sasl_client_active = 1;
  UNLOCK_MUTEX(&client_active_mutex);

  /* load plugins */
  ret=init_mechlist(gctx);

  if (ret!=SASL_OK) {
    client_done(gctx);
    UNLOCK_MUTEX(&init_client_mutex);
    return ret;
  }
  _sasl_client_add_plugin(gctx, "EXTERNAL", &external_client_plug_init);

  ret = _sasl_common_init(gctx, &gctx->client_global_callbacks, 0);
#else
int sasl_client_init(const sasl_callback_t *callbacks)
{
  int ret;
  const add_plugin_list_t ep_list[] = {
      { "sasl_client_plug_init", (add_plugin_t *)sasl_client_add_plugin },
      { "sasl_canonuser_init", (add_plugin_t *)sasl_canonuser_add_plugin },
      { NULL, NULL }
  };

  if(_sasl_client_active) {
      /* We're already active, just increase our refcount */
      /* xxx do something with the callback structure? */
      _sasl_client_active++;
      return SASL_OK;
  }

  global_callbacks.callbacks = callbacks;
  global_callbacks.appname = NULL;

  cmechlist=sasl_ALLOC(sizeof(cmech_list_t));
  if (cmechlist==NULL) return SASL_NOMEM;

  /* We need to call client_done if we fail now */
  _sasl_client_active = 1;

  /* load plugins */
  ret=init_mechlist();  
  if (ret!=SASL_OK) {
      client_done();
      return ret;
  }

  sasl_client_add_plugin("EXTERNAL", &external_client_plug_init);

  ret = _sasl_common_init(&global_callbacks);
#endif /* _SUN_SDK_ */

  if (ret == SASL_OK)
#ifdef _SUN_SDK_
      ret = _load_client_plugins(gctx);
#else
      ret = _sasl_load_plugins(ep_list,
			       _sasl_find_getpath_callback(callbacks),
			       _sasl_find_verifyfile_callback(callbacks));
#endif /* _SUN_SDK_ */
  
#ifdef _SUN_SDK_
  if (ret == SASL_OK)
	/* If sasl_client_init returns error, sasl_done() need not be called */
      ret = _sasl_build_mechlist(gctx);
  if (ret == SASL_OK) {
      gctx->sasl_client_cleanup_hook = &client_done;
      gctx->sasl_client_idle_hook = &client_idle;
  } else {
      client_done(gctx);
  }
  UNLOCK_MUTEX(&init_client_mutex);
#else
  if (ret == SASL_OK) {
      _sasl_client_cleanup_hook = &client_done;
      _sasl_client_idle_hook = &client_idle;

      ret = _sasl_build_mechlist();
  } else {
      client_done();
  }
#endif /* _SUN_SDK_ */
      
  return ret;
}

static void client_dispose(sasl_conn_t *pconn)
{
  sasl_client_conn_t *c_conn=(sasl_client_conn_t *) pconn;
#ifdef _SUN_SDK_
  sasl_free_t *free_func = c_conn->cparams->utils->free;
#endif /* _SUN_SDK_ */

  if (c_conn->mech && c_conn->mech->plug->mech_dispose) {
    c_conn->mech->plug->mech_dispose(pconn->context,
				     c_conn->cparams->utils);
  }

  pconn->context = NULL;

  if (c_conn->clientFQDN)
#ifdef _SUN_SDK_
      free_func(c_conn->clientFQDN);
#else
      sasl_FREE(c_conn->clientFQDN);
#endif /* _SUN_SDK_ */

  if (c_conn->cparams) {
      _sasl_free_utils(&(c_conn->cparams->utils));
#ifdef _SUN_SDK_
      free_func(c_conn->cparams);
#else
      sasl_FREE(c_conn->cparams);
#endif /* _SUN_SDK_ */
  }

  _sasl_conn_dispose(pconn);
}

/* initialize a client exchange based on the specified mechanism
 *  service       -- registered name of the service using SASL (e.g. "imap")
 *  serverFQDN    -- the fully qualified domain name of the server
 *  iplocalport   -- client IPv4/IPv6 domain literal string with port
 *                    (if NULL, then mechanisms requiring IPaddr are disabled)
 *  ipremoteport  -- server IPv4/IPv6 domain literal string with port
 *                    (if NULL, then mechanisms requiring IPaddr are disabled)
 *  prompt_supp   -- list of client interactions supported
 *                   may also include sasl_getopt_t context & call
 *                   NULL prompt_supp = user/pass via SASL_INTERACT only
 *                   NULL proc = interaction supported via SASL_INTERACT
 *  secflags      -- security flags (see above)
 * in/out:
 *  pconn         -- connection negotiation structure
 *                   pointer to NULL => allocate new
 *                   non-NULL => recycle storage and go for next available mech
 *
 * Returns:
 *  SASL_OK       -- success
 *  SASL_NOMECH   -- no mechanism meets requested properties
 *  SASL_NOMEM    -- not enough memory
 */
int sasl_client_new(const char *service,
		    const char *serverFQDN,
		    const char *iplocalport,
		    const char *ipremoteport,
		    const sasl_callback_t *prompt_supp,
		    unsigned flags,
		    sasl_conn_t **pconn)
{
#ifdef _SUN_SDK_
    return _sasl_client_new(NULL, service, serverFQDN, iplocalport,
			    ipremoteport, prompt_supp, flags, pconn);
}
int _sasl_client_new(void *ctx,
		     const char *service,
		     const char *serverFQDN,
		     const char *iplocalport,
		     const char *ipremoteport,
		     const sasl_callback_t *prompt_supp,
		     unsigned flags,
		     sasl_conn_t **pconn)
{
  _sasl_global_context_t *gctx = ctx == NULL ? _sasl_gbl_ctx() : ctx;
#endif /* _SUN_SDK_ */
  int result;
  char name[MAXHOSTNAMELEN];
  sasl_client_conn_t *conn;
  sasl_utils_t *utils;

#ifdef _SUN_SDK_
  if (gctx == NULL)
	gctx = _sasl_gbl_ctx();

  if(gctx->sasl_client_active==0) return SASL_NOTINIT;
#else
  if(_sasl_client_active==0) return SASL_NOTINIT;
#endif /* _SUN_SDK_ */
  
  /* Remember, iplocalport and ipremoteport can be NULL and be valid! */
  if (!pconn || !service || !serverFQDN)
    return SASL_BADPARAM;

  *pconn=sasl_ALLOC(sizeof(sasl_client_conn_t));
  if (*pconn==NULL) {
#ifdef _SUN_SDK_
      __sasl_log(gctx, gctx->client_global_callbacks.callbacks, SASL_LOG_ERR,
		"Out of memory allocating connection context");
#else
      _sasl_log(NULL, SASL_LOG_ERR,
		"Out of memory allocating connection context");
#endif /* _SUN_SDK_ */
      return SASL_NOMEM;
  }
  memset(*pconn, 0, sizeof(sasl_client_conn_t));

#ifdef _SUN_SDK_
  (*pconn)->gctx = gctx;
#endif /* _SUN_SDK_ */

  (*pconn)->destroy_conn = &client_dispose;

  conn = (sasl_client_conn_t *)*pconn;
  
  conn->mech = NULL;

  conn->cparams=sasl_ALLOC(sizeof(sasl_client_params_t));
  if (conn->cparams==NULL) 
      MEMERROR(*pconn);
  memset(conn->cparams,0,sizeof(sasl_client_params_t));

  result = _sasl_conn_init(*pconn, service, flags, SASL_CONN_CLIENT,
			   &client_idle, serverFQDN,
			   iplocalport, ipremoteport,
#ifdef _SUN_SDK_
			   prompt_supp, &gctx->client_global_callbacks);
#else
			   prompt_supp, &global_callbacks);
#endif /* _SUN_SDK_ */

  if (result != SASL_OK) RETURN(*pconn, result);
  
#ifdef _SUN_SDK_
  utils=_sasl_alloc_utils(gctx, *pconn, &gctx->client_global_callbacks);
#else
  utils=_sasl_alloc_utils(*pconn, &global_callbacks);
#endif /* _SUN_SDK_ */
  if (utils==NULL)
      MEMERROR(*pconn);
  
  utils->conn= *pconn;

  /* Setup the non-lazy parts of cparams, the rest is done in
   * sasl_client_start */
  conn->cparams->utils = utils;
  conn->cparams->canon_user = &_sasl_canon_user;
  conn->cparams->flags = flags;
  conn->cparams->prompt_supp = (*pconn)->callbacks;
  
  /* get the clientFQDN (serverFQDN was set in _sasl_conn_init) */
  memset(name, 0, sizeof(name));
  gethostname(name, MAXHOSTNAMELEN);

  result = _sasl_strdup(name, &conn->clientFQDN, NULL);

  if(result == SASL_OK) return SASL_OK;

#ifdef _SUN_SDK_
  conn->cparams->iplocalport = (*pconn)->iplocalport;
  conn->cparams->iploclen = strlen((*pconn)->iplocalport);
  conn->cparams->ipremoteport = (*pconn)->ipremoteport;
  conn->cparams->ipremlen = strlen((*pconn)->ipremoteport);
#endif /* _SUN_SDK_ */

  /* result isn't SASL_OK */
  _sasl_conn_dispose(*pconn);
  sasl_FREE(*pconn);
  *pconn = NULL;
#ifdef _SUN_SDK_
  __sasl_log(gctx, gctx->client_global_callbacks.callbacks, SASL_LOG_ERR,
	"Out of memory in sasl_client_new");
#else
  _sasl_log(NULL, SASL_LOG_ERR, "Out of memory in sasl_client_new");
#endif /* _SUN_SDK_ */
  return result;
}

static int have_prompts(sasl_conn_t *conn,
			const sasl_client_plug_t *mech)
{
  static const unsigned long default_prompts[] = {
    SASL_CB_AUTHNAME,
    SASL_CB_PASS,
    SASL_CB_LIST_END
  };

  const unsigned long *prompt;
  int (*pproc)();
  void *pcontext;
  int result;

  for (prompt = (mech->required_prompts
		 ? mech->required_prompts :
		 default_prompts);
       *prompt != SASL_CB_LIST_END;
       prompt++) {
    result = _sasl_getcallback(conn, *prompt, &pproc, &pcontext);
    if (result != SASL_OK && result != SASL_INTERACT)
      return 0;			/* we don't have this required prompt */
  }

  return 1; /* we have all the prompts */
}

/* select a mechanism for a connection
 *  mechlist      -- mechanisms server has available (punctuation ignored)
 *  secret        -- optional secret from previous session
 * output:
 *  prompt_need   -- on SASL_INTERACT, list of prompts needed to continue
 *  clientout     -- the initial client response to send to the server
 *  mech          -- set to mechanism name
 *
 * Returns:
 *  SASL_OK       -- success
 *  SASL_NOMEM    -- not enough memory
 *  SASL_NOMECH   -- no mechanism meets requested properties
 *  SASL_INTERACT -- user interaction needed to fill in prompt_need list
 */

/* xxx confirm this with rfc 2222
 * SASL mechanism allowable characters are "AZaz-_"
 * seperators can be any other characters and of any length
 * even variable lengths between
 *
 * Apps should be encouraged to simply use space or comma space
 * though
 */
int sasl_client_start(sasl_conn_t *conn,
		      const char *mechlist,
		      sasl_interact_t **prompt_need,
		      const char **clientout,
		      unsigned *clientoutlen,
		      const char **mech)
{
    sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
    char name[SASL_MECHNAMEMAX + 1];
    cmechanism_t *m=NULL,*bestm=NULL;
    size_t pos=0,place;
    size_t list_len;
    sasl_ssf_t bestssf = 0, minssf = 0;
    int result;
#ifdef _SUN_SDK_
    _sasl_global_context_t *gctx = (conn == NULL) ?
		_sasl_gbl_ctx() : conn->gctx;
    cmech_list_t *cmechlist;

    if(gctx->sasl_client_active==0) return SASL_NOTINIT;
    cmechlist = gctx->cmechlist;
#else
    if(_sasl_client_active==0) return SASL_NOTINIT;
#endif /* _SUN_SDK_ */

    if (!conn) return SASL_BADPARAM;

    /* verify parameters */
    if (mechlist == NULL)
	PARAMERROR(conn);

    /* if prompt_need != NULL we've already been here
       and just need to do the continue step again */

    /* do a step */
    /* FIXME: Hopefully they only give us our own prompt_need back */
    if (prompt_need && *prompt_need != NULL) {
	goto dostep;
    }

#ifdef _SUN_SDK_
    if (c_conn->mech != NULL) {
	if (c_conn->mech->plug->mech_dispose != NULL) {
	    c_conn->mech->plug->mech_dispose(conn->context,
		c_conn->cparams->utils);
	    c_conn->mech = NULL;
	}
    }
    memset(&conn->oparams, 0, sizeof(sasl_out_params_t));

    (void) _load_client_plugins(gctx);
#endif /* _SUN_SDK_ */

    if(conn->props.min_ssf < conn->external.ssf) {
	minssf = 0;
    } else {
	minssf = conn->props.min_ssf - conn->external.ssf;
    }

    /* parse mechlist */
    list_len = strlen(mechlist);

    while (pos<list_len)
    {
	place=0;
	while ((pos<list_len) && (isalnum((unsigned char)mechlist[pos])
				  || mechlist[pos] == '_'
				  || mechlist[pos] == '-')) {
	    name[place]=mechlist[pos];
	    pos++;
	    place++;
	    if (SASL_MECHNAMEMAX < place) {
		place--;
		while(pos<list_len && (isalnum((unsigned char)mechlist[pos])
				       || mechlist[pos] == '_'
				       || mechlist[pos] == '-'))
		    pos++;
	    }
	}
	pos++;
	name[place]=0;

	if (! place) continue;

	/* foreach in server list */
	for (m = cmechlist->mech_list; m != NULL; m = m->next) {
	    int myflags;
	    
	    /* Is this the mechanism the server is suggesting? */
	    if (strcasecmp(m->plug->mech_name, name))
		continue; /* no */

	    /* Do we have the prompts for it? */
	    if (!have_prompts(conn, m->plug))
		break;

	    /* Is it strong enough? */
	    if (minssf > m->plug->max_ssf)
		break;

	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
	    /* If not SUN supplied mech, it has no strength */
	    if (minssf > 0 && !m->sun_reg)
		break;
#endif /* _INTEGRATED_SOLARIS_ */
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */

	    /* Does it meet our security properties? */
	    myflags = conn->props.security_flags;
	    
	    /* if there's an external layer this is no longer plaintext */
	    if ((conn->props.min_ssf <= conn->external.ssf) && 
		(conn->external.ssf > 1)) {
		myflags &= ~SASL_SEC_NOPLAINTEXT;
	    }

	    if (((myflags ^ m->plug->security_flags) & myflags) != 0) {
		break;
	    }

	    /* Can we meet it's features? */
	    if ((m->plug->features & SASL_FEAT_NEEDSERVERFQDN)
		&& !conn->serverFQDN) {
		break;
	    }

	    /* Can it meet our features? */
	    if ((conn->flags & SASL_NEED_PROXY) &&
		!(m->plug->features & SASL_FEAT_ALLOWS_PROXY)) {
		break;
	    }
	    
#ifdef PREFER_MECH
	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
	    if (strcasecmp(m->plug->mech_name, PREFER_MECH) &&
		bestm && (m->sun_reg && m->plug->max_ssf <= bestssf) ||
		(m->plug->max_ssf == 0)) {
#else
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */
	    if (strcasecmp(m->plug->mech_name, PREFER_MECH) &&
		bestm && m->plug->max_ssf <= bestssf) {

		/* EXPORT DELETE START */
		/* CRYPT DELETE START */
#endif /* _INTEGRATED_SOLARIS_ */
		/* CRYPT DELETE END */
		/* EXPORT DELETE END */

		/* this mechanism isn't our favorite, and it's no better
		   than what we already have! */
		break;
	    }
#else
	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
	    if (bestm && m->sun_reg && m->plug->max_ssf <= bestssf) {
#else
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */

	    if (bestm && m->plug->max_ssf <= bestssf) {
	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#endif /* _INTEGRATED_SOLARIS_ */
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */

		/* this mechanism is no better than what we already have! */
		break;
	    }
#endif

	    /* compare security flags, only take new mechanism if it has
	     * all the security flags of the previous one.
	     *
	     * From the mechanisms we ship with, this yields the order:
	     *
	     * SRP
	     * GSSAPI + KERBEROS_V4
	     * DIGEST + OTP
	     * CRAM + EXTERNAL
	     * PLAIN + LOGIN + ANONYMOUS
	     *
	     * This might be improved on by comparing the numeric value of
	     * the bitwise-or'd security flags, which splits DIGEST/OTP,
	     * CRAM/EXTERNAL, and PLAIN/LOGIN from ANONYMOUS, but then we
	     * are depending on the numeric values of the flags (which may
	     * change, and their ordering could be considered dumb luck.
	     */

	    if (bestm &&
		((m->plug->security_flags ^ bestm->plug->security_flags) &
		 bestm->plug->security_flags)) {
		break;
	    }

	    if (mech) {
		*mech = m->plug->mech_name;
	    }
	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
	    bestssf = m->sun_reg ? m->plug->max_ssf : 0;
#else
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */
	    bestssf = m->plug->max_ssf;
	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#endif /* _INTEGRATED_SOLARIS_ */
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */
	    bestm = m;
	    break;
	}
    }

    if (bestm == NULL) {
#ifdef _INTEGRATED_SOLARIS_
	sasl_seterror(conn, 0, gettext("No worthy mechs found"));
#else
	sasl_seterror(conn, 0, "No worthy mechs found");
#endif /* _INTEGRATED_SOLARIS_ */
	result = SASL_NOMECH;
	goto done;
    }

    /* make (the rest of) cparams */
    c_conn->cparams->service = conn->service;
    c_conn->cparams->servicelen = strlen(conn->service);
    
    c_conn->cparams->serverFQDN = conn->serverFQDN; 
    c_conn->cparams->slen = strlen(conn->serverFQDN);

    c_conn->cparams->clientFQDN = c_conn->clientFQDN; 
    c_conn->cparams->clen = strlen(c_conn->clientFQDN);

    c_conn->cparams->external_ssf = conn->external.ssf;
    c_conn->cparams->props = conn->props;
    /* EXPORT DELETE START */
    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
    if (!bestm->sun_reg) {
	c_conn->cparams->props.min_ssf = 0;
	c_conn->cparams->props.max_ssf = 0;
    }
    c_conn->base.sun_reg = bestm->sun_reg;
#endif /* _INTEGRATED_SOLARIS_ */
    /* CRYPT DELETE END */
    /* EXPORT DELETE END */
    c_conn->mech = bestm;

    /* init that plugin */
#ifdef _SUN_SDK_
    result = c_conn->mech->plug->mech_new(c_conn->mech->glob_context,
#else
    result = c_conn->mech->plug->mech_new(c_conn->mech->plug->glob_context,
#endif /* _SUN_SDK_ */
					  c_conn->cparams,
					  &(conn->context));
    if(result != SASL_OK) goto done;

    /* do a step -- but only if we can do a client-send-first */
 dostep:
    if(clientout) {
        if(c_conn->mech->plug->features & SASL_FEAT_SERVER_FIRST) {
            *clientout = NULL;
            *clientoutlen = 0;
            result = SASL_CONTINUE;
        } else {
            result = sasl_client_step(conn, NULL, 0, prompt_need,
                                      clientout, clientoutlen);
        }
    }
    else
	result = SASL_CONTINUE;

 done:
    RETURN(conn, result);
}

/* do a single authentication step.
 *  serverin    -- the server message received by the client, MUST have a NUL
 *                 sentinel, not counted by serverinlen
 * output:
 *  prompt_need -- on SASL_INTERACT, list of prompts needed to continue
 *  clientout   -- the client response to send to the server
 *
 * returns:
 *  SASL_OK        -- success
 *  SASL_INTERACT  -- user interaction needed to fill in prompt_need list
 *  SASL_BADPROT   -- server protocol incorrect/cancelled
 *  SASL_BADSERV   -- server failed mutual auth
 */

int sasl_client_step(sasl_conn_t *conn,
		     const char *serverin,
		     unsigned serverinlen,
		     sasl_interact_t **prompt_need,
		     const char **clientout,
		     unsigned *clientoutlen)
{
  sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
  int result;

#ifdef _SUN_SDK_
  _sasl_global_context_t *gctx = (conn == NULL) ?
		_sasl_gbl_ctx() : conn->gctx;

  if(gctx->sasl_client_active==0) return SASL_NOTINIT;
#else
  if(_sasl_client_active==0) return SASL_NOTINIT;
#endif	/* _SUN_SDK_ */
  if(!conn) return SASL_BADPARAM;

  /* check parameters */
  if ((serverin==NULL) && (serverinlen>0))
      PARAMERROR(conn);

  /* Don't do another step if the plugin told us that we're done */
  if (conn->oparams.doneflag) {
      _sasl_log(conn, SASL_LOG_ERR, "attempting client step after doneflag");
      return SASL_FAIL;
  }

  if(clientout) *clientout = NULL;
  if(clientoutlen) *clientoutlen = 0;

  /* do a step */
  result = c_conn->mech->plug->mech_step(conn->context,
					 c_conn->cparams,
					 serverin,
					 serverinlen,
					 prompt_need,
					 clientout, clientoutlen,
					 &conn->oparams);

  if (result == SASL_OK) {
      /* So we're done on this end, but if both
       * 1. the mech does server-send-last
       * 2. the protocol does not
       * we need to return no data */
      if(!*clientout && !(conn->flags & SASL_SUCCESS_DATA)) {
	  *clientout = "";
	  *clientoutlen = 0;
      }
      
      if(!conn->oparams.maxoutbuf) {
	  conn->oparams.maxoutbuf = conn->props.maxbufsize;
      }

      if(conn->oparams.user == NULL || conn->oparams.authid == NULL) {
#ifdef _SUN_SDK_
	_sasl_log(conn, SASL_LOG_ERR,
		  "mech did not call canon_user for both authzid and authid");
#else
	  sasl_seterror(conn, 0,
			"mech did not call canon_user for both authzid and authid");
#endif /* _SUN_SDK_ */
	  result = SASL_BADPROT;
      }
  }  

  RETURN(conn,result);
}

/* returns the length of all the mechanisms
 * added up 
 */

#ifdef _SUN_SDK_
static unsigned mech_names_len(_sasl_global_context_t *gctx)
{
  cmech_list_t *cmechlist = gctx->cmechlist;
#else
static unsigned mech_names_len()
{
#endif /* _SUN_SDK_ */
  cmechanism_t *listptr;
  unsigned result = 0;

  for (listptr = cmechlist->mech_list;
       listptr;
       listptr = listptr->next)
    result += strlen(listptr->plug->mech_name);

  return result;
}


int _sasl_client_listmech(sasl_conn_t *conn,
			  const char *prefix,
			  const char *sep,
			  const char *suffix,
			  const char **result,
			  unsigned *plen,
			  int *pcount)
{
    cmechanism_t *m=NULL;
    sasl_ssf_t minssf = 0;
    int ret;
    unsigned int resultlen;
    int flag;
    const char *mysep;
#ifdef _SUN_SDK_
    _sasl_global_context_t *gctx = conn == NULL ? _sasl_gbl_ctx() : conn->gctx;
    cmech_list_t *cmechlist;

    if(gctx->sasl_client_active==0) return SASL_NOTINIT;
    cmechlist = gctx->cmechlist;
#else
    if(_sasl_client_active == 0) return SASL_NOTINIT;
#endif /* _SUN_SDK_ */
    if (!conn) return SASL_BADPARAM;
    if(conn->type != SASL_CONN_CLIENT) PARAMERROR(conn);
    
    if (! result)
	PARAMERROR(conn);
    
#ifdef _SUN_SDK_
     (void) _load_client_plugins(gctx);
#endif /* _SUN_SDK_ */

    if (plen != NULL)
	*plen = 0;
    if (pcount != NULL)
	*pcount = 0;

    if (sep) {
	mysep = sep;
    } else {
	mysep = " ";
    }

    if(conn->props.min_ssf < conn->external.ssf) {
	minssf = 0;
    } else {
	minssf = conn->props.min_ssf - conn->external.ssf;
    }

    if (! cmechlist || cmechlist->mech_length <= 0)
	INTERROR(conn, SASL_NOMECH);

    resultlen = (prefix ? strlen(prefix) : 0)
	+ (strlen(mysep) * (cmechlist->mech_length - 1))
#ifdef _SUN_SDK_
	+ mech_names_len(gctx)
#else
	+ mech_names_len()
#endif /* _SUN_SDK_ */
	+ (suffix ? strlen(suffix) : 0)
	+ 1;
    ret = _buf_alloc(&conn->mechlist_buf,
		     &conn->mechlist_buf_len, resultlen);
    if(ret != SASL_OK) MEMERROR(conn);

    if (prefix)
	strcpy (conn->mechlist_buf,prefix);
    else
	*(conn->mechlist_buf) = '\0';

    flag = 0;
    for (m = cmechlist->mech_list; m != NULL; m = m->next) {
	    /* do we have the prompts for it? */
	    if (!have_prompts(conn, m->plug))
		continue;

	    /* is it strong enough? */
	    if (minssf > m->plug->max_ssf)
		continue;

	    /* EXPORT DELETE START */
	    /* CRYPT DELETE START */
#ifdef _INTEGRATED_SOLARIS_
	    /* If not SUN supplied mech, it has no strength */
	    if (minssf > 0 && !m->sun_reg)
		continue;
#endif /* _INTEGRATED_SOLARIS_ */
	    /* CRYPT DELETE END */
	    /* EXPORT DELETE END */

	    /* does it meet our security properties? */
	    if (((conn->props.security_flags ^ m->plug->security_flags)
		 & conn->props.security_flags) != 0) {
		continue;
	    }

	    /* Can we meet it's features? */
	    if ((m->plug->features & SASL_FEAT_NEEDSERVERFQDN)
		&& !conn->serverFQDN) {
		continue;
	    }

	    /* Can it meet our features? */
	    if ((conn->flags & SASL_NEED_PROXY) &&
		!(m->plug->features & SASL_FEAT_ALLOWS_PROXY)) {
		break;
	    }

	    /* Okay, we like it, add it to the list! */

	    if (pcount != NULL)
		(*pcount)++;

	    /* print seperator */
	    if (flag) {
		strcat(conn->mechlist_buf, mysep);
	    } else {
		flag = 1;
	    }
	    
	    /* now print the mechanism name */
	    strcat(conn->mechlist_buf, m->plug->mech_name);
    }
    
  if (suffix)
      strcat(conn->mechlist_buf,suffix);

  if (plen!=NULL)
      *plen=strlen(conn->mechlist_buf);

  *result = conn->mechlist_buf;

  return SASL_OK;
}

#ifdef _SUN_SDK_
sasl_string_list_t *_sasl_client_mechs(_sasl_global_context_t *gctx)
{
  cmech_list_t *cmechlist = gctx->cmechlist;
#else
sasl_string_list_t *_sasl_client_mechs(void) 
{
#endif /* _SUN_SDK_ */
  cmechanism_t *listptr;
  sasl_string_list_t *retval = NULL, *next=NULL;

#ifdef _SUN_SDK_
  if(!gctx->sasl_client_active) return NULL;
#else
  if(!_sasl_client_active) return NULL;
#endif /* _SUN_SDK_ */

  /* make list */
  for (listptr = cmechlist->mech_list; listptr; listptr = listptr->next) {
      next = sasl_ALLOC(sizeof(sasl_string_list_t));

      if(!next && !retval) return NULL;
      else if(!next) {
	  next = retval->next;
	  do {
	      sasl_FREE(retval);
	      retval = next;
	      next = retval->next;
	  } while(next);
	  return NULL;
      }
      
      next->d = listptr->plug->mech_name;

      if(!retval) {
	  next->next = NULL;
	  retval = next;
      } else {
	  next->next = retval;
	  retval = next;
      }
  }

  return retval;
}