1 /* CRAM-MD5 SASL plugin
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 
48 #include <string.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #ifndef macintosh
52 #include <sys/stat.h>
53 #endif
54 #include <fcntl.h>
55 
56 #include <sasl.h>
57 #include <saslplug.h>
58 #include <saslutil.h>
59 
60 #include "plugin_common.h"
61 
62 #ifdef macintosh
63 #include <sasl_cram_plugin_decl.h>
64 #endif
65 
66 /*****************************  Common Section  *****************************/
67 
68 /* convert a string of 8bit chars to it's representation in hex
69  * using lowercase letters
70  */
convert16(unsigned char * in,int inlen,const sasl_utils_t * utils)71 static char *convert16(unsigned char *in, int inlen, const sasl_utils_t *utils)
72 {
73     static char hex[]="0123456789abcdef";
74     int lup;
75     char *out;
76 
77     out = utils->malloc(inlen*2+1);
78     if (out == NULL) return NULL;
79 
80     for (lup=0; lup < inlen; lup++) {
81 	out[lup*2] = hex[in[lup] >> 4];
82 	out[lup*2+1] = hex[in[lup] & 15];
83     }
84 
85     out[lup*2] = 0;
86     return out;
87 }
88 
89 
90 /*****************************  Server Section  *****************************/
91 
92 typedef struct server_context {
93     int state;
94 
95     char *challenge;
96 } server_context_t;
97 
98 static int
crammd5_server_mech_new(void * glob_context,sasl_server_params_t * sparams,const char * challenge,unsigned challen,void ** conn_context)99 crammd5_server_mech_new(void *glob_context __attribute__((unused)),
100 			sasl_server_params_t *sparams,
101 			const char *challenge __attribute__((unused)),
102 			unsigned challen __attribute__((unused)),
103 			void **conn_context)
104 {
105     server_context_t *text;
106 
107     /* holds state are in */
108     text = sparams->utils->malloc(sizeof(server_context_t));
109     if (text == NULL) {
110 	MEMERROR( sparams->utils );
111 	return SASL_NOMEM;
112     }
113 
114     memset(text, 0, sizeof(server_context_t));
115 
116     text->state = 1;
117 
118     *conn_context = text;
119 
120     return SASL_OK;
121 }
122 
123 /*
124  * Returns the current time (or part of it) in string form
125  *  maximum length=15
126  */
gettime(sasl_server_params_t * sparams)127 static char *gettime(sasl_server_params_t *sparams)
128 {
129     char *ret;
130     time_t t;
131 
132     t=time(NULL);
133     ret= sparams->utils->malloc(15);
134     if (ret==NULL) return NULL;
135 
136     /* the bottom bits are really the only random ones so if
137        we overflow we don't want to loose them */
138     snprintf(ret,15,"%lu",t%(0xFFFFFF));
139 
140     return ret;
141 }
142 
randomdigits(sasl_server_params_t * sparams)143 static char *randomdigits(sasl_server_params_t *sparams)
144 {
145     unsigned int num;
146     char *ret;
147     unsigned char temp[5]; /* random 32-bit number */
148 
149     sparams->utils->rand(sparams->utils->rpool,(char *) temp,4);
150     num=(temp[0] * 256 * 256 * 256) +
151 	(temp[1] * 256 * 256) +
152 	(temp[2] * 256) +
153 	(temp[3] );
154 
155     ret = sparams->utils->malloc(15); /* there's no way an unsigned can be longer than this right? */
156     if (ret == NULL) return NULL;
157     sprintf(ret, "%u", num);
158 
159     return ret;
160 }
161 
162 static int
crammd5_server_mech_step1(server_context_t * text,sasl_server_params_t * sparams,const char * clientin,unsigned clientinlen,const char ** serverout,unsigned * serveroutlen,sasl_out_params_t * oparams)163 crammd5_server_mech_step1(server_context_t *text,
164 			  sasl_server_params_t *sparams,
165 			  const char *clientin __attribute__((unused)),
166 			  unsigned clientinlen,
167 			  const char **serverout,
168 			  unsigned *serveroutlen,
169 			  sasl_out_params_t *oparams __attribute__((unused)))
170 {
171     char *time, *randdigits;
172 
173     /* we shouldn't have received anything */
174     if (clientinlen != 0) {
175 	SETERROR(sparams->utils, "CRAM-MD5 does not accept inital data");
176 	return SASL_BADPROT;
177     }
178 
179     /* get time and a random number for the nonce */
180     time = gettime(sparams);
181     randdigits = randomdigits(sparams);
182     if ((time == NULL) || (randdigits == NULL)) {
183 	MEMERROR( sparams->utils );
184 	return SASL_NOMEM;
185     }
186 
187     /* allocate some space for the challenge */
188     text->challenge = sparams->utils->malloc(200 + 1);
189     if (text->challenge == NULL) {
190 	MEMERROR(sparams->utils);
191 	return SASL_NOMEM;
192     }
193 
194     /* create the challenge */
195     snprintf(text->challenge, 200, "<%s.%s@%s>", randdigits, time,
196 	     sparams->serverFQDN);
197 
198     *serverout = text->challenge;
199     *serveroutlen = (unsigned) strlen(text->challenge);
200 
201     /* free stuff */
202     sparams->utils->free(time);
203     sparams->utils->free(randdigits);
204 
205     text->state = 2;
206 
207     return SASL_CONTINUE;
208 }
209 
210 static int
crammd5_server_mech_step2(server_context_t * text,sasl_server_params_t * sparams,const char * clientin,unsigned clientinlen,const char ** serverout,unsigned * serveroutlen,sasl_out_params_t * oparams)211 crammd5_server_mech_step2(server_context_t *text,
212 			  sasl_server_params_t *sparams,
213 			  const char *clientin,
214 			  unsigned clientinlen,
215 			  const char **serverout __attribute__((unused)),
216 			  unsigned *serveroutlen __attribute__((unused)),
217 			  sasl_out_params_t *oparams)
218 {
219     char *userid = NULL;
220     sasl_secret_t *sec = NULL;
221     int pos;
222     size_t len;
223     int result = SASL_FAIL;
224     const char *password_request[] = { SASL_AUX_PASSWORD,
225 #if defined(OBSOLETE_CRAM_ATTR)
226 				       "*cmusaslsecretCRAM-MD5",
227 #endif
228 				       NULL };
229     struct propval auxprop_values[3];
230     HMAC_MD5_CTX tmphmac;
231     HMAC_MD5_STATE md5state;
232     int clear_md5state = 0;
233     char *digest_str = NULL;
234     UINT4 digest[4];
235 
236     /* extract userid; everything before last space */
237     pos = clientinlen-1;
238     while ((pos > 0) && (clientin[pos] != ' ')) pos--;
239 
240     if (pos <= 0) {
241 	SETERROR( sparams->utils,"need authentication name");
242 	return SASL_BADPROT;
243     }
244 
245     userid = (char *) sparams->utils->malloc(pos+1);
246     if (userid == NULL) {
247 	MEMERROR( sparams->utils);
248 	return SASL_NOMEM;
249     }
250 
251     /* copy authstr out */
252     memcpy(userid, clientin, pos);
253     userid[pos] = '\0';
254 
255     result = sparams->utils->prop_request(sparams->propctx, password_request);
256     if (result != SASL_OK) goto done;
257 
258     /* this will trigger the getting of the aux properties */
259     result = sparams->canon_user(sparams->utils->conn,
260 				 userid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID,
261 				 oparams);
262     if (result != SASL_OK) goto done;
263 
264     result = sparams->utils->prop_getnames(sparams->propctx,
265 					   password_request,
266 					   auxprop_values);
267     if (result < 0 ||
268 	((!auxprop_values[0].name || !auxprop_values[0].values)
269 #if defined(OBSOLETE_CRAM_ATTR)
270 	  && (!auxprop_values[1].name || !auxprop_values[1].values)
271 #endif
272 	)) {
273 	/* We didn't find this username */
274 	sparams->utils->seterror(sparams->utils->conn,0,
275 				 "no secret in database");
276 	result = sparams->transition ? SASL_TRANS : SASL_NOUSER;
277 	goto done;
278     }
279 
280     if (auxprop_values[0].name && auxprop_values[0].values) {
281 	len = strlen(auxprop_values[0].values[0]);
282 	if (len == 0) {
283 	    sparams->utils->seterror(sparams->utils->conn,0,
284 				     "empty secret");
285 	    result = SASL_FAIL;
286 	    goto done;
287 	}
288 
289 	sec = sparams->utils->malloc(sizeof(sasl_secret_t) + len);
290 	if (!sec) goto done;
291 
292 	sec->len = (unsigned) len;
293 	strncpy((char *)sec->data, auxprop_values[0].values[0], len + 1);
294 
295 	clear_md5state = 1;
296 	/* Do precalculation on plaintext secret */
297 	sparams->utils->hmac_md5_precalc(&md5state, /* OUT */
298 					 sec->data,
299 					 sec->len);
300 #if defined(OBSOLETE_CRAM_ATTR)
301     } else if (auxprop_values[1].name && auxprop_values[1].values) {
302 	/* We have a precomputed secret */
303 	memcpy(&md5state, auxprop_values[1].values[0],
304 	       sizeof(HMAC_MD5_STATE));
305 #endif
306     } else {
307 	sparams->utils->seterror(sparams->utils->conn, 0,
308 				 "Have neither type of secret");
309 	return SASL_FAIL;
310     }
311 
312     /* erase the plaintext password */
313     sparams->utils->prop_erase(sparams->propctx, password_request[0]);
314 
315     /* ok this is annoying:
316        so we have this half-way hmac transform instead of the plaintext
317        that means we half to:
318        -import it back into a md5 context
319        -do an md5update with the nonce
320        -finalize it
321     */
322     sparams->utils->hmac_md5_import(&tmphmac, (HMAC_MD5_STATE *) &md5state);
323     sparams->utils->MD5Update(&(tmphmac.ictx),
324 			      (const unsigned char *) text->challenge,
325 			      (unsigned) strlen(text->challenge));
326     sparams->utils->hmac_md5_final((unsigned char *) &digest, &tmphmac);
327 
328     /* convert to base 16 with lower case letters */
329     digest_str = convert16((unsigned char *) digest, 16, sparams->utils);
330 
331     /* if same then verified
332      *  - we know digest_str is null terminated but clientin might not be
333      *  - verify the length of clientin anyway!
334      */
335     len = strlen(digest_str);
336     if (clientinlen-pos-1 < len ||
337 	strncmp(digest_str, clientin+pos+1, len) != 0) {
338 	sparams->utils->seterror(sparams->utils->conn, 0,
339 				 "incorrect digest response");
340 	result = SASL_BADAUTH;
341 	goto done;
342     }
343 
344     /* set oparams */
345     oparams->doneflag = 1;
346     oparams->mech_ssf = 0;
347     oparams->maxoutbuf = 0;
348     oparams->encode_context = NULL;
349     oparams->encode = NULL;
350     oparams->decode_context = NULL;
351     oparams->decode = NULL;
352     oparams->param_version = 0;
353 
354     result = SASL_OK;
355 
356   done:
357     if (userid) sparams->utils->free(userid);
358     if (sec) _plug_free_secret(sparams->utils, &sec);
359 
360     if (digest_str) sparams->utils->free(digest_str);
361     if (clear_md5state) memset(&md5state, 0, sizeof(md5state));
362 
363     return result;
364 }
365 
crammd5_server_mech_step(void * conn_context,sasl_server_params_t * sparams,const char * clientin,unsigned clientinlen,const char ** serverout,unsigned * serveroutlen,sasl_out_params_t * oparams)366 static int crammd5_server_mech_step(void *conn_context,
367 				    sasl_server_params_t *sparams,
368 				    const char *clientin,
369 				    unsigned clientinlen,
370 				    const char **serverout,
371 				    unsigned *serveroutlen,
372 				    sasl_out_params_t *oparams)
373 {
374     server_context_t *text = (server_context_t *) conn_context;
375 
376     *serverout = NULL;
377     *serveroutlen = 0;
378 
379     if (text == NULL) {
380 	return SASL_BADPROT;
381     }
382 
383     /* this should be well more than is ever needed */
384     if (clientinlen > 1024) {
385 	SETERROR(sparams->utils, "CRAM-MD5 input longer than 1024 bytes");
386 	return SASL_BADPROT;
387     }
388 
389     switch (text->state) {
390 
391     case 1:
392 	return crammd5_server_mech_step1(text, sparams,
393 					 clientin, clientinlen,
394 					 serverout, serveroutlen,
395 					 oparams);
396 
397     case 2:
398 	return crammd5_server_mech_step2(text, sparams,
399 					 clientin, clientinlen,
400 					 serverout, serveroutlen,
401 					 oparams);
402 
403     default: /* should never get here */
404 	sparams->utils->log(NULL, SASL_LOG_ERR,
405 			   "Invalid CRAM-MD5 server step %d\n", text->state);
406 	return SASL_FAIL;
407     }
408 
409     return SASL_FAIL; /* should never get here */
410 }
411 
crammd5_server_mech_dispose(void * conn_context,const sasl_utils_t * utils)412 static void crammd5_server_mech_dispose(void *conn_context,
413 					const sasl_utils_t *utils)
414 {
415     server_context_t *text = (server_context_t *) conn_context;
416 
417     if (!text) return;
418 
419     if (text->challenge) _plug_free_string(utils,&(text->challenge));
420 
421     utils->free(text);
422 }
423 
424 static sasl_server_plug_t crammd5_server_plugins[] =
425 {
426     {
427 	"CRAM-MD5",			/* mech_name */
428 	0,				/* max_ssf */
429 	SASL_SEC_NOPLAINTEXT
430 	| SASL_SEC_NOANONYMOUS,		/* security_flags */
431 	SASL_FEAT_SERVER_FIRST,		/* features */
432 	NULL,				/* glob_context */
433 	&crammd5_server_mech_new,	/* mech_new */
434 	&crammd5_server_mech_step,	/* mech_step */
435 	&crammd5_server_mech_dispose,	/* mech_dispose */
436 	NULL,				/* mech_free */
437 	NULL,				/* setpass */
438 	NULL,				/* user_query */
439 	NULL,				/* idle */
440 	NULL,				/* mech avail */
441 	NULL				/* spare */
442     }
443 };
444 
crammd5_server_plug_init(const sasl_utils_t * utils,int maxversion,int * out_version,sasl_server_plug_t ** pluglist,int * plugcount)445 int crammd5_server_plug_init(const sasl_utils_t *utils,
446 			     int maxversion,
447 			     int *out_version,
448 			     sasl_server_plug_t **pluglist,
449 			     int *plugcount)
450 {
451     if (maxversion < SASL_SERVER_PLUG_VERSION) {
452 	SETERROR( utils, "CRAM version mismatch");
453 	return SASL_BADVERS;
454     }
455 
456     *out_version = SASL_SERVER_PLUG_VERSION;
457     *pluglist = crammd5_server_plugins;
458     *plugcount = 1;
459 
460     return SASL_OK;
461 }
462 
463 /*****************************  Client Section  *****************************/
464 
465 typedef struct client_context {
466     char *out_buf;
467     unsigned out_buf_len;
468 } client_context_t;
469 
crammd5_client_mech_new(void * glob_context,sasl_client_params_t * params,void ** conn_context)470 static int crammd5_client_mech_new(void *glob_context __attribute__((unused)),
471 				   sasl_client_params_t *params,
472 				   void **conn_context)
473 {
474     client_context_t *text;
475 
476     /* holds state are in */
477     text = params->utils->malloc(sizeof(client_context_t));
478     if (text == NULL) {
479 	MEMERROR(params->utils);
480 	return SASL_NOMEM;
481     }
482 
483     memset(text, 0, sizeof(client_context_t));
484 
485     *conn_context = text;
486 
487     return SASL_OK;
488 }
489 
make_hashed(sasl_secret_t * sec,char * nonce,int noncelen,const sasl_utils_t * utils)490 static char *make_hashed(sasl_secret_t *sec, char *nonce, int noncelen,
491 			 const sasl_utils_t *utils)
492 {
493     unsigned char digest[24];
494     char *in16;
495 
496     if (sec == NULL) return NULL;
497 
498     /* do the hmac md5 hash output 128 bits */
499     utils->hmac_md5((unsigned char *) nonce, noncelen,
500 		    sec->data, sec->len, digest);
501 
502     /* convert that to hex form */
503     in16 = convert16(digest, 16, utils);
504     if (in16 == NULL) return NULL;
505 
506     return in16;
507 }
508 
crammd5_client_mech_step(void * conn_context,sasl_client_params_t * params,const char * serverin,unsigned serverinlen,sasl_interact_t ** prompt_need,const char ** clientout,unsigned * clientoutlen,sasl_out_params_t * oparams)509 static int crammd5_client_mech_step(void *conn_context,
510 				    sasl_client_params_t *params,
511 				    const char *serverin,
512 				    unsigned serverinlen,
513 				    sasl_interact_t **prompt_need,
514 				    const char **clientout,
515 				    unsigned *clientoutlen,
516 				    sasl_out_params_t *oparams)
517 {
518     client_context_t *text = (client_context_t *) conn_context;
519     const char *authid = NULL;
520     sasl_secret_t *password = NULL;
521     unsigned int free_password = 0; /* set if we need to free password */
522     int auth_result = SASL_OK;
523     int pass_result = SASL_OK;
524     int result;
525     size_t maxsize;
526     char *in16 = NULL;
527 
528     *clientout = NULL;
529     *clientoutlen = 0;
530 
531     /* First check for absurd lengths */
532     if (serverinlen > 1024) {
533 	params->utils->seterror(params->utils->conn, 0,
534 				"CRAM-MD5 input longer than 1024 bytes");
535 	return SASL_BADPROT;
536     }
537 
538     /* check if sec layer strong enough */
539     if (params->props.min_ssf > params->external_ssf) {
540 	SETERROR( params->utils, "SSF requested of CRAM-MD5 plugin");
541 	return SASL_TOOWEAK;
542     }
543 
544     /* try to get the userid */
545     if (oparams->authid == NULL) {
546 	auth_result=_plug_get_authid(params->utils, &authid, prompt_need);
547 
548 	if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
549 	    return auth_result;
550     }
551 
552     /* try to get the password */
553     if (password == NULL) {
554 	pass_result=_plug_get_password(params->utils, &password,
555 				       &free_password, prompt_need);
556 
557 	if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
558 	    return pass_result;
559     }
560 
561     /* free prompts we got */
562     if (prompt_need && *prompt_need) {
563 	params->utils->free(*prompt_need);
564 	*prompt_need = NULL;
565     }
566 
567     /* if there are prompts not filled in */
568     if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) {
569 	/* make the prompt list */
570 	result =
571 	    _plug_make_prompts(params->utils, prompt_need,
572 			       NULL, NULL,
573 			       auth_result == SASL_INTERACT ?
574 			       "Please enter your authentication name" : NULL,
575 			       NULL,
576 			       pass_result == SASL_INTERACT ?
577 			       "Please enter your password" : NULL, NULL,
578 			       NULL, NULL, NULL,
579 			       NULL, NULL, NULL);
580 	if (result != SASL_OK) goto cleanup;
581 
582 	return SASL_INTERACT;
583     }
584 
585     if (!password) {
586 	PARAMERROR(params->utils);
587 	return SASL_BADPARAM;
588     }
589 
590     result = params->canon_user(params->utils->conn, authid, 0,
591 				SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
592     if (result != SASL_OK) goto cleanup;
593 
594     /*
595      * username SP digest (keyed md5 where key is passwd)
596      */
597 
598     in16 = make_hashed(password, (char *) serverin, serverinlen,
599 		       params->utils);
600 
601     if (in16 == NULL) {
602 	SETERROR(params->utils, "whoops, make_hashed failed us this time");
603 	result = SASL_FAIL;
604 	goto cleanup;
605     }
606 
607     maxsize = 32+1+strlen(oparams->authid)+30;
608     result = _plug_buf_alloc(params->utils, &(text->out_buf),
609 			     &(text->out_buf_len), (unsigned) maxsize);
610     if (result != SASL_OK) goto cleanup;
611 
612     snprintf(text->out_buf, maxsize, "%s %s", oparams->authid, in16);
613 
614     *clientout = text->out_buf;
615     *clientoutlen = (unsigned) strlen(*clientout);
616 
617     /* set oparams */
618     oparams->doneflag = 1;
619     oparams->mech_ssf = 0;
620     oparams->maxoutbuf = 0;
621     oparams->encode_context = NULL;
622     oparams->encode = NULL;
623     oparams->decode_context = NULL;
624     oparams->decode = NULL;
625     oparams->param_version = 0;
626 
627     result = SASL_OK;
628 
629   cleanup:
630     /* get rid of private information */
631     if (in16) _plug_free_string(params->utils, &in16);
632 
633     /* get rid of all sensitive info */
634     if (free_password) _plug_free_secret(params-> utils, &password);
635 
636     return result;
637 }
638 
crammd5_client_mech_dispose(void * conn_context,const sasl_utils_t * utils)639 static void crammd5_client_mech_dispose(void *conn_context,
640 					const sasl_utils_t *utils)
641 {
642     client_context_t *text = (client_context_t *) conn_context;
643 
644     if (!text) return;
645 
646     if (text->out_buf) utils->free(text->out_buf);
647 
648     utils->free(text);
649 }
650 
651 static sasl_client_plug_t crammd5_client_plugins[] =
652 {
653     {
654 	"CRAM-MD5",			/* mech_name */
655 	0,				/* max_ssf */
656 	SASL_SEC_NOPLAINTEXT
657 	| SASL_SEC_NOANONYMOUS,		/* security_flags */
658 	SASL_FEAT_SERVER_FIRST,		/* features */
659 	NULL,				/* required_prompts */
660 	NULL,				/* glob_context */
661 	&crammd5_client_mech_new,	/* mech_new */
662 	&crammd5_client_mech_step,	/* mech_step */
663 	&crammd5_client_mech_dispose,	/* mech_dispose */
664 	NULL,				/* mech_free */
665 	NULL,				/* idle */
666 	NULL,				/* spare */
667 	NULL				/* spare */
668     }
669 };
670 
crammd5_client_plug_init(const sasl_utils_t * utils,int maxversion,int * out_version,sasl_client_plug_t ** pluglist,int * plugcount)671 int crammd5_client_plug_init(const sasl_utils_t *utils,
672 			     int maxversion,
673 			     int *out_version,
674 			     sasl_client_plug_t **pluglist,
675 			     int *plugcount)
676 {
677     if (maxversion < SASL_CLIENT_PLUG_VERSION) {
678 	SETERROR( utils, "CRAM version mismatch");
679 	return SASL_BADVERS;
680     }
681 
682     *out_version = SASL_CLIENT_PLUG_VERSION;
683     *pluglist = crammd5_client_plugins;
684     *plugcount = 1;
685 
686     return SASL_OK;
687 }
688