1/* SASL client API implementation
2 * Rob Siemborski
3 * Tim Martin
4 * $Id: client.c,v 1.86 2011/09/01 14:12:53 mel Exp $
5 */
6/*
7 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in
18 *    the documentation and/or other materials provided with the
19 *    distribution.
20 *
21 * 3. The name "Carnegie Mellon University" must not be used to
22 *    endorse or promote products derived from this software without
23 *    prior written permission. For permission or any other legal
24 *    details, please contact
25 *      Office of Technology Transfer
26 *      Carnegie Mellon University
27 *      5000 Forbes Avenue
28 *      Pittsburgh, PA  15213-3890
29 *      (412) 268-4387, fax: (412) 268-7395
30 *      tech-transfer@andrew.cmu.edu
31 *
32 * 4. Redistributions of any form whatsoever must retain the following
33 *    acknowledgment:
34 *    "This product includes software developed by Computing Services
35 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
36 *
37 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
38 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
39 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
40 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
41 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
42 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
43 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
44 */
45
46#include <config.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <limits.h>
50#include <ctype.h>
51#include <string.h>
52#ifdef HAVE_UNISTD_H
53#include <unistd.h>
54#endif
55
56/* SASL Headers */
57#include "sasl.h"
58#include "saslplug.h"
59#include "saslutil.h"
60#include "saslint.h"
61
62static cmech_list_t *cmechlist; /* global var which holds the list */
63static sasl_global_callbacks_t global_callbacks_client;
64static int _sasl_client_active = 0;
65
66static int init_mechlist()
67{
68  cmechlist->utils=_sasl_alloc_utils(NULL, &global_callbacks_client);
69  if (cmechlist->utils==NULL)
70    return SASL_NOMEM;
71
72  cmechlist->mech_list=NULL;
73  cmechlist->mech_length=0;
74
75  return SASL_OK;
76}
77
78int sasl_client_done(void)
79{
80    int result = SASL_CONTINUE;
81
82    if (_sasl_server_cleanup_hook == NULL && _sasl_client_cleanup_hook == NULL) {
83	return SASL_NOTINIT;
84    }
85
86    if (_sasl_client_cleanup_hook) {
87	result = _sasl_client_cleanup_hook();
88
89	if (result == SASL_OK) {
90	    _sasl_client_idle_hook = NULL;
91	    _sasl_client_cleanup_hook = NULL;
92	} else {
93	    return result;
94	}
95    }
96
97    if (_sasl_server_cleanup_hook || _sasl_client_cleanup_hook) {
98	return result;
99    }
100
101    sasl_common_done();
102
103    return SASL_OK;
104}
105
106static int client_done(void) {
107    cmechanism_t *cm;
108    cmechanism_t *cprevm;
109
110    if (!_sasl_client_active) {
111	return SASL_NOTINIT;
112    } else {
113	_sasl_client_active--;
114    }
115
116    if(_sasl_client_active) {
117	/* Don't de-init yet! Our refcount is nonzero. */
118	return SASL_CONTINUE;
119    }
120
121    cm = cmechlist->mech_list; /* m point to beginning of the list */
122    while (cm != NULL) {
123	cprevm = cm;
124	cm = cm->next;
125
126	if (cprevm->m.plug->mech_free) {
127	    cprevm->m.plug->mech_free(cprevm->m.plug->glob_context,
128				      cmechlist->utils);
129	}
130
131	sasl_FREE(cprevm->m.plugname);
132	sasl_FREE(cprevm);
133    }
134    _sasl_free_utils(&cmechlist->utils);
135    sasl_FREE(cmechlist);
136
137    cmechlist = NULL;
138
139    return SASL_OK;
140}
141
142/* This is nearly identical to the version in server.c.
143   Keep in sync. */
144static int mech_compare(const sasl_client_plug_t *a,
145			const sasl_client_plug_t *b)
146{
147    unsigned sec_diff;
148    unsigned features_diff;
149
150    /* XXX  the following is fairly arbitrary, but its independent
151       of the order in which the plugins are loaded
152    */
153    sec_diff = a->security_flags ^ b->security_flags;
154    if (sec_diff & a->security_flags & SASL_SEC_NOANONYMOUS) return 1;
155    if (sec_diff & b->security_flags & SASL_SEC_NOANONYMOUS) return -1;
156    if (sec_diff & a->security_flags & SASL_SEC_NOPLAINTEXT) return 1;
157    if (sec_diff & b->security_flags & SASL_SEC_NOPLAINTEXT) return -1;
158    if (sec_diff & a->security_flags & SASL_SEC_MUTUAL_AUTH) return 1;
159    if (sec_diff & b->security_flags & SASL_SEC_MUTUAL_AUTH) return -1;
160    if (sec_diff & a->security_flags & SASL_SEC_NOACTIVE) return 1;
161    if (sec_diff & b->security_flags & SASL_SEC_NOACTIVE) return -1;
162    if (sec_diff & a->security_flags & SASL_SEC_NODICTIONARY) return 1;
163    if (sec_diff & b->security_flags & SASL_SEC_NODICTIONARY) return -1;
164    if (sec_diff & a->security_flags & SASL_SEC_FORWARD_SECRECY) return 1;
165    if (sec_diff & b->security_flags & SASL_SEC_FORWARD_SECRECY) return -1;
166    if (sec_diff & a->security_flags & SASL_SEC_DEVICE_AUTH) return 1;
167    if (sec_diff & b->security_flags & SASL_SEC_DEVICE_AUTH) return -1;
168    if (sec_diff & a->security_flags & SASL_SEC_NOLEGACY) return 1;
169    if (sec_diff & b->security_flags & SASL_SEC_NOLEGACY) return -1;
170
171    features_diff = a->features ^ b->features;
172    if (features_diff & a->features & SASL_FEAT_CHANNEL_BINDING) return 1;
173    if (features_diff & b->features & SASL_FEAT_CHANNEL_BINDING) return -1;
174
175    if (a->max_ssf > b->max_ssf) return 1;
176    if (a->max_ssf < b->max_ssf) return -1;
177
178    return 0;
179}
180
181int sasl_client_add_plugin(const char *plugname,
182			   sasl_client_plug_init_t *entry_point)
183{
184    int plugcount;
185    sasl_client_plug_t *pluglist;
186    cmechanism_t *mech, *mp;
187    int result;
188    int version;
189    int lupe;
190
191    if (!plugname || !entry_point) return SASL_BADPARAM;
192
193    result = entry_point(cmechlist->utils,
194			 SASL_CLIENT_PLUG_VERSION,
195			 &version,
196			 &pluglist,
197			 &plugcount);
198
199    if (result != SASL_OK)
200    {
201	_sasl_log(NULL, SASL_LOG_WARN,
202		  "sasl_client_add_plugin(): entry_point(): failed for plugname %s: %z",
203		  plugname, result);
204	return result;
205    }
206
207    if (version != SASL_CLIENT_PLUG_VERSION)
208    {
209	_sasl_log(NULL, SASL_LOG_WARN,
210	      "version conflict in sasl_client_add_plugin for %s", plugname);
211	return SASL_BADVERS;
212    }
213
214    for (lupe=0; lupe < plugcount; lupe++, pluglist++)
215    {
216	mech = sasl_ALLOC(sizeof(cmechanism_t));
217	if (!mech) return SASL_NOMEM;
218
219	mech->m.plug = pluglist;
220	if (_sasl_strdup(plugname, &mech->m.plugname, NULL) != SASL_OK) {
221	    sasl_FREE(mech);
222	    return SASL_NOMEM;
223	}
224	mech->m.version = version;
225
226	/* sort mech_list by relative "strength" */
227	mp = cmechlist->mech_list;
228	if (!mp || mech_compare(pluglist, mp->m.plug) >= 0) {
229	    /* add mech to head of list */
230	    mech->next = cmechlist->mech_list;
231	    cmechlist->mech_list = mech;
232	} else {
233	    /* find where to insert mech into list */
234	    while (mp->next &&
235		   mech_compare(pluglist, mp->next->m.plug) <= 0) mp = mp->next;
236	    mech->next = mp->next;
237	    mp->next = mech;
238	}
239
240	cmechlist->mech_length++;
241    }
242
243    return SASL_OK;
244}
245
246static int
247client_idle(sasl_conn_t *conn)
248{
249  cmechanism_t *m;
250  if (! cmechlist)
251    return 0;
252
253  for (m = cmechlist->mech_list;
254       m;
255       m = m->next)
256    if (m->m.plug->idle
257	&&  m->m.plug->idle(m->m.plug->glob_context,
258			  conn,
259			  conn ? ((sasl_client_conn_t *)conn)->cparams : NULL))
260      return 1;
261  return 0;
262}
263
264/* initialize the SASL client drivers
265 *  callbacks      -- base callbacks for all client connections
266 * returns:
267 *  SASL_OK        -- Success
268 *  SASL_NOMEM     -- Not enough memory
269 *  SASL_BADVERS   -- Mechanism version mismatch
270 *  SASL_BADPARAM  -- error in config file
271 *  SASL_NOMECH    -- No mechanisms available
272 *  ...
273 */
274
275int sasl_client_init(const sasl_callback_t *callbacks)
276{
277  int ret;
278  const add_plugin_list_t ep_list[] = {
279      { "sasl_client_plug_init", (add_plugin_t *)sasl_client_add_plugin },
280      { "sasl_canonuser_init", (add_plugin_t *)sasl_canonuser_add_plugin },
281      { NULL, NULL }
282  };
283
284  /* lock allocation type */
285  _sasl_allocation_locked++;
286
287  if(_sasl_client_active) {
288      /* We're already active, just increase our refcount */
289      /* xxx do something with the callback structure? */
290      _sasl_client_active++;
291      return SASL_OK;
292  }
293
294  global_callbacks_client.callbacks = callbacks;
295  global_callbacks_client.appname = NULL;
296
297  cmechlist=sasl_ALLOC(sizeof(cmech_list_t));
298  if (cmechlist==NULL) return SASL_NOMEM;
299
300  /* We need to call client_done if we fail now */
301  _sasl_client_active = 1;
302
303  /* load plugins */
304  ret=init_mechlist();
305  if (ret!=SASL_OK) {
306      client_done();
307      return ret;
308  }
309
310  sasl_client_add_plugin("EXTERNAL", &external_client_plug_init);
311
312  ret = _sasl_common_init(&global_callbacks_client);
313
314  if (ret == SASL_OK)
315      ret = _sasl_load_plugins(ep_list,
316			       _sasl_find_getpath_callback(callbacks),
317			       _sasl_find_verifyfile_callback(callbacks));
318
319  if (ret == SASL_OK) {
320      _sasl_client_cleanup_hook = &client_done;
321      _sasl_client_idle_hook = &client_idle;
322
323      ret = _sasl_build_mechlist();
324  } else {
325      client_done();
326  }
327
328  return ret;
329}
330
331static void client_dispose(sasl_conn_t *pconn)
332{
333  sasl_client_conn_t *c_conn=(sasl_client_conn_t *) pconn;
334
335  if (c_conn->mech && c_conn->mech->m.plug->mech_dispose) {
336    c_conn->mech->m.plug->mech_dispose(pconn->context,
337				     c_conn->cparams->utils);
338  }
339
340  pconn->context = NULL;
341
342  if (c_conn->clientFQDN)
343      sasl_FREE(c_conn->clientFQDN);
344
345  if (c_conn->cparams) {
346      _sasl_free_utils(&(c_conn->cparams->utils));
347      sasl_FREE(c_conn->cparams);
348  }
349
350  if (c_conn->mech_list != cmechlist->mech_list) {
351      /* free connection-specific mech_list */
352      cmechanism_t *m, *prevm;
353
354      m = c_conn->mech_list; /* m point to beginning of the list */
355
356      while (m) {
357	  prevm = m;
358	  m = m->next;
359	  sasl_FREE(prevm);
360      }
361  }
362
363  _sasl_conn_dispose(pconn);
364}
365
366/* initialize a client exchange based on the specified mechanism
367 *  service       -- registered name of the service using SASL (e.g. "imap")
368 *  serverFQDN    -- the fully qualified domain name of the server
369 *  iplocalport   -- client IPv4/IPv6 domain literal string with port
370 *                    (if NULL, then mechanisms requiring IPaddr are disabled)
371 *  ipremoteport  -- server IPv4/IPv6 domain literal string with port
372 *                    (if NULL, then mechanisms requiring IPaddr are disabled)
373 *  prompt_supp   -- list of client interactions supported
374 *                   may also include sasl_getopt_t context & call
375 *                   NULL prompt_supp = user/pass via SASL_INTERACT only
376 *                   NULL proc = interaction supported via SASL_INTERACT
377 *  secflags      -- security flags (see above)
378 * in/out:
379 *  pconn         -- connection negotiation structure
380 *                   pointer to NULL => allocate new
381 *                   non-NULL => recycle storage and go for next available mech
382 *
383 * Returns:
384 *  SASL_OK       -- success
385 *  SASL_NOMECH   -- no mechanism meets requested properties
386 *  SASL_NOMEM    -- not enough memory
387 */
388int sasl_client_new(const char *service,
389		    const char *serverFQDN,
390		    const char *iplocalport,
391		    const char *ipremoteport,
392		    const sasl_callback_t *prompt_supp,
393		    unsigned flags,
394		    sasl_conn_t **pconn)
395{
396  int result;
397  char name[MAXHOSTNAMELEN];
398  sasl_client_conn_t *conn;
399  sasl_utils_t *utils;
400  sasl_getopt_t *getopt;
401  void *context;
402  const char *mlist = NULL;
403  int plus = 0;
404
405  if (_sasl_client_active == 0) return SASL_NOTINIT;
406
407  /* Remember, serverFQDN, iplocalport and ipremoteport can be NULL and be valid! */
408  if (!pconn || !service)
409    return SASL_BADPARAM;
410
411  *pconn=sasl_ALLOC(sizeof(sasl_client_conn_t));
412  if (*pconn==NULL) {
413      _sasl_log(NULL, SASL_LOG_ERR,
414		"Out of memory allocating connection context");
415      return SASL_NOMEM;
416  }
417  memset(*pconn, 0, sizeof(sasl_client_conn_t));
418
419  (*pconn)->destroy_conn = &client_dispose;
420
421  conn = (sasl_client_conn_t *)*pconn;
422
423  conn->mech = NULL;
424
425  conn->cparams=sasl_ALLOC(sizeof(sasl_client_params_t));
426  if (conn->cparams==NULL)
427      MEMERROR(*pconn);
428  memset(conn->cparams,0,sizeof(sasl_client_params_t));
429
430  result = _sasl_conn_init(*pconn, service, flags, SASL_CONN_CLIENT,
431			   &client_idle, serverFQDN,
432			   iplocalport, ipremoteport,
433			   prompt_supp, &global_callbacks_client);
434  if (result != SASL_OK) RETURN(*pconn, result);
435
436  utils = _sasl_alloc_utils(*pconn, &global_callbacks_client);
437  if (utils == NULL) {
438      MEMERROR(*pconn);
439  }
440
441  utils->conn= *pconn;
442  conn->cparams->utils = utils;
443
444  if(_sasl_getcallback(*pconn, SASL_CB_GETOPT, (sasl_callback_ft *)&getopt, &context) == SASL_OK) {
445    getopt(context, NULL, "client_mech_list", &mlist, NULL);
446  }
447
448  /* if we have a client_mech_list, create ordered list of
449     available mechanisms for this conn */
450  if (mlist) {
451      const char *cp;
452      cmechanism_t *mptr, *tail = NULL;
453      cmechanism_t *new;
454
455      while (*mlist) {
456	  /* find end of current mech name */
457	  for (cp = mlist; *cp && !isspace((int) *cp); cp++);
458
459	  /* search for mech name in loaded plugins */
460	  for (mptr = cmechlist->mech_list; mptr; mptr = mptr->next) {
461	      const sasl_client_plug_t *plug = mptr->m.plug;
462
463	      if (_sasl_is_equal_mech(mlist, plug->mech_name, (size_t) (cp - mlist), &plus)) {
464		  /* found a match */
465		  break;
466	      }
467	  }
468	  if (mptr) {
469	      new = sasl_ALLOC(sizeof(cmechanism_t));
470	      if (!new) {
471		  result = SASL_NOMEM;
472		  goto failed_client_new;
473	      }
474	      memcpy(&new->m, &mptr->m, sizeof(client_sasl_mechanism_t));
475	      new->next = NULL;
476
477	      if (!conn->mech_list) {
478		  conn->mech_list = new;
479		  tail = conn->mech_list;
480	      } else {
481		  tail->next = new;
482		  tail = new;
483	      }
484	      conn->mech_length++;
485	  }
486
487	  /* find next mech name */
488	  mlist = cp;
489	  while (*mlist && isspace((int) *mlist)) mlist++;
490      }
491  } else {
492      conn->mech_list = cmechlist->mech_list;
493      conn->mech_length = cmechlist->mech_length;
494  }
495
496  if (conn->mech_list == NULL) {
497      sasl_seterror(*pconn, 0, "No worthy mechs found");
498      result = SASL_NOMECH;
499      goto failed_client_new;
500  }
501
502  /* Setup the non-lazy parts of cparams, the rest is done in
503   * sasl_client_start */
504  conn->cparams->utils = utils;
505  conn->cparams->canon_user = &_sasl_canon_user_lookup;
506  conn->cparams->flags = flags;
507  conn->cparams->prompt_supp = (*pconn)->callbacks;
508
509  /* get the clientFQDN (serverFQDN was set in _sasl_conn_init) */
510  memset(name, 0, sizeof(name));
511  if (get_fqhostname (name, MAXHOSTNAMELEN, 0) != 0) {
512      return (SASL_FAIL);
513  }
514
515  result = _sasl_strdup(name, &conn->clientFQDN, NULL);
516
517  if (result == SASL_OK) return SASL_OK;
518
519failed_client_new:
520  /* result isn't SASL_OK */
521  _sasl_conn_dispose(*pconn);
522  sasl_FREE(*pconn);
523  *pconn = NULL;
524  _sasl_log(NULL, SASL_LOG_ERR, "Out of memory in sasl_client_new");
525  return result;
526}
527
528static int have_prompts(sasl_conn_t *conn,
529			const sasl_client_plug_t *mech)
530{
531  static const unsigned long default_prompts[] = {
532    SASL_CB_AUTHNAME,
533    SASL_CB_PASS,
534    SASL_CB_LIST_END
535  };
536
537  const unsigned long *prompt;
538  sasl_callback_ft pproc;
539  void *pcontext;
540  int result;
541
542  for (prompt = (mech->required_prompts
543		 ? mech->required_prompts :
544		 default_prompts);
545       *prompt != SASL_CB_LIST_END;
546       prompt++) {
547    result = _sasl_getcallback(conn, *prompt, &pproc, &pcontext);
548    if (result != SASL_OK && result != SASL_INTERACT)
549      return 0;			/* we don't have this required prompt */
550  }
551
552  return 1; /* we have all the prompts */
553}
554
555static int
556_mech_plus_p(const char *mech, size_t len)
557{
558    return (len > 5 && strncasecmp(&mech[len - 5], "-PLUS", 5) == 0);
559}
560
561static char *_sasl_sort_mechlist(const sasl_utils_t *utils, const char *unsorted);
562
563/*
564 * Order PLUS mechanisms first. Returns NUL separated list of
565 * *count items.
566 */
567static int
568_sasl_client_order_mechs(const sasl_utils_t *utils,
569			 const char *unordered_mechs,
570			 int has_cb_data,
571			 char **ordered_mechs,
572			 size_t *count,
573			 int *server_can_cb)
574{
575    char *list, *listp, *mechs;
576    size_t i, mechslen, start;
577
578    *count = 0;
579    *server_can_cb = 0;
580
581    mechs = _sasl_sort_mechlist(utils, unordered_mechs); // APPLE: presort mechlist to match plugin order mech_compare(), 15938147
582
583    if (mechs == NULL || mechs[0] == '\0')
584        return SASL_NOMECH;
585
586    mechslen = strlen(mechs);
587
588    listp = list = utils->malloc(mechslen + 1);
589    if (list == NULL)
590	return SASL_NOMEM;
591
592    /* As per RFC 4422:
593     * SASL mechanism allowable characters are "AZ-_"
594     * separators can be any other characters and of any length
595     * even variable lengths between.
596     *
597     * But for convenience we accept lowercase ASCII.
598     *
599     * Apps should be encouraged to simply use space or comma space
600     * though
601     */
602#define ismechchar(c)   (isalnum((c)) || (c) == '_' || (c) == '-')
603#define NON_MECH_CHAR "\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&\'()*+,./:;<=>?@[\\]^`{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
604    do {
605        for (i = start = 0; i <= mechslen; i++) {
606	    if (!ismechchar(mechs[i])) {
607                const char *mechp = &mechs[start];
608		size_t len = i - start;
609
610		if (len != 0 &&
611                    _mech_plus_p(mechp, len) == has_cb_data) {
612		    memcpy(listp, mechp, len);
613		    listp[len] = '\0';
614		    listp += len + 1;
615		    (*count)++;
616		    if (*server_can_cb == 0 && has_cb_data)
617			*server_can_cb = 1;
618		}
619		start = ++i;
620	    }
621	}
622	if (has_cb_data)
623	    has_cb_data = 0;
624	else
625	    break;
626    } while (1);
627
628    if (*count == 0) {
629        utils->free(list);
630        return SASL_NOMECH;
631    }
632    if (mechs)
633        utils->free(mechs);
634
635    *ordered_mechs = list;
636
637    return SASL_OK;
638}
639
640static char **_sasl_split_mechlist(const sasl_utils_t *utils, char *s, size_t *count_out)
641{
642	char *token = NULL, *brk = NULL;
643	size_t i = 0, max_count = 2;
644
645	for (i = 0; i < strlen(s); ++i) {
646		if (!ismechchar(s[i])) max_count += 1;
647	}
648
649	char **array = utils->malloc(max_count * sizeof(char *));
650
651	bzero(array, max_count * sizeof(char *));
652
653	token = strtok_r(s, NON_MECH_CHAR, &brk);
654	i = 0;
655	while(token) {
656		array[i] = token;
657		i += 1;
658		if (i > max_count) return NULL;
659
660		token = strtok_r(NULL, NON_MECH_CHAR, &brk);
661	}
662	*count_out = i;
663
664	return array;
665}
666
667static char *_sasl_join_mechlist(const sasl_utils_t *utils, char **array)
668{
669	size_t max_length = 1;
670	char *s = NULL;
671
672	for (size_t i = 0; array[i]; ++i) {
673		max_length += strlen(array[i]) + 1;
674	}
675	s = utils->malloc(max_length);
676	bzero(s, max_length);
677
678	for (size_t i = 0; array[i]; ++i) {
679		strlcat(s, array[i], max_length);
680		strlcat(s, " ", max_length);
681	}
682
683	return s;
684}
685
686/* Get the mechanism index from the loaded plugin list, which was sorted on load
687 * by mech_compare() in sasl_client_add_plugin(). */
688static int _sasl_mech_index(const char *name)
689{
690	cmechanism_t *m;
691	int plus = 0;
692	size_t i = 0;
693
694	for (m = cmechlist->mech_list; m != NULL; m = m->next) {
695		size_t name_len = strlen(name);
696		if (_sasl_is_equal_mech(name, m->m.plug->mech_name, name_len, &plus)) {
697			return i;
698		}
699		++i;
700	}
701
702	return INT_MAX;
703}
704
705static int _sasl_mech_order(const char *a, const char *b)
706{
707	return _sasl_mech_index(a) - _sasl_mech_index(b);
708}
709
710static char *_sasl_sort_mechlist(const sasl_utils_t *utils, const char *unsorted)
711{
712	char *unsorted_copy = strdup(unsorted);
713	char *sorted = NULL;
714	size_t count = 0;
715	char **array = _sasl_split_mechlist(utils, unsorted_copy, &count);
716	if (!array) {
717		free(unsorted_copy);
718		return NULL;
719	}
720
721	qsort_b(array, count, sizeof(char *), ^(const void *a, const void *b) {
722		return _sasl_mech_order(*(const char **)a, *(const char **)b);
723	});
724
725	sorted = _sasl_join_mechlist(utils, array);
726	utils->free(array);
727	utils->free(unsorted_copy);
728
729	return sorted;
730}
731
732static INLINE int
733_sasl_cbinding_disp(sasl_client_params_t *cparams,
734                    int mech_nego,
735                    int server_can_cb,
736                    sasl_cbinding_disp_t *cbindingdisp)
737{
738    /*
739     * If negotiating mechanisms, then we fail immediately if the
740     * client requires channel binding and the server does not
741     * advertise support. Otherwise we send "y" (which later will
742     * become "p" if we select a supporting mechanism).
743     *
744     * If the client explicitly selected a mechanism, then we only
745     * send channel bindings if they're marked critical.
746     */
747
748    *cbindingdisp = SASL_CB_DISP_NONE;
749
750    if (SASL_CB_PRESENT(cparams)) {
751        if (mech_nego) {
752	    if (!server_can_cb && SASL_CB_CRITICAL(cparams)) {
753	        return SASL_NOMECH;
754	    } else {
755                *cbindingdisp = SASL_CB_DISP_WANT;
756	    }
757        } else if (SASL_CB_CRITICAL(cparams)) {
758            *cbindingdisp = SASL_CB_DISP_USED;
759        }
760    }
761
762    return SASL_OK;
763}
764
765/* select a mechanism for a connection
766 *  mechlist      -- mechanisms server has available (punctuation ignored)
767 *  secret        -- optional secret from previous session
768 * output:
769 *  prompt_need   -- on SASL_INTERACT, list of prompts needed to continue
770 *  clientout     -- the initial client response to send to the server
771 *  mech          -- set to mechanism name
772 *
773 * Returns:
774 *  SASL_OK       -- success
775 *  SASL_NOMEM    -- not enough memory
776 *  SASL_NOMECH   -- no mechanism meets requested properties
777 *  SASL_INTERACT -- user interaction needed to fill in prompt_need list
778 */
779
780/*
781 * SASL mechanism allowable characters are "AZ-_"
782 * separators can be any other characters and of any length
783 * even variable lengths between.
784 *
785 * But for convenience we accept lowercase ASCII.
786 *
787 * Apps should be encouraged to simply use space or comma space
788 * though
789 */
790int sasl_client_start(sasl_conn_t *conn,
791		      const char *mechlist,
792		      sasl_interact_t **prompt_need,
793		      const char **clientout,
794		      unsigned *clientoutlen,
795		      const char **mech)
796{
797    sasl_client_conn_t *c_conn = (sasl_client_conn_t *) conn;
798    char *ordered_mechs = NULL, *name;
799    cmechanism_t *m = NULL, *bestm = NULL;
800    size_t i, list_len, name_len;
801    sasl_ssf_t bestssf = 0, minssf = 0;
802    int result, server_can_cb = 0;
803    sasl_cbinding_disp_t cbindingdisp;
804    sasl_cbinding_disp_t cur_cbindingdisp;
805    sasl_cbinding_disp_t best_cbindingdisp = SASL_CB_DISP_NONE;
806
807    if (_sasl_client_active == 0) return SASL_NOTINIT;
808
809    if (!conn) return SASL_BADPARAM;
810
811    /* verify parameters */
812    if (mechlist == NULL) {
813	PARAMERROR(conn);
814    }
815
816    /* if prompt_need != NULL we've already been here
817       and just need to do the continue step again */
818
819    /* do a step */
820    /* FIXME: Hopefully they only give us our own prompt_need back */
821    if (prompt_need && *prompt_need != NULL) {
822	goto dostep;
823    }
824
825    if (conn->props.min_ssf < conn->external.ssf) {
826	minssf = 0;
827    } else {
828	minssf = conn->props.min_ssf - conn->external.ssf;
829    }
830
831    /* Order mechanisms so -PLUS are preferred */
832    result = _sasl_client_order_mechs(c_conn->cparams->utils,
833				      mechlist,
834				      SASL_CB_PRESENT(c_conn->cparams),
835				      &ordered_mechs,
836				      &list_len,
837				      &server_can_cb);
838    if (result != 0)
839	goto done;
840
841    /*
842     * Determine channel binding disposition based on whether we
843     * are doing mechanism negotiation and whether server supports
844     * channel bindings.
845     */
846    result = _sasl_cbinding_disp(c_conn->cparams,
847				 (list_len > 1),
848                                 server_can_cb,
849				 &cbindingdisp);
850    if (result != 0)
851	goto done;
852
853    for (i = 0, name = ordered_mechs; i < list_len; i++) {
854
855	name_len = strlen(name);
856
857	/* for each mechanism in client's list */
858	for (m = c_conn->mech_list; m != NULL; m = m->next) {
859	    unsigned myflags;
860	    int plus;
861
862	    if (!_sasl_is_equal_mech(name, m->m.plug->mech_name, name_len, &plus)) {
863		continue;
864	    }
865
866	    /* Do we have the prompts for it? */
867	    if (!have_prompts(conn, m->m.plug))
868		break;
869
870	    /* Is it strong enough? */
871	    if (minssf > m->m.plug->max_ssf)
872		break;
873
874	    myflags = conn->props.security_flags;
875
876	    /* if there's an external layer with a better SSF then this is no
877	     * longer considered a plaintext mechanism
878	     */
879	    if ((conn->props.min_ssf <= conn->external.ssf) &&
880		(conn->external.ssf > 1)) {
881		myflags &= ~SASL_SEC_NOPLAINTEXT;
882	    }
883
884	    /* Does it meet our security properties? */
885	    if (((myflags ^ m->m.plug->security_flags) & myflags) != 0) {
886		break;
887	    }
888
889	    /* Can we meet it's features? */
890	    if (cbindingdisp == SASL_CB_DISP_USED &&
891		!(m->m.plug->features & SASL_FEAT_CHANNEL_BINDING)) {
892		break;
893	    }
894
895	    if ((m->m.plug->features & SASL_FEAT_NEEDSERVERFQDN)
896		&& !conn->serverFQDN) {
897		break;
898	    }
899
900	    /* Can it meet our features? */
901	    if ((conn->flags & SASL_NEED_PROXY) &&
902		!(m->m.plug->features & SASL_FEAT_ALLOWS_PROXY)) {
903		break;
904	    }
905
906	    if ((conn->flags & SASL_NEED_HTTP) &&
907		!(m->m.plug->features & SASL_FEAT_SUPPORTS_HTTP)) {
908		break;
909	    }
910
911	    /* compare security flags, only take new mechanism if it has
912	     * all the security flags of the previous one.
913	     *
914	     * From the mechanisms we ship with, this yields the order:
915	     *
916	     * SRP
917	     * GSSAPI + KERBEROS_V4
918	     * DIGEST + OTP
919	     * CRAM + EXTERNAL
920	     * PLAIN + LOGIN + ANONYMOUS
921	     *
922	     * This might be improved on by comparing the numeric value of
923	     * the bitwise-or'd security flags, which splits DIGEST/OTP,
924	     * CRAM/EXTERNAL, and PLAIN/LOGIN from ANONYMOUS, but then we
925	     * are depending on the numeric values of the flags (which may
926	     * change, and their ordering could be considered dumb luck.
927	     */
928
929	    if (bestm &&
930		((m->m.plug->security_flags ^ bestm->m.plug->security_flags) &
931		 bestm->m.plug->security_flags)) {
932		break;
933	    }
934
935	    if (SASL_CB_PRESENT(c_conn->cparams) && plus) {
936		cur_cbindingdisp = SASL_CB_DISP_USED;
937	    } else {
938		cur_cbindingdisp = cbindingdisp;
939	    }
940
941	    if (bestm && (best_cbindingdisp > cur_cbindingdisp)) {
942		break;
943	    }
944
945#ifdef PREFER_MECH
946	    if (strcasecmp(m->m.plug->mech_name, PREFER_MECH) &&
947		bestm && m->m.plug->max_ssf <= bestssf) {
948		/* this mechanism isn't our favorite, and it's no better
949		   than what we already have! */
950		break;
951	    }
952#else
953	    if (bestm && m->m.plug->max_ssf <= bestssf) {
954		/* this mechanism is no better than what we already have! */
955		break;
956	    }
957#endif
958
959	    if (mech) {
960		*mech = m->m.plug->mech_name;
961	    }
962
963	    best_cbindingdisp = cur_cbindingdisp;
964	    bestssf = m->m.plug->max_ssf;
965	    bestm = m;
966	    break;
967	}
968	name += strlen(name) + 1;
969    }
970
971    if (bestm == NULL) {
972	sasl_seterror(conn, 0, "No worthy mechs found");
973	result = SASL_NOMECH;
974	goto done;
975    }
976
977    /* make (the rest of) cparams */
978    c_conn->cparams->service = conn->service;
979    c_conn->cparams->servicelen = (unsigned) strlen(conn->service);
980
981    if (conn->serverFQDN) {
982	c_conn->cparams->serverFQDN = conn->serverFQDN;
983	c_conn->cparams->slen = (unsigned) strlen(conn->serverFQDN);
984    }
985
986    c_conn->cparams->clientFQDN = c_conn->clientFQDN;
987    c_conn->cparams->clen = (unsigned) strlen(c_conn->clientFQDN);
988
989    c_conn->cparams->external_ssf = conn->external.ssf;
990    c_conn->cparams->props = conn->props;
991    c_conn->cparams->cbindingdisp = best_cbindingdisp;
992    c_conn->mech = bestm;
993
994    /* init that plugin */
995    result = c_conn->mech->m.plug->mech_new(c_conn->mech->m.plug->glob_context,
996					  c_conn->cparams,
997					  &(conn->context));
998    if (result != SASL_OK) goto done;
999
1000    /* do a step -- but only if we can do a client-send-first */
1001 dostep:
1002    if(clientout) {
1003        if(c_conn->mech->m.plug->features & SASL_FEAT_SERVER_FIRST) {
1004            *clientout = NULL;
1005            *clientoutlen = 0;
1006            result = SASL_CONTINUE;
1007        } else {
1008            result = sasl_client_step(conn, NULL, 0, prompt_need,
1009                                      clientout, clientoutlen);
1010        }
1011    }
1012    else
1013	result = SASL_CONTINUE;
1014
1015 done:
1016    if (ordered_mechs != NULL)
1017	c_conn->cparams->utils->free(ordered_mechs);
1018    RETURN(conn, result);
1019}
1020
1021/* do a single authentication step.
1022 *  serverin    -- the server message received by the client, MUST have a NUL
1023 *                 sentinel, not counted by serverinlen
1024 * output:
1025 *  prompt_need -- on SASL_INTERACT, list of prompts needed to continue
1026 *  clientout   -- the client response to send to the server
1027 *
1028 * returns:
1029 *  SASL_OK        -- success
1030 *  SASL_INTERACT  -- user interaction needed to fill in prompt_need list
1031 *  SASL_BADPROT   -- server protocol incorrect/cancelled
1032 *  SASL_BADSERV   -- server failed mutual auth
1033 */
1034
1035int sasl_client_step(sasl_conn_t *conn,
1036		     const char *serverin,
1037		     unsigned serverinlen,
1038		     sasl_interact_t **prompt_need,
1039		     const char **clientout,
1040		     unsigned *clientoutlen)
1041{
1042  sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
1043  int result;
1044
1045  if (_sasl_client_active == 0) return SASL_NOTINIT;
1046  if (!conn) return SASL_BADPARAM;
1047
1048  /* check parameters */
1049  if ((serverin==NULL) && (serverinlen>0))
1050      PARAMERROR(conn);
1051
1052  /* Don't do another step if the plugin told us that we're done */
1053  if (conn->oparams.doneflag) {
1054      _sasl_log(conn, SASL_LOG_ERR, "attempting client step after doneflag");
1055      return SASL_FAIL;
1056  }
1057
1058  if(clientout) *clientout = NULL;
1059  if(clientoutlen) *clientoutlen = 0;
1060
1061  /* do a step */
1062  result = c_conn->mech->m.plug->mech_step(conn->context,
1063					 c_conn->cparams,
1064					 serverin,
1065					 serverinlen,
1066					 prompt_need,
1067					 clientout, clientoutlen,
1068					 &conn->oparams);
1069
1070  if (result == SASL_OK) {
1071      /* So we're done on this end, but if both
1072       * 1. the mech does server-send-last
1073       * 2. the protocol does not
1074       * we need to return no data */
1075      if(!*clientout && !(conn->flags & SASL_SUCCESS_DATA)) {
1076	  *clientout = "";
1077	  *clientoutlen = 0;
1078      }
1079
1080      if(!conn->oparams.maxoutbuf) {
1081	  conn->oparams.maxoutbuf = conn->props.maxbufsize;
1082      }
1083
1084      if(conn->oparams.user == NULL || conn->oparams.authid == NULL) {
1085	  sasl_seterror(conn, 0,
1086			"mech did not call canon_user for both authzid and authid");
1087	  result = SASL_BADPROT;
1088      }
1089  }
1090
1091  RETURN(conn,result);
1092}
1093
1094/* returns the length of all the mechanisms
1095 * added up
1096 */
1097
1098static unsigned mech_names_len(cmechanism_t *mech_list)
1099{
1100  cmechanism_t *listptr;
1101  unsigned result = 0;
1102
1103  for (listptr = mech_list;
1104       listptr;
1105       listptr = listptr->next)
1106    result += (unsigned) strlen(listptr->m.plug->mech_name);
1107
1108  return result;
1109}
1110
1111
1112int _sasl_client_listmech(sasl_conn_t *conn,
1113			  const char *prefix,
1114			  const char *sep,
1115			  const char *suffix,
1116			  const char **result,
1117			  unsigned *plen,
1118			  int *pcount)
1119{
1120    sasl_client_conn_t *c_conn = (sasl_client_conn_t *)conn;
1121    cmechanism_t *m = NULL;
1122    sasl_ssf_t minssf = 0;
1123    int ret;
1124    size_t resultlen;
1125    int flag;
1126    const char *mysep;
1127
1128    if (_sasl_client_active == 0) return SASL_NOTINIT;
1129    if (!conn) return SASL_BADPARAM;
1130    if (conn->type != SASL_CONN_CLIENT) PARAMERROR(conn);
1131
1132    if (! result)
1133	PARAMERROR(conn);
1134
1135    if (plen != NULL)
1136	*plen = 0;
1137    if (pcount != NULL)
1138	*pcount = 0;
1139
1140    if (sep) {
1141	mysep = sep;
1142    } else {
1143	mysep = " ";
1144    }
1145
1146    if (conn->props.min_ssf < conn->external.ssf) {
1147	minssf = 0;
1148    } else {
1149	minssf = conn->props.min_ssf - conn->external.ssf;
1150    }
1151
1152    if (!c_conn->mech_list || c_conn->mech_length <= 0) {
1153	INTERROR(conn, SASL_NOMECH);
1154    }
1155
1156    resultlen = (prefix ? strlen(prefix) : 0)
1157	+ (strlen(mysep) * (c_conn->mech_length - 1))
1158	+ mech_names_len(c_conn->mech_list)
1159	+ (suffix ? strlen(suffix) : 0)
1160	+ 1;
1161    ret = _buf_alloc(&conn->mechlist_buf,
1162		     &conn->mechlist_buf_len,
1163		     resultlen);
1164    if (ret != SASL_OK) MEMERROR(conn);
1165
1166    if (prefix) {
1167	strcpy (conn->mechlist_buf,prefix);
1168    } else {
1169	*(conn->mechlist_buf) = '\0';
1170    }
1171
1172    flag = 0;
1173    for (m = c_conn->mech_list; m != NULL; m = m->next) {
1174	    /* do we have the prompts for it? */
1175	    if (!have_prompts(conn, m->m.plug)) {
1176		continue;
1177	    }
1178
1179	    /* is it strong enough? */
1180	    if (minssf > m->m.plug->max_ssf) {
1181		continue;
1182	    }
1183
1184	    /* does it meet our security properties? */
1185	    if (((conn->props.security_flags ^ m->m.plug->security_flags)
1186		 & conn->props.security_flags) != 0) {
1187		continue;
1188	    }
1189
1190	    /* Can we meet it's features? */
1191	    if ((m->m.plug->features & SASL_FEAT_NEEDSERVERFQDN)
1192		&& !conn->serverFQDN) {
1193		continue;
1194	    }
1195
1196	    /* Can it meet our features? */
1197	    if ((conn->flags & SASL_NEED_PROXY) &&
1198		!(m->m.plug->features & SASL_FEAT_ALLOWS_PROXY)) {
1199		continue;
1200	    }
1201
1202	    /* Okay, we like it, add it to the list! */
1203
1204	    if (pcount != NULL)
1205		(*pcount)++;
1206
1207	    /* print seperator */
1208	    if (flag) {
1209		strcat(conn->mechlist_buf, mysep);
1210	    } else {
1211		flag = 1;
1212	    }
1213
1214	    /* now print the mechanism name */
1215	    strcat(conn->mechlist_buf, m->m.plug->mech_name);
1216    }
1217
1218  if (suffix)
1219      strcat(conn->mechlist_buf,suffix);
1220
1221  if (plen!=NULL)
1222      *plen = (unsigned) strlen(conn->mechlist_buf);
1223
1224  *result = conn->mechlist_buf;
1225
1226  return SASL_OK;
1227}
1228
1229sasl_string_list_t *_sasl_client_mechs(void)
1230{
1231  cmechanism_t *listptr;
1232  sasl_string_list_t *retval = NULL, *next=NULL;
1233
1234  if(!_sasl_client_active) return NULL;
1235
1236  /* make list */
1237  for (listptr = cmechlist->mech_list; listptr; listptr = listptr->next) {
1238      next = sasl_ALLOC(sizeof(sasl_string_list_t));
1239
1240      if(!next && !retval) return NULL;
1241      else if(!next) {
1242	  next = retval->next;
1243	  do {
1244	      sasl_FREE(retval);
1245	      retval = next;
1246	      next = retval->next;
1247	  } while(next);
1248	  return NULL;
1249      }
1250
1251      next->d = listptr->m.plug->mech_name;
1252
1253      if(!retval) {
1254	  next->next = NULL;
1255	  retval = next;
1256      } else {
1257	  next->next = retval;
1258	  retval = next;
1259      }
1260  }
1261
1262  return retval;
1263}
1264
1265
1266
1267
1268/* It would be nice if we can show other information like Author, Company, Year, plugin version */
1269static void
1270_sasl_print_mechanism (
1271  client_sasl_mechanism_t *m,
1272  sasl_info_callback_stage_t stage,
1273  void *rock __attribute__((unused))
1274)
1275{
1276    char delimiter;
1277
1278    if (stage == SASL_INFO_LIST_START) {
1279	printf ("List of client plugins follows\n");
1280	return;
1281    } else if (stage == SASL_INFO_LIST_END) {
1282	return;
1283    }
1284
1285    /* Process the mechanism */
1286    printf ("Plugin \"%s\" ", m->plugname);
1287
1288    /* There is no delay loading for client side plugins */
1289    printf ("[loaded]");
1290
1291    printf (", \tAPI version: %d\n", m->version);
1292
1293    if (m->plug != NULL) {
1294	printf ("\tSASL mechanism: %s, best SSF: %d\n",
1295		m->plug->mech_name,
1296		m->plug->max_ssf);
1297
1298	printf ("\tsecurity flags:");
1299
1300	delimiter = ' ';
1301	if (m->plug->security_flags & SASL_SEC_NOANONYMOUS) {
1302	    printf ("%cNO_ANONYMOUS", delimiter);
1303	    delimiter = '|';
1304	}
1305
1306	if (m->plug->security_flags & SASL_SEC_NOPLAINTEXT) {
1307	    printf ("%cNO_PLAINTEXT", delimiter);
1308	    delimiter = '|';
1309	}
1310
1311	if (m->plug->security_flags & SASL_SEC_NOACTIVE) {
1312	    printf ("%cNO_ACTIVE", delimiter);
1313	    delimiter = '|';
1314	}
1315
1316	if (m->plug->security_flags & SASL_SEC_NODICTIONARY) {
1317	    printf ("%cNO_DICTIONARY", delimiter);
1318	    delimiter = '|';
1319	}
1320
1321	if (m->plug->security_flags & SASL_SEC_FORWARD_SECRECY) {
1322	    printf ("%cFORWARD_SECRECY", delimiter);
1323	    delimiter = '|';
1324	}
1325
1326	if (m->plug->security_flags & SASL_SEC_PASS_CREDENTIALS) {
1327	    printf ("%cPASS_CREDENTIALS", delimiter);
1328	    delimiter = '|';
1329	}
1330
1331	if (m->plug->security_flags & SASL_SEC_MUTUAL_AUTH) {
1332	    printf ("%cMUTUAL_AUTH", delimiter);
1333	    delimiter = '|';
1334	}
1335
1336
1337
1338	printf ("\n\tfeatures:");
1339
1340	delimiter = ' ';
1341	if (m->plug->features & SASL_FEAT_WANT_CLIENT_FIRST) {
1342	    printf ("%cWANT_CLIENT_FIRST", delimiter);
1343	    delimiter = '|';
1344	}
1345
1346	if (m->plug->features & SASL_FEAT_SERVER_FIRST) {
1347	    printf ("%cSERVER_FIRST", delimiter);
1348	    delimiter = '|';
1349	}
1350
1351	if (m->plug->features & SASL_FEAT_ALLOWS_PROXY) {
1352	    printf ("%cPROXY_AUTHENTICATION", delimiter);
1353	    delimiter = '|';
1354	}
1355
1356	if (m->plug->features & SASL_FEAT_NEEDSERVERFQDN) {
1357	    printf ("%cNEED_SERVER_FQDN", delimiter);
1358	    delimiter = '|';
1359	}
1360
1361	if (m->plug->features & SASL_FEAT_GSS_FRAMING) {
1362	    printf ("%cGSS_FRAMING", delimiter);
1363	    delimiter = '|';
1364	}
1365
1366	if (m->plug->features & SASL_FEAT_CHANNEL_BINDING) {
1367	    printf ("%cCHANNEL_BINDING", delimiter);
1368	    delimiter = '|';
1369	}
1370
1371	if (m->plug->features & SASL_FEAT_SUPPORTS_HTTP) {
1372	    printf ("%cSUPPORTS_HTTP", delimiter);
1373	    delimiter = '|';
1374	}
1375    }
1376
1377/* Delay loading is not supported for the client side plugins:
1378    if (m->f) {
1379	printf ("\n\twill be loaded from \"%s\"", m->f);
1380    }
1381 */
1382
1383    printf ("\n");
1384}
1385
1386
1387/* Dump information about available client plugins */
1388int sasl_client_plugin_info (
1389  const char *c_mech_list,		/* space separated mechanism list or NULL for ALL */
1390  sasl_client_info_callback_t *info_cb,
1391  void *info_cb_rock
1392)
1393{
1394    cmechanism_t *m;
1395    client_sasl_mechanism_t plug_data;
1396    char * cur_mech;
1397    char * mech_list = NULL;
1398    char * p;
1399
1400    if (info_cb == NULL) {
1401	info_cb = _sasl_print_mechanism;
1402    }
1403
1404    if (cmechlist != NULL) {
1405	info_cb (NULL, SASL_INFO_LIST_START, info_cb_rock);
1406
1407	if (c_mech_list == NULL) {
1408	    m = cmechlist->mech_list; /* m point to beginning of the list */
1409
1410	    while (m != NULL) {
1411		memcpy (&plug_data, &m->m, sizeof(plug_data));
1412
1413		info_cb (&plug_data, SASL_INFO_LIST_MECH, info_cb_rock);
1414
1415		m = m->next;
1416	    }
1417	} else {
1418            mech_list = strdup (c_mech_list);
1419
1420	    cur_mech = mech_list;
1421
1422	    while (cur_mech != NULL) {
1423		p = strchr (cur_mech, ' ');
1424		if (p != NULL) {
1425		    *p = '\0';
1426		    p++;
1427		}
1428
1429		m = cmechlist->mech_list; /* m point to beginning of the list */
1430
1431		while (m != NULL) {
1432		    if (strcasecmp (cur_mech, m->m.plug->mech_name) == 0) {
1433			memcpy (&plug_data, &m->m, sizeof(plug_data));
1434
1435			info_cb (&plug_data, SASL_INFO_LIST_MECH, info_cb_rock);
1436		    }
1437
1438		    m = m->next;
1439		}
1440
1441		cur_mech = p;
1442	    }
1443
1444            free (mech_list);
1445	}
1446
1447	info_cb (NULL, SASL_INFO_LIST_END, info_cb_rock);
1448
1449	return (SASL_OK);
1450    }
1451
1452    return (SASL_NOTINIT);
1453}
1454