1 /* SASL client API implementation
2  * Rob Siemborski
3  * Tim Martin
4  */
5 /*
6  * Copyright (c) 1998-2016 Carnegie Mellon University.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  *
20  * 3. The name "Carnegie Mellon University" must not be used to
21  *    endorse or promote products derived from this software without
22  *    prior written permission. For permission or any other legal
23  *    details, please contact
24  *      Carnegie Mellon University
25  *      Center for Technology Transfer and Enterprise Creation
26  *      4615 Forbes Avenue
27  *      Suite 302
28  *      Pittsburgh, PA  15213
29  *      (412) 268-7393, fax: (412) 268-7395
30  *      innovation@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 
62 static cmech_list_t *cmechlist; /* global var which holds the list */
63 static sasl_global_callbacks_t global_callbacks_client;
64 static int _sasl_client_active = 0;
65 
init_mechlist()66 static 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 
sasl_client_done(void)78 int 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 
client_done(void)106 static 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. */
mech_compare(const sasl_client_plug_t * a,const sasl_client_plug_t * b)144 static 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 #ifdef PREFER_MECH
154     if (!strcasecmp(a->mech_name, PREFER_MECH)) return 1;
155     if (!strcasecmp(b->mech_name, PREFER_MECH)) return -1;
156 #endif
157 
158     sec_diff = a->security_flags ^ b->security_flags;
159     if (sec_diff & a->security_flags & SASL_SEC_NOANONYMOUS) return 1;
160     if (sec_diff & b->security_flags & SASL_SEC_NOANONYMOUS) return -1;
161     if (sec_diff & a->security_flags & SASL_SEC_NOPLAINTEXT) return 1;
162     if (sec_diff & b->security_flags & SASL_SEC_NOPLAINTEXT) return -1;
163     if (sec_diff & a->security_flags & SASL_SEC_MUTUAL_AUTH) return 1;
164     if (sec_diff & b->security_flags & SASL_SEC_MUTUAL_AUTH) return -1;
165     if (sec_diff & a->security_flags & SASL_SEC_NOACTIVE) return 1;
166     if (sec_diff & b->security_flags & SASL_SEC_NOACTIVE) return -1;
167     if (sec_diff & a->security_flags & SASL_SEC_NODICTIONARY) return 1;
168     if (sec_diff & b->security_flags & SASL_SEC_NODICTIONARY) return -1;
169     if (sec_diff & a->security_flags & SASL_SEC_FORWARD_SECRECY) return 1;
170     if (sec_diff & b->security_flags & SASL_SEC_FORWARD_SECRECY) return -1;
171 
172     features_diff = a->features ^ b->features;
173     if (features_diff & a->features & SASL_FEAT_CHANNEL_BINDING) return 1;
174     if (features_diff & b->features & SASL_FEAT_CHANNEL_BINDING) return -1;
175 
176     if (a->max_ssf > b->max_ssf) return 1;
177     if (a->max_ssf < b->max_ssf) return -1;
178 
179     return 0;
180 }
181 
sasl_client_add_plugin(const char * plugname,sasl_client_plug_init_t * entry_point)182 int sasl_client_add_plugin(const char *plugname,
183 			   sasl_client_plug_init_t *entry_point)
184 {
185     int plugcount;
186     sasl_client_plug_t *pluglist;
187     cmechanism_t *mech, *mp;
188     int result;
189     int version;
190     int lupe;
191 
192     if (!plugname || !entry_point) return SASL_BADPARAM;
193 
194     result = entry_point(cmechlist->utils,
195 			 SASL_CLIENT_PLUG_VERSION,
196 			 &version,
197 			 &pluglist,
198 			 &plugcount);
199 
200     if (result != SASL_OK)
201     {
202 	_sasl_log(NULL, SASL_LOG_WARN,
203 		  "sasl_client_add_plugin(): entry_point(): failed for plugname %s: %z",
204 		  plugname, result);
205 	return result;
206     }
207 
208     if (version != SASL_CLIENT_PLUG_VERSION)
209     {
210 	_sasl_log(NULL, SASL_LOG_WARN,
211 	      "version conflict in sasl_client_add_plugin for %s", plugname);
212 	return SASL_BADVERS;
213     }
214 
215     for (lupe=0; lupe < plugcount; lupe++, pluglist++)
216     {
217 	mech = sasl_ALLOC(sizeof(cmechanism_t));
218 	if (!mech) return SASL_NOMEM;
219 
220 	mech->m.plug = pluglist;
221 	if (_sasl_strdup(plugname, &mech->m.plugname, NULL) != SASL_OK) {
222 	    sasl_FREE(mech);
223 	    return SASL_NOMEM;
224 	}
225 	mech->m.version = version;
226 
227 	/* sort mech_list by relative "strength" */
228 	mp = cmechlist->mech_list;
229 	if (!mp || mech_compare(pluglist, mp->m.plug) >= 0) {
230 	    /* add mech to head of list */
231 	    mech->next = cmechlist->mech_list;
232 	    cmechlist->mech_list = mech;
233 	} else {
234 	    /* find where to insert mech into list */
235 	    while (mp->next &&
236 		   mech_compare(pluglist, mp->next->m.plug) <= 0) mp = mp->next;
237 	    mech->next = mp->next;
238 	    mp->next = mech;
239 	}
240 
241 	cmechlist->mech_length++;
242     }
243 
244     return SASL_OK;
245 }
246 
247 static int
client_idle(sasl_conn_t * conn)248 client_idle(sasl_conn_t *conn)
249 {
250   cmechanism_t *m;
251   if (! cmechlist)
252     return 0;
253 
254   for (m = cmechlist->mech_list;
255        m;
256        m = m->next)
257     if (m->m.plug->idle
258 	&&  m->m.plug->idle(m->m.plug->glob_context,
259 			  conn,
260 			  conn ? ((sasl_client_conn_t *)conn)->cparams : NULL))
261       return 1;
262   return 0;
263 }
264 
265 /* initialize the SASL client drivers
266  *  callbacks      -- base callbacks for all client connections
267  * returns:
268  *  SASL_OK        -- Success
269  *  SASL_NOMEM     -- Not enough memory
270  *  SASL_BADVERS   -- Mechanism version mismatch
271  *  SASL_BADPARAM  -- error in config file
272  *  SASL_NOMECH    -- No mechanisms available
273  *  ...
274  */
275 
sasl_client_init(const sasl_callback_t * callbacks)276 int sasl_client_init(const sasl_callback_t *callbacks)
277 {
278   int ret;
279   const add_plugin_list_t ep_list[] = {
280       { "sasl_client_plug_init", (add_plugin_t *)sasl_client_add_plugin },
281       { "sasl_canonuser_init", (add_plugin_t *)sasl_canonuser_add_plugin },
282       { NULL, NULL }
283   };
284 
285   /* lock allocation type */
286   _sasl_allocation_locked++;
287 
288   if(_sasl_client_active) {
289       /* We're already active, just increase our refcount */
290       /* xxx do something with the callback structure? */
291       _sasl_client_active++;
292       return SASL_OK;
293   }
294 
295   global_callbacks_client.callbacks = callbacks;
296   global_callbacks_client.appname = NULL;
297 
298   cmechlist=sasl_ALLOC(sizeof(cmech_list_t));
299   if (cmechlist==NULL) return SASL_NOMEM;
300 
301   /* We need to call client_done if we fail now */
302   _sasl_client_active = 1;
303 
304   /* load plugins */
305   ret=init_mechlist();
306   if (ret!=SASL_OK) {
307       client_done();
308       return ret;
309   }
310 
311   sasl_client_add_plugin("EXTERNAL", &external_client_plug_init);
312 
313   ret = _sasl_common_init(&global_callbacks_client);
314 
315   if (ret == SASL_OK)
316       ret = _sasl_load_plugins(ep_list,
317 			       _sasl_find_getpath_callback(callbacks),
318 			       _sasl_find_verifyfile_callback(callbacks));
319 
320   if (ret == SASL_OK) {
321       _sasl_client_cleanup_hook = &client_done;
322       _sasl_client_idle_hook = &client_idle;
323 
324       ret = _sasl_build_mechlist();
325   } else {
326       client_done();
327   }
328 
329   return ret;
330 }
331 
client_dispose(sasl_conn_t * pconn)332 static void client_dispose(sasl_conn_t *pconn)
333 {
334   sasl_client_conn_t *c_conn=(sasl_client_conn_t *) pconn;
335 
336   if (c_conn->mech && c_conn->mech->m.plug->mech_dispose) {
337     c_conn->mech->m.plug->mech_dispose(pconn->context,
338 				     c_conn->cparams->utils);
339   }
340 
341   pconn->context = NULL;
342 
343   if (c_conn->clientFQDN)
344       sasl_FREE(c_conn->clientFQDN);
345 
346   if (c_conn->cparams) {
347       _sasl_free_utils(&(c_conn->cparams->utils));
348       sasl_FREE(c_conn->cparams);
349   }
350 
351   if (c_conn->mech_list != cmechlist->mech_list) {
352       /* free connection-specific mech_list */
353       cmechanism_t *m, *prevm;
354 
355       m = c_conn->mech_list; /* m point to beginning of the list */
356 
357       while (m) {
358 	  prevm = m;
359 	  m = m->next;
360 	  sasl_FREE(prevm);
361       }
362   }
363 
364   _sasl_conn_dispose(pconn);
365 }
366 
367 /* initialize a client exchange based on the specified mechanism
368  *  service       -- registered name of the service using SASL (e.g. "imap")
369  *  serverFQDN    -- the fully qualified domain name of the server
370  *  iplocalport   -- client IPv4/IPv6 domain literal string with port
371  *                    (if NULL, then mechanisms requiring IPaddr are disabled)
372  *  ipremoteport  -- server IPv4/IPv6 domain literal string with port
373  *                    (if NULL, then mechanisms requiring IPaddr are disabled)
374  *  prompt_supp   -- list of client interactions supported
375  *                   may also include sasl_getopt_t context & call
376  *                   NULL prompt_supp = user/pass via SASL_INTERACT only
377  *                   NULL proc = interaction supported via SASL_INTERACT
378  *  secflags      -- security flags (see above)
379  * in/out:
380  *  pconn         -- connection negotiation structure
381  *                   pointer to NULL => allocate new
382  *                   non-NULL => recycle storage and go for next available mech
383  *
384  * Returns:
385  *  SASL_OK       -- success
386  *  SASL_NOMECH   -- no mechanism meets requested properties
387  *  SASL_NOMEM    -- not enough memory
388  */
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)389 int sasl_client_new(const char *service,
390 		    const char *serverFQDN,
391 		    const char *iplocalport,
392 		    const char *ipremoteport,
393 		    const sasl_callback_t *prompt_supp,
394 		    unsigned flags,
395 		    sasl_conn_t **pconn)
396 {
397   int result;
398   char name[MAXHOSTNAMELEN];
399   sasl_client_conn_t *conn;
400   sasl_utils_t *utils;
401   sasl_getopt_t *getopt;
402   void *context;
403   const char *mlist = NULL;
404   int plus = 0;
405 
406   if (_sasl_client_active == 0) return SASL_NOTINIT;
407 
408   /* Remember, serverFQDN, iplocalport and ipremoteport can be NULL and be valid! */
409   if (!pconn || !service)
410     return SASL_BADPARAM;
411 
412   *pconn=sasl_ALLOC(sizeof(sasl_client_conn_t));
413   if (*pconn==NULL) {
414       _sasl_log(NULL, SASL_LOG_ERR,
415 		"Out of memory allocating connection context");
416       return SASL_NOMEM;
417   }
418   memset(*pconn, 0, sizeof(sasl_client_conn_t));
419 
420   (*pconn)->destroy_conn = &client_dispose;
421 
422   conn = (sasl_client_conn_t *)*pconn;
423 
424   conn->mech = NULL;
425 
426   conn->cparams=sasl_ALLOC(sizeof(sasl_client_params_t));
427   if (conn->cparams==NULL)
428       MEMERROR(*pconn);
429   memset(conn->cparams,0,sizeof(sasl_client_params_t));
430 
431   result = _sasl_conn_init(*pconn, service, flags, SASL_CONN_CLIENT,
432 			   &client_idle, serverFQDN,
433 			   iplocalport, ipremoteport,
434 			   prompt_supp, &global_callbacks_client);
435   if (result != SASL_OK) RETURN(*pconn, result);
436 
437   utils = _sasl_alloc_utils(*pconn, &global_callbacks_client);
438   if (utils == NULL) {
439       MEMERROR(*pconn);
440   }
441 
442   utils->conn= *pconn;
443   conn->cparams->utils = utils;
444 
445   if(_sasl_getcallback(*pconn, SASL_CB_GETOPT, (sasl_callback_ft *)&getopt, &context) == SASL_OK) {
446     getopt(context, NULL, "client_mech_list", &mlist, NULL);
447   }
448 
449   /* if we have a client_mech_list, create ordered list of
450      available mechanisms for this conn */
451   if (mlist) {
452       const char *cp;
453       cmechanism_t *mptr, *tail = NULL;
454       cmechanism_t *new;
455 
456       while (*mlist) {
457 	  /* find end of current mech name */
458 	  for (cp = mlist; *cp && !isspace((int) *cp); cp++);
459 
460 	  /* search for mech name in loaded plugins */
461 	  for (mptr = cmechlist->mech_list; mptr; mptr = mptr->next) {
462 	      const sasl_client_plug_t *plug = mptr->m.plug;
463 
464 	      if (_sasl_is_equal_mech(mlist, plug->mech_name, (size_t) (cp - mlist), &plus)) {
465 		  /* found a match */
466 		  break;
467 	      }
468 	  }
469 	  if (mptr) {
470 	      new = sasl_ALLOC(sizeof(cmechanism_t));
471 	      if (!new) {
472 		  result = SASL_NOMEM;
473 		  goto failed_client_new;
474 	      }
475 	      memcpy(&new->m, &mptr->m, sizeof(client_sasl_mechanism_t));
476 	      new->next = NULL;
477 
478 	      if (!conn->mech_list) {
479 		  conn->mech_list = new;
480 		  tail = conn->mech_list;
481 	      } else {
482 		  if (tail)
483                     tail->next = new;
484 		  tail = new;
485 	      }
486 	      conn->mech_length++;
487 	  }
488 
489 	  /* find next mech name */
490 	  mlist = cp;
491 	  while (*mlist && isspace((int) *mlist)) mlist++;
492       }
493   } else {
494       conn->mech_list = cmechlist->mech_list;
495       conn->mech_length = cmechlist->mech_length;
496   }
497 
498   if (conn->mech_list == NULL) {
499       sasl_seterror(*pconn, 0, "No worthy mechs found");
500       result = SASL_NOMECH;
501       goto failed_client_new;
502   }
503 
504   /* Setup the non-lazy parts of cparams, the rest is done in
505    * sasl_client_start */
506   conn->cparams->canon_user = &_sasl_canon_user_lookup;
507   conn->cparams->flags = flags;
508   conn->cparams->prompt_supp = (*pconn)->callbacks;
509 
510   /* get the clientFQDN (serverFQDN was set in _sasl_conn_init) */
511   memset(name, 0, sizeof(name));
512   if (get_fqhostname (name, MAXHOSTNAMELEN, 0) != 0) {
513       return (SASL_FAIL);
514   }
515 
516   result = _sasl_strdup(name, &conn->clientFQDN, NULL);
517 
518   if (result == SASL_OK) return SASL_OK;
519 
520 failed_client_new:
521   /* result isn't SASL_OK */
522   _sasl_conn_dispose(*pconn);
523   sasl_FREE(*pconn);
524   *pconn = NULL;
525   _sasl_log(NULL, SASL_LOG_ERR, "Out of memory in sasl_client_new");
526   return result;
527 }
528 
have_prompts(sasl_conn_t * conn,const sasl_client_plug_t * mech)529 static int have_prompts(sasl_conn_t *conn,
530 			const sasl_client_plug_t *mech)
531 {
532   static const unsigned long default_prompts[] = {
533     SASL_CB_AUTHNAME,
534     SASL_CB_PASS,
535     SASL_CB_LIST_END
536   };
537 
538   const unsigned long *prompt;
539   sasl_callback_ft pproc;
540   void *pcontext;
541   int result;
542 
543   for (prompt = (mech->required_prompts
544 		 ? mech->required_prompts :
545 		 default_prompts);
546        *prompt != SASL_CB_LIST_END;
547        prompt++) {
548     result = _sasl_getcallback(conn, *prompt, &pproc, &pcontext);
549     if (result != SASL_OK && result != SASL_INTERACT)
550       return 0;			/* we don't have this required prompt */
551   }
552 
553   return 1; /* we have all the prompts */
554 }
555 
556 static int
_mech_plus_p(const char * mech,size_t len)557 _mech_plus_p(const char *mech, size_t len)
558 {
559     return (len > 5 && strncasecmp(&mech[len - 5], "-PLUS", 5) == 0);
560 }
561 
562 /*
563  * Order PLUS mechanisms first. Returns NUL separated list of
564  * *count items.
565  */
566 static int
_sasl_client_order_mechs(const sasl_utils_t * utils,const char * mechs,int has_cb_data,char ** ordered_mechs,size_t * count,int * server_can_cb)567 _sasl_client_order_mechs(const sasl_utils_t *utils,
568 			 const char *mechs,
569 			 int has_cb_data,
570 			 char **ordered_mechs,
571 			 size_t *count,
572 			 int *server_can_cb)
573 {
574     char *list, *listp;
575     size_t i, mechslen, start;
576 
577     *count = 0;
578     *server_can_cb = 0;
579 
580     if (mechs == NULL || mechs[0] == '\0')
581         return SASL_NOMECH;
582 
583     mechslen = strlen(mechs);
584 
585     listp = list = utils->malloc(mechslen + 1);
586     if (list == NULL)
587 	return SASL_NOMEM;
588 
589     /* As per RFC 4422:
590      * SASL mechanism allowable characters are "AZ-_"
591      * separators can be any other characters and of any length
592      * even variable lengths between.
593      *
594      * But for convenience we accept lowercase ASCII.
595      *
596      * Apps should be encouraged to simply use space or comma space
597      * though
598      */
599 #define ismechchar(c)   (isalnum((c)) || (c) == '_' || (c) == '-')
600     do {
601         for (i = start = 0; i <= mechslen; i++) {
602 	    if (!ismechchar(mechs[i])) {
603                 const char *mechp = &mechs[start];
604 		size_t len = i - start;
605 
606 		if (len != 0 &&
607                     _mech_plus_p(mechp, len) == has_cb_data) {
608 		    memcpy(listp, mechp, len);
609 		    listp[len] = '\0';
610 		    listp += len + 1;
611 		    (*count)++;
612 		    if (*server_can_cb == 0 && has_cb_data)
613 			*server_can_cb = 1;
614 		}
615 		start = i+1;
616 	    }
617 	}
618 	if (has_cb_data)
619 	    has_cb_data = 0;
620 	else
621 	    break;
622     } while (1);
623 
624     if (*count == 0) {
625         utils->free(list);
626         return SASL_NOMECH;
627     }
628 
629     *ordered_mechs = list;
630 
631     return SASL_OK;
632 }
633 
634 static INLINE int
_sasl_cbinding_disp(sasl_client_params_t * cparams,int mech_nego,int server_can_cb,sasl_cbinding_disp_t * cbindingdisp)635 _sasl_cbinding_disp(sasl_client_params_t *cparams,
636                     int mech_nego,
637                     int server_can_cb,
638                     sasl_cbinding_disp_t *cbindingdisp)
639 {
640     /*
641      * If negotiating mechanisms, then we fail immediately if the
642      * client requires channel binding and the server does not
643      * advertise support. Otherwise we send "y" (which later will
644      * become "p" if we select a supporting mechanism).
645      *
646      * If the client explicitly selected a mechanism, then we only
647      * send channel bindings if they're marked critical.
648      */
649 
650     *cbindingdisp = SASL_CB_DISP_NONE;
651 
652     if (SASL_CB_PRESENT(cparams)) {
653         if (mech_nego) {
654 	    if (!server_can_cb && SASL_CB_CRITICAL(cparams)) {
655 	        return SASL_NOMECH;
656 	    } else {
657                 *cbindingdisp = SASL_CB_DISP_WANT;
658 	    }
659         } else if (SASL_CB_CRITICAL(cparams)) {
660             *cbindingdisp = SASL_CB_DISP_USED;
661         }
662     }
663 
664     return SASL_OK;
665 }
666 
667 /* select a mechanism for a connection
668  *  mechlist      -- mechanisms server has available (punctuation ignored)
669  *  secret        -- optional secret from previous session
670  * output:
671  *  prompt_need   -- on SASL_INTERACT, list of prompts needed to continue
672  *  clientout     -- the initial client response to send to the server
673  *  mech          -- set to mechanism name
674  *
675  * Returns:
676  *  SASL_OK       -- success
677  *  SASL_NOMEM    -- not enough memory
678  *  SASL_NOMECH   -- no mechanism meets requested properties
679  *  SASL_INTERACT -- user interaction needed to fill in prompt_need list
680  */
681 
682 /*
683  * SASL mechanism allowable characters are "AZ-_"
684  * separators can be any other characters and of any length
685  * even variable lengths between.
686  *
687  * But for convenience we accept lowercase ASCII.
688  *
689  * Apps should be encouraged to simply use space or comma space
690  * though
691  */
sasl_client_start(sasl_conn_t * conn,const char * mechlist,sasl_interact_t ** prompt_need,const char ** clientout,unsigned * clientoutlen,const char ** mech)692 int sasl_client_start(sasl_conn_t *conn,
693 		      const char *mechlist,
694 		      sasl_interact_t **prompt_need,
695 		      const char **clientout,
696 		      unsigned *clientoutlen,
697 		      const char **mech)
698 {
699     sasl_client_conn_t *c_conn = (sasl_client_conn_t *) conn;
700     char *ordered_mechs = NULL, *name;
701     cmechanism_t *m = NULL, *bestm = NULL;
702     size_t i, list_len, name_len;
703     sasl_ssf_t minssf = 0;
704     int result, server_can_cb = 0;
705     sasl_cbinding_disp_t cbindingdisp;
706     sasl_cbinding_disp_t cur_cbindingdisp;
707     sasl_cbinding_disp_t best_cbindingdisp = SASL_CB_DISP_NONE;
708 
709     if (_sasl_client_active == 0) return SASL_NOTINIT;
710 
711     if (!conn) return SASL_BADPARAM;
712 
713     /* verify parameters */
714     if (mechlist == NULL) {
715 	PARAMERROR(conn);
716     }
717 
718     /* if prompt_need != NULL we've already been here
719        and just need to do the continue step again */
720 
721     /* do a step */
722     /* FIXME: Hopefully they only give us our own prompt_need back */
723     if (prompt_need && *prompt_need != NULL) {
724 	goto dostep;
725     }
726 
727     if (conn->props.min_ssf < conn->external.ssf) {
728 	minssf = 0;
729     } else {
730 	minssf = conn->props.min_ssf - conn->external.ssf;
731     }
732 
733     /* Order mechanisms so -PLUS are preferred */
734     result = _sasl_client_order_mechs(c_conn->cparams->utils,
735 				      mechlist,
736 				      SASL_CB_PRESENT(c_conn->cparams),
737 				      &ordered_mechs,
738 				      &list_len,
739 				      &server_can_cb);
740     if (result != 0)
741 	goto done;
742 
743     /*
744      * Determine channel binding disposition based on whether we
745      * are doing mechanism negotiation and whether server supports
746      * channel bindings.
747      */
748     result = _sasl_cbinding_disp(c_conn->cparams,
749 				 (list_len > 1),
750                                  server_can_cb,
751 				 &cbindingdisp);
752     if (result != 0)
753 	goto done;
754 
755     /* for each mechanism in client's list */
756     for (m = c_conn->mech_list; !bestm && m != NULL; m = m->next) {
757 
758         for (i = 0, name = ordered_mechs; i < list_len; i++, name += name_len + 1) {
759             unsigned myflags;
760             int plus;
761 
762             name_len = strlen(name);
763 
764 	    if (!_sasl_is_equal_mech(name, m->m.plug->mech_name, name_len, &plus)) {
765 		continue;
766 	    }
767 
768 	    /* Do we have the prompts for it? */
769 	    if (!have_prompts(conn, m->m.plug))
770 		break;
771 
772 	    /* Is it strong enough? */
773 	    if (minssf > m->m.plug->max_ssf)
774 		break;
775 
776 	    myflags = conn->props.security_flags;
777 
778 	    /* if there's an external layer with a better SSF then this is no
779 	     * longer considered a plaintext mechanism
780 	     */
781 	    if ((conn->props.min_ssf <= conn->external.ssf) &&
782 		(conn->external.ssf > 1)) {
783 		myflags &= ~SASL_SEC_NOPLAINTEXT;
784 	    }
785 
786 	    /* Does it meet our security properties? */
787 	    if (((myflags ^ m->m.plug->security_flags) & myflags) != 0) {
788 		break;
789 	    }
790 
791 	    /* Can we meet it's features? */
792 	    if (cbindingdisp == SASL_CB_DISP_USED &&
793 		!(m->m.plug->features & SASL_FEAT_CHANNEL_BINDING)) {
794 		break;
795 	    }
796 
797 	    if ((m->m.plug->features & SASL_FEAT_NEEDSERVERFQDN)
798 		&& !conn->serverFQDN) {
799 		break;
800 	    }
801 
802 	    /* Can it meet our features? */
803 	    if ((conn->flags & SASL_NEED_PROXY) &&
804 		!(m->m.plug->features & SASL_FEAT_ALLOWS_PROXY)) {
805 		break;
806 	    }
807 
808 	    if ((conn->flags & SASL_NEED_HTTP) &&
809 		!(m->m.plug->features & SASL_FEAT_SUPPORTS_HTTP)) {
810 		break;
811 	    }
812 
813 	    if (SASL_CB_PRESENT(c_conn->cparams) && plus) {
814 		cur_cbindingdisp = SASL_CB_DISP_USED;
815 	    } else {
816 		cur_cbindingdisp = cbindingdisp;
817 	    }
818 
819 	    if (mech) {
820 		*mech = m->m.plug->mech_name;
821 	    }
822 
823             /* Since the list of client mechs is ordered by preference/strength,
824                the first mech in our list that is available on the server and
825                meets our security properties and features is the "best" */
826 	    best_cbindingdisp = cur_cbindingdisp;
827 	    bestm = m;
828 	    break;
829 	}
830     }
831 
832     if (bestm == NULL) {
833 	sasl_seterror(conn, 0, "No worthy mechs found");
834 	result = SASL_NOMECH;
835 	goto done;
836     }
837 
838     /* make (the rest of) cparams */
839     c_conn->cparams->service = conn->service;
840     c_conn->cparams->servicelen = (unsigned) strlen(conn->service);
841 
842     if (conn->serverFQDN) {
843 	c_conn->cparams->serverFQDN = conn->serverFQDN;
844 	c_conn->cparams->slen = (unsigned) strlen(conn->serverFQDN);
845     }
846 
847     c_conn->cparams->clientFQDN = c_conn->clientFQDN;
848     c_conn->cparams->clen = (unsigned) strlen(c_conn->clientFQDN);
849 
850     c_conn->cparams->external_ssf = conn->external.ssf;
851     c_conn->cparams->props = conn->props;
852     c_conn->cparams->cbindingdisp = best_cbindingdisp;
853     c_conn->mech = bestm;
854 
855     /* init that plugin */
856     result = c_conn->mech->m.plug->mech_new(c_conn->mech->m.plug->glob_context,
857 					  c_conn->cparams,
858 					  &(conn->context));
859     if (result != SASL_OK) goto done;
860 
861     /* do a step -- but only if we can do a client-send-first */
862  dostep:
863     if(clientout) {
864         if(c_conn->mech->m.plug->features & SASL_FEAT_SERVER_FIRST) {
865             *clientout = NULL;
866             *clientoutlen = 0;
867             result = SASL_CONTINUE;
868         } else {
869             result = sasl_client_step(conn, NULL, 0, prompt_need,
870                                       clientout, clientoutlen);
871         }
872     }
873     else
874 	result = SASL_CONTINUE;
875 
876  done:
877     if (ordered_mechs != NULL)
878 	c_conn->cparams->utils->free(ordered_mechs);
879     RETURN(conn, result);
880 }
881 
882 /* do a single authentication step.
883  *  serverin    -- the server message received by the client, MUST have a NUL
884  *                 sentinel, not counted by serverinlen
885  * output:
886  *  prompt_need -- on SASL_INTERACT, list of prompts needed to continue
887  *  clientout   -- the client response to send to the server
888  *
889  * returns:
890  *  SASL_OK        -- success
891  *  SASL_INTERACT  -- user interaction needed to fill in prompt_need list
892  *  SASL_BADPROT   -- server protocol incorrect/cancelled
893  *  SASL_BADSERV   -- server failed mutual auth
894  */
895 
sasl_client_step(sasl_conn_t * conn,const char * serverin,unsigned serverinlen,sasl_interact_t ** prompt_need,const char ** clientout,unsigned * clientoutlen)896 int sasl_client_step(sasl_conn_t *conn,
897 		     const char *serverin,
898 		     unsigned serverinlen,
899 		     sasl_interact_t **prompt_need,
900 		     const char **clientout,
901 		     unsigned *clientoutlen)
902 {
903   sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
904   int result;
905 
906   if (_sasl_client_active == 0) return SASL_NOTINIT;
907   if (!conn) return SASL_BADPARAM;
908 
909   /* check parameters */
910   if ((serverin==NULL) && (serverinlen>0))
911       PARAMERROR(conn);
912 
913   /* Don't do another step if the plugin told us that we're done */
914   if (conn->oparams.doneflag) {
915       _sasl_log(conn, SASL_LOG_ERR, "attempting client step after doneflag");
916       return SASL_FAIL;
917   }
918 
919   if(clientout) *clientout = NULL;
920   if(clientoutlen) *clientoutlen = 0;
921 
922   /* do a step */
923   result = c_conn->mech->m.plug->mech_step(conn->context,
924 					 c_conn->cparams,
925 					 serverin,
926 					 serverinlen,
927 					 prompt_need,
928 					 clientout, clientoutlen,
929 					 &conn->oparams);
930 
931   if (result == SASL_OK) {
932       /* So we're done on this end, but if both
933        * 1. the mech does server-send-last
934        * 2. the protocol does not
935        * we need to return no data */
936       if(!*clientout && !(conn->flags & SASL_SUCCESS_DATA)) {
937 	  *clientout = "";
938 	  *clientoutlen = 0;
939       }
940 
941       if(!conn->oparams.maxoutbuf) {
942 	  conn->oparams.maxoutbuf = conn->props.maxbufsize;
943       }
944 
945       if(conn->oparams.user == NULL || conn->oparams.authid == NULL) {
946 	  sasl_seterror(conn, 0,
947 			"mech did not call canon_user for both authzid and authid");
948 	  result = SASL_BADPROT;
949       }
950   }
951 
952   RETURN(conn,result);
953 }
954 
955 /* returns the length of all the mechanisms
956  * added up
957  */
958 
mech_names_len(cmechanism_t * mech_list)959 static unsigned mech_names_len(cmechanism_t *mech_list)
960 {
961   cmechanism_t *listptr;
962   unsigned result = 0;
963 
964   for (listptr = mech_list;
965        listptr;
966        listptr = listptr->next)
967     result += (unsigned) strlen(listptr->m.plug->mech_name);
968 
969   return result;
970 }
971 
972 
_sasl_client_listmech(sasl_conn_t * conn,const char * prefix,const char * sep,const char * suffix,const char ** result,unsigned * plen,int * pcount)973 int _sasl_client_listmech(sasl_conn_t *conn,
974 			  const char *prefix,
975 			  const char *sep,
976 			  const char *suffix,
977 			  const char **result,
978 			  unsigned *plen,
979 			  int *pcount)
980 {
981     sasl_client_conn_t *c_conn = (sasl_client_conn_t *)conn;
982     cmechanism_t *m = NULL;
983     sasl_ssf_t minssf = 0;
984     int ret;
985     size_t resultlen;
986     int flag;
987     const char *mysep;
988 
989     if (_sasl_client_active == 0) return SASL_NOTINIT;
990     if (!conn) return SASL_BADPARAM;
991     if (conn->type != SASL_CONN_CLIENT) PARAMERROR(conn);
992 
993     if (! result)
994 	PARAMERROR(conn);
995 
996     if (plen != NULL)
997 	*plen = 0;
998     if (pcount != NULL)
999 	*pcount = 0;
1000 
1001     if (sep) {
1002 	mysep = sep;
1003     } else {
1004 	mysep = " ";
1005     }
1006 
1007     if (conn->props.min_ssf < conn->external.ssf) {
1008 	minssf = 0;
1009     } else {
1010 	minssf = conn->props.min_ssf - conn->external.ssf;
1011     }
1012 
1013     if (!c_conn->mech_list || c_conn->mech_length <= 0) {
1014 	INTERROR(conn, SASL_NOMECH);
1015     }
1016 
1017     resultlen = (prefix ? strlen(prefix) : 0)
1018 	+ (strlen(mysep) * (c_conn->mech_length - 1))
1019 	+ mech_names_len(c_conn->mech_list)
1020 	+ (suffix ? strlen(suffix) : 0)
1021 	+ 1;
1022     ret = _buf_alloc(&conn->mechlist_buf,
1023 		     &conn->mechlist_buf_len,
1024 		     resultlen);
1025     if (ret != SASL_OK) MEMERROR(conn);
1026 
1027     if (prefix) {
1028 	strcpy (conn->mechlist_buf,prefix);
1029     } else {
1030 	*(conn->mechlist_buf) = '\0';
1031     }
1032 
1033     flag = 0;
1034     for (m = c_conn->mech_list; m != NULL; m = m->next) {
1035 	    /* do we have the prompts for it? */
1036 	    if (!have_prompts(conn, m->m.plug)) {
1037 		continue;
1038 	    }
1039 
1040 	    /* is it strong enough? */
1041 	    if (minssf > m->m.plug->max_ssf) {
1042 		continue;
1043 	    }
1044 
1045 	    /* does it meet our security properties? */
1046 	    if (((conn->props.security_flags ^ m->m.plug->security_flags)
1047 		 & conn->props.security_flags) != 0) {
1048 		continue;
1049 	    }
1050 
1051 	    /* Can we meet it's features? */
1052 	    if ((m->m.plug->features & SASL_FEAT_NEEDSERVERFQDN)
1053 		&& !conn->serverFQDN) {
1054 		continue;
1055 	    }
1056 
1057 	    /* Can it meet our features? */
1058 	    if ((conn->flags & SASL_NEED_PROXY) &&
1059 		!(m->m.plug->features & SASL_FEAT_ALLOWS_PROXY)) {
1060 		continue;
1061 	    }
1062 
1063 	    /* Okay, we like it, add it to the list! */
1064 
1065 	    if (pcount != NULL)
1066 		(*pcount)++;
1067 
1068 	    /* print seperator */
1069 	    if (flag) {
1070 		strcat(conn->mechlist_buf, mysep);
1071 	    } else {
1072 		flag = 1;
1073 	    }
1074 
1075 	    /* now print the mechanism name */
1076 	    strcat(conn->mechlist_buf, m->m.plug->mech_name);
1077     }
1078 
1079   if (suffix)
1080       strcat(conn->mechlist_buf,suffix);
1081 
1082   if (plen!=NULL)
1083       *plen = (unsigned) strlen(conn->mechlist_buf);
1084 
1085   *result = conn->mechlist_buf;
1086 
1087   return SASL_OK;
1088 }
1089 
_sasl_client_mechs(void)1090 sasl_string_list_t *_sasl_client_mechs(void)
1091 {
1092   cmechanism_t *listptr;
1093   sasl_string_list_t *retval = NULL, *next=NULL;
1094 
1095   if(!_sasl_client_active) return NULL;
1096 
1097   /* make list */
1098   for (listptr = cmechlist->mech_list; listptr; listptr = listptr->next) {
1099       next = sasl_ALLOC(sizeof(sasl_string_list_t));
1100 
1101       if(!next && !retval) return NULL;
1102       else if(!next) {
1103 	  next = retval->next;
1104 	  do {
1105 	      sasl_FREE(retval);
1106 	      retval = next;
1107 	      next = retval->next;
1108 	  } while(next);
1109 	  return NULL;
1110       }
1111 
1112       next->d = listptr->m.plug->mech_name;
1113 
1114       if(!retval) {
1115 	  next->next = NULL;
1116 	  retval = next;
1117       } else {
1118 	  next->next = retval;
1119 	  retval = next;
1120       }
1121   }
1122 
1123   return retval;
1124 }
1125 
1126 
1127 
1128 
1129 /* It would be nice if we can show other information like Author, Company, Year, plugin version */
1130 static void
_sasl_print_mechanism(client_sasl_mechanism_t * m,sasl_info_callback_stage_t stage,void * rock)1131 _sasl_print_mechanism (
1132   client_sasl_mechanism_t *m,
1133   sasl_info_callback_stage_t stage,
1134   void *rock __attribute__((unused))
1135 )
1136 {
1137     char delimiter;
1138 
1139     if (stage == SASL_INFO_LIST_START) {
1140 	printf ("List of client plugins follows\n");
1141 	return;
1142     } else if (stage == SASL_INFO_LIST_END) {
1143 	return;
1144     }
1145 
1146     /* Process the mechanism */
1147     printf ("Plugin \"%s\" ", m->plugname);
1148 
1149     /* There is no delay loading for client side plugins */
1150     printf ("[loaded]");
1151 
1152     printf (", \tAPI version: %d\n", m->version);
1153 
1154     if (m->plug != NULL) {
1155 	printf ("\tSASL mechanism: %s, best SSF: %d\n",
1156 		m->plug->mech_name,
1157 		m->plug->max_ssf);
1158 
1159 	printf ("\tsecurity flags:");
1160 
1161 	delimiter = ' ';
1162 	if (m->plug->security_flags & SASL_SEC_NOANONYMOUS) {
1163 	    printf ("%cNO_ANONYMOUS", delimiter);
1164 	    delimiter = '|';
1165 	}
1166 
1167 	if (m->plug->security_flags & SASL_SEC_NOPLAINTEXT) {
1168 	    printf ("%cNO_PLAINTEXT", delimiter);
1169 	    delimiter = '|';
1170 	}
1171 
1172 	if (m->plug->security_flags & SASL_SEC_NOACTIVE) {
1173 	    printf ("%cNO_ACTIVE", delimiter);
1174 	    delimiter = '|';
1175 	}
1176 
1177 	if (m->plug->security_flags & SASL_SEC_NODICTIONARY) {
1178 	    printf ("%cNO_DICTIONARY", delimiter);
1179 	    delimiter = '|';
1180 	}
1181 
1182 	if (m->plug->security_flags & SASL_SEC_FORWARD_SECRECY) {
1183 	    printf ("%cFORWARD_SECRECY", delimiter);
1184 	    delimiter = '|';
1185 	}
1186 
1187 	if (m->plug->security_flags & SASL_SEC_PASS_CREDENTIALS) {
1188 	    printf ("%cPASS_CREDENTIALS", delimiter);
1189 	    delimiter = '|';
1190 	}
1191 
1192 	if (m->plug->security_flags & SASL_SEC_MUTUAL_AUTH) {
1193 	    printf ("%cMUTUAL_AUTH", delimiter);
1194 	    delimiter = '|';
1195 	}
1196 
1197 
1198 
1199 	printf ("\n\tfeatures:");
1200 
1201 	delimiter = ' ';
1202 	if (m->plug->features & SASL_FEAT_WANT_CLIENT_FIRST) {
1203 	    printf ("%cWANT_CLIENT_FIRST", delimiter);
1204 	    delimiter = '|';
1205 	}
1206 
1207 	if (m->plug->features & SASL_FEAT_SERVER_FIRST) {
1208 	    printf ("%cSERVER_FIRST", delimiter);
1209 	    delimiter = '|';
1210 	}
1211 
1212 	if (m->plug->features & SASL_FEAT_ALLOWS_PROXY) {
1213 	    printf ("%cPROXY_AUTHENTICATION", delimiter);
1214 	    delimiter = '|';
1215 	}
1216 
1217 	if (m->plug->features & SASL_FEAT_NEEDSERVERFQDN) {
1218 	    printf ("%cNEED_SERVER_FQDN", delimiter);
1219 	    delimiter = '|';
1220 	}
1221 
1222 	if (m->plug->features & SASL_FEAT_GSS_FRAMING) {
1223 	    printf ("%cGSS_FRAMING", delimiter);
1224 	    delimiter = '|';
1225 	}
1226 
1227 	if (m->plug->features & SASL_FEAT_CHANNEL_BINDING) {
1228 	    printf ("%cCHANNEL_BINDING", delimiter);
1229 	    delimiter = '|';
1230 	}
1231 
1232 	if (m->plug->features & SASL_FEAT_SUPPORTS_HTTP) {
1233 	    printf ("%cSUPPORTS_HTTP", delimiter);
1234 	    delimiter = '|';
1235 	}
1236     }
1237 
1238 /* Delay loading is not supported for the client side plugins:
1239     if (m->f) {
1240 	printf ("\n\twill be loaded from \"%s\"", m->f);
1241     }
1242  */
1243 
1244     printf ("\n");
1245 }
1246 
1247 
1248 /* Dump information about available client plugins */
sasl_client_plugin_info(const char * c_mech_list,sasl_client_info_callback_t * info_cb,void * info_cb_rock)1249 int sasl_client_plugin_info (
1250   const char *c_mech_list,		/* space separated mechanism list or NULL for ALL */
1251   sasl_client_info_callback_t *info_cb,
1252   void *info_cb_rock
1253 )
1254 {
1255     cmechanism_t *m;
1256     client_sasl_mechanism_t plug_data;
1257     char * cur_mech;
1258     char * mech_list = NULL;
1259     char * p;
1260 
1261     if (info_cb == NULL) {
1262 	info_cb = _sasl_print_mechanism;
1263     }
1264 
1265     if (cmechlist != NULL) {
1266 	info_cb (NULL, SASL_INFO_LIST_START, info_cb_rock);
1267 
1268 	if (c_mech_list == NULL) {
1269 	    m = cmechlist->mech_list; /* m point to beginning of the list */
1270 
1271 	    while (m != NULL) {
1272 		memcpy (&plug_data, &m->m, sizeof(plug_data));
1273 
1274 		info_cb (&plug_data, SASL_INFO_LIST_MECH, info_cb_rock);
1275 
1276 		m = m->next;
1277 	    }
1278 	} else {
1279             mech_list = strdup (c_mech_list);
1280 
1281 	    cur_mech = mech_list;
1282 
1283 	    while (cur_mech != NULL) {
1284 		p = strchr (cur_mech, ' ');
1285 		if (p != NULL) {
1286 		    *p = '\0';
1287 		    p++;
1288 		}
1289 
1290 		m = cmechlist->mech_list; /* m point to beginning of the list */
1291 
1292 		while (m != NULL) {
1293 		    if (strcasecmp (cur_mech, m->m.plug->mech_name) == 0) {
1294 			memcpy (&plug_data, &m->m, sizeof(plug_data));
1295 
1296 			info_cb (&plug_data, SASL_INFO_LIST_MECH, info_cb_rock);
1297 		    }
1298 
1299 		    m = m->next;
1300 		}
1301 
1302 		cur_mech = p;
1303 	    }
1304 
1305             free (mech_list);
1306 	}
1307 
1308 	info_cb (NULL, SASL_INFO_LIST_END, info_cb_rock);
1309 
1310 	return (SASL_OK);
1311     }
1312 
1313     return (SASL_NOTINIT);
1314 }
1315