xref: /netbsd-src/external/ibm-public/postfix/dist/src/xsasl/xsasl_cyrus_client.c (revision 33881f779a77dce6440bdc44610d94de75bebefe)
1 /*	$NetBSD: xsasl_cyrus_client.c,v 1.3 2020/03/18 19:05:22 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	xsasl_cyrus_client 3
6 /* SUMMARY
7 /*	Cyrus SASL client-side plug-in
8 /* SYNOPSIS
9 /*	#include <xsasl_cyrus_client.h>
10 /*
11 /*	XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info)
12 /*	const char *client_type;
13 /* DESCRIPTION
14 /*	This module implements the Cyrus SASL client-side authentication
15 /*	plug-in.
16 /*
17 /*	xsasl_cyrus_client_init() initializes the Cyrus SASL library and
18 /*	returns an implementation handle that can be used to generate
19 /*	SASL client instances.
20 /*
21 /*	Arguments:
22 /* .IP client_type
23 /*	The plug-in SASL client type (cyrus). This argument is
24 /*	ignored, but it could be used when one implementation
25 /*	provides multiple variants.
26 /* .IP path_info
27 /*	Implementation-specific information to specify the location
28 /*	of a configuration file, rendez-vous point, etc. This
29 /*	information is ignored by the Cyrus SASL client plug-in.
30 /* DIAGNOSTICS
31 /*	Fatal: out of memory.
32 /*
33 /*	Panic: interface violation.
34 /*
35 /*	Other: the routines log a warning and return an error result
36 /*	as specified in xsasl_client(3).
37 /* SEE ALSO
38 /*	xsasl_client(3) Client API
39 /* LICENSE
40 /* .ad
41 /* .fi
42 /*	The Secure Mailer license must be distributed with this software.
43 /* AUTHOR(S)
44 /*	Original author:
45 /*	Till Franke
46 /*	SuSE Rhein/Main AG
47 /*	65760 Eschborn, Germany
48 /*
49 /*	Adopted by:
50 /*	Wietse Venema
51 /*	IBM T.J. Watson Research
52 /*	P.O. Box 704
53 /*	Yorktown Heights, NY 10598, USA
54 /*
55 /*	Wietse Venema
56 /*	Google, Inc.
57 /*	111 8th Avenue
58 /*	New York, NY 10011, USA
59 /*--*/
60 
61  /*
62   * System library.
63   */
64 #include <sys_defs.h>
65 #include <stdlib.h>
66 #include <string.h>
67 
68  /*
69   * Utility library
70   */
71 #include <msg.h>
72 #include <mymalloc.h>
73 #include <stringops.h>
74 
75  /*
76   * Global library
77   */
78 #include <mail_params.h>
79 
80  /*
81   * Application-specific
82   */
83 #include <xsasl.h>
84 #include <xsasl_cyrus.h>
85 #include <xsasl_cyrus_common.h>
86 
87 #if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
88 
89 #include <sasl.h>
90 #include <saslutil.h>
91 
92 /*
93  * Silly little macros.
94  */
95 #define STR(s)  vstring_str(s)
96 
97  /*
98   * Macros to handle API differences between SASLv1 and SASLv2. Specifics:
99   *
100   * The SASL_LOG_* constants were renamed in SASLv2.
101   *
102   * SASLv2's sasl_client_new takes two new parameters to specify local and
103   * remote IP addresses for auth mechs that use them.
104   *
105   * SASLv2's sasl_client_start function no longer takes the secret parameter.
106   *
107   * SASLv2's sasl_decode64 function takes an extra parameter for the length of
108   * the output buffer.
109   *
110   * The other major change is that SASLv2 now takes more responsibility for
111   * deallocating memory that it allocates internally.  Thus, some of the
112   * function parameters are now 'const', to make sure we don't try to free
113   * them too.  This is dealt with in the code later on.
114   */
115 #if SASL_VERSION_MAJOR < 2
116 /* SASL version 1.x */
117 #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \
118 	sasl_client_new(srv, fqdn, prompt, secflags, pconn)
119 #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \
120 	sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech)
121 #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
122 	sasl_decode64(in, inlen, out, outlen)
123 typedef char *CLIENTOUT_TYPE;
124 
125 #endif
126 
127 #if SASL_VERSION_MAJOR >= 2
128 /* SASL version > 2.x */
129 #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \
130 	sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn)
131 #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \
132 	sasl_client_start(conn, mechlst, prompt, clout, cllen, mech)
133 #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
134 	sasl_decode64(in, inlen, out, outmaxlen, outlen)
135 typedef const char *CLIENTOUT_TYPE;
136 
137 #endif
138 
139  /*
140   * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT
141   * object.
142   */
143 typedef struct {
144     XSASL_CLIENT xsasl;			/* generic members, must be first */
145     VSTREAM *stream;			/* client-server connection */
146     sasl_conn_t *sasl_conn;		/* SASL context */
147     VSTRING *decoded;			/* decoded server challenge */
148     sasl_callback_t *callbacks;		/* user/password lookup */
149     char   *username;
150     char   *password;
151 } XSASL_CYRUS_CLIENT;
152 
153  /*
154   * Forward declarations.
155   */
156 static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *);
157 static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *,
158 					        XSASL_CLIENT_CREATE_ARGS *);
159 static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *);
160 static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *,
161 			            const char *, const char **, VSTRING *);
162 static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *);
163 static void xsasl_cyrus_client_free(XSASL_CLIENT *);
164 
165 /* xsasl_cyrus_client_get_user - username lookup call-back routine */
166 
xsasl_cyrus_client_get_user(void * context,int unused_id,const char ** result,unsigned * len)167 static int xsasl_cyrus_client_get_user(void *context, int unused_id,
168 				               const char **result,
169 				               unsigned *len)
170 {
171     const char *myname = "xsasl_cyrus_client_get_user";
172     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context;
173 
174     if (msg_verbose)
175 	msg_info("%s: %s", myname, client->username);
176 
177     /*
178      * Sanity check.
179      */
180     if (client->password == 0)
181 	msg_panic("%s: no username looked up", myname);
182 
183     *result = client->username;
184     if (len)
185 	*len = strlen(client->username);
186     return (SASL_OK);
187 }
188 
189 /* xsasl_cyrus_client_get_passwd - password lookup call-back routine */
190 
xsasl_cyrus_client_get_passwd(sasl_conn_t * conn,void * context,int id,sasl_secret_t ** psecret)191 static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context,
192 				            int id, sasl_secret_t **psecret)
193 {
194     const char *myname = "xsasl_cyrus_client_get_passwd";
195     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context;
196     int     len;
197 
198     if (msg_verbose)
199 	msg_info("%s: %s", myname, client->password);
200 
201     /*
202      * Sanity check.
203      */
204     if (!conn || !psecret || id != SASL_CB_PASS)
205 	return (SASL_BADPARAM);
206     if (client->password == 0)
207 	msg_panic("%s: no password looked up", myname);
208 
209     /*
210      * Convert the password into a counted string.
211      */
212     len = strlen(client->password);
213     if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0)
214 	return (SASL_NOMEM);
215     (*psecret)->len = len;
216     memcpy((*psecret)->data, client->password, len + 1);
217 
218     return (SASL_OK);
219 }
220 
221 /* xsasl_cyrus_client_init - initialize Cyrus SASL library */
222 
xsasl_cyrus_client_init(const char * unused_client_type,const char * unused_path_info)223 XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type,
224 				               const char *unused_path_info)
225 {
226     XSASL_CLIENT_IMPL *xp;
227     int     sasl_status;
228 
229     /*
230      * Global callbacks. These have no per-session context.
231      */
232     static sasl_callback_t callbacks[] = {
233 	{SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0},
234 	{SASL_CB_LIST_END, 0, 0}
235     };
236 
237 #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \
238     || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19))
239     int     sasl_major;
240     int     sasl_minor;
241     int     sasl_step;
242 
243     /*
244      * DLL hell guard.
245      */
246     sasl_version_info((const char **) 0, (const char **) 0,
247 		      &sasl_major, &sasl_minor,
248 		      &sasl_step, (int *) 0);
249     if (sasl_major != SASL_VERSION_MAJOR
250 #if 0
251 	|| sasl_minor != SASL_VERSION_MINOR
252 	|| sasl_step != SASL_VERSION_STEP
253 #endif
254 	) {
255 	msg_warn("incorrect SASL library version. "
256 	      "Postfix was built with include files from version %d.%d.%d, "
257 		 "but the run-time library version is %d.%d.%d",
258 		 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
259 		 sasl_major, sasl_minor, sasl_step);
260 	return (0);
261     }
262 #endif
263 
264     if (*var_cyrus_conf_path) {
265 #ifdef SASL_PATH_TYPE_CONFIG			/* Cyrus SASL 2.1.22 */
266 	if (sasl_set_path(SASL_PATH_TYPE_CONFIG,
267 			  var_cyrus_conf_path) != SASL_OK)
268 	    msg_warn("failed to set Cyrus SASL configuration path: \"%s\"",
269 		     var_cyrus_conf_path);
270 #else
271 	msg_warn("%s is not empty, but setting the Cyrus SASL configuration "
272 		 "path is not supported with SASL library version %d.%d.%d",
273 		 VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR,
274 		 SASL_VERSION_MINOR, SASL_VERSION_STEP);
275 #endif
276     }
277 
278     /*
279      * Initialize the SASL library.
280      */
281     if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) {
282 	msg_warn("SASL library initialization error: %s",
283 		 xsasl_cyrus_strerror(sasl_status));
284 	return (0);
285     }
286 
287     /*
288      * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it
289      * with our own methods or data.
290      */
291     xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp));
292     xp->create = xsasl_cyrus_client_create;
293     xp->done = xsasl_cyrus_client_done;
294     return (xp);
295 }
296 
297 /* xsasl_cyrus_client_done - dispose of implementation */
298 
xsasl_cyrus_client_done(XSASL_CLIENT_IMPL * impl)299 static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl)
300 {
301     myfree((void *) impl);
302     sasl_done();
303 }
304 
305 /* xsasl_cyrus_client_create - per-session SASL initialization */
306 
xsasl_cyrus_client_create(XSASL_CLIENT_IMPL * unused_impl,XSASL_CLIENT_CREATE_ARGS * args)307 XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl,
308 				             XSASL_CLIENT_CREATE_ARGS *args)
309 {
310     XSASL_CYRUS_CLIENT *client = 0;
311     static sasl_callback_t callbacks[] = {
312 	{SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0},
313 	{SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0},
314 	{SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0},
315 	{SASL_CB_LIST_END, 0, 0}
316     };
317     sasl_conn_t *sasl_conn = 0;
318     sasl_callback_t *custom_callbacks = 0;
319     sasl_callback_t *cp;
320     int     sasl_status;
321 
322     /*
323      * The optimizer will eliminate code duplication and/or dead code.
324      */
325 #define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \
326     do { \
327 	if (client) { \
328 	    xsasl_cyrus_client_free(&client->xsasl); \
329 	} else { \
330 	    if (custom_callbacks) \
331 		myfree((void *) custom_callbacks); \
332 	    if (sasl_conn) \
333 		sasl_dispose(&sasl_conn); \
334 	} \
335 	return (x); \
336     } while (0)
337 
338     /*
339      * Per-session initialization. Provide each session with its own callback
340      * context.
341      */
342 #define NULL_SECFLAGS		0
343 
344     custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks));
345     memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks));
346 
347 #define NULL_SERVER_ADDR	((char *) 0)
348 #define NULL_CLIENT_ADDR	((char *) 0)
349 
350     if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name,
351 				       NULL_CLIENT_ADDR, NULL_SERVER_ADDR,
352 				 var_cyrus_sasl_authzid ? custom_callbacks :
353 				       custom_callbacks + 1, NULL_SECFLAGS,
354 				       &sasl_conn)) != SASL_OK) {
355 	msg_warn("per-session SASL client initialization: %s",
356 		 xsasl_cyrus_strerror(sasl_status));
357 	XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0);
358     }
359 
360     /*
361      * Extend the XSASL_CLIENT object with our own state. We use long-lived
362      * conversion buffers rather than local variables to avoid memory leaks
363      * in case of read/write timeout or I/O error.
364      *
365      * XXX If we enable SASL encryption, there needs to be a way to inform the
366      * application, so that they can turn off connection caching, refuse
367      * STARTTLS, etc.
368      */
369     client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client));
370     client->xsasl.free = xsasl_cyrus_client_free;
371     client->xsasl.first = xsasl_cyrus_client_first;
372     client->xsasl.next = xsasl_cyrus_client_next;
373     client->stream = args->stream;
374     client->sasl_conn = sasl_conn;
375     client->callbacks = custom_callbacks;
376     client->decoded = vstring_alloc(20);
377     client->username = 0;
378     client->password = 0;
379 
380     for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++)
381 	cp->context = (void *) client;
382 
383     if (xsasl_cyrus_client_set_security(&client->xsasl,
384 					args->security_options)
385 	!= XSASL_AUTH_OK)
386 	XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0);
387 
388     return (&client->xsasl);
389 }
390 
391 /* xsasl_cyrus_client_set_security - set security properties */
392 
xsasl_cyrus_client_set_security(XSASL_CLIENT * xp,const char * sasl_opts_val)393 static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp,
394 					           const char *sasl_opts_val)
395 {
396     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
397     sasl_security_properties_t sec_props;
398     int     sasl_status;
399 
400     /*
401      * Per-session security properties. XXX This routine is not sufficiently
402      * documented. What is the purpose of all this?
403      */
404     memset(&sec_props, 0, sizeof(sec_props));
405     sec_props.min_ssf = 0;
406     sec_props.max_ssf = 0;			/* don't allow real SASL
407 						 * security layer */
408     if (*sasl_opts_val == 0) {
409 	sec_props.security_flags = 0;
410     } else {
411 	sec_props.security_flags =
412 	    xsasl_cyrus_security_parse_opts(sasl_opts_val);
413 	if (sec_props.security_flags == 0) {
414 	    msg_warn("bad per-session SASL security properties");
415 	    return (XSASL_AUTH_FAIL);
416 	}
417     }
418     sec_props.maxbufsize = 0;
419     sec_props.property_names = 0;
420     sec_props.property_values = 0;
421     if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS,
422 				    &sec_props)) != SASL_OK) {
423 	msg_warn("set per-session SASL security properties: %s",
424 		 xsasl_cyrus_strerror(sasl_status));
425 	return (XSASL_AUTH_FAIL);
426     }
427     return (XSASL_AUTH_OK);
428 }
429 
430 /* xsasl_cyrus_client_first - run authentication protocol */
431 
xsasl_cyrus_client_first(XSASL_CLIENT * xp,const char * mechanism_list,const char * username,const char * password,const char ** mechanism,VSTRING * init_resp)432 static int xsasl_cyrus_client_first(XSASL_CLIENT *xp,
433 				            const char *mechanism_list,
434 				            const char *username,
435 				            const char *password,
436 				            const char **mechanism,
437 				            VSTRING *init_resp)
438 {
439     const char *myname = "xsasl_cyrus_client_first";
440     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
441     unsigned enc_length;
442     unsigned enc_length_out;
443     CLIENTOUT_TYPE clientout;
444     unsigned clientoutlen;
445     int     sasl_status;
446 
447 #define NO_SASL_SECRET		0
448 #define NO_SASL_INTERACTION	0
449 
450     /*
451      * Save the username and password for the call-backs.
452      */
453     if (client->username)
454 	myfree(client->username);
455     client->username = mystrdup(username);
456     if (client->password)
457 	myfree(client->password);
458     client->password = mystrdup(password);
459 
460     /*
461      * Start the client side authentication protocol.
462      */
463     sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn,
464 				    mechanism_list,
465 				    NO_SASL_SECRET, NO_SASL_INTERACTION,
466 				    &clientout, &clientoutlen, mechanism);
467     if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) {
468 	vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status));
469 	return (XSASL_AUTH_FAIL);
470     }
471 
472     /*
473      * Generate the AUTH command and the optional initial client response.
474      * sasl_encode64() produces four bytes for each complete or incomplete
475      * triple of input bytes. Allocate an extra byte for string termination.
476      */
477 #define ENCODE64_LENGTH(n)	((((n) + 2) / 3) * 4)
478 
479     if (clientoutlen > 0) {
480 	if (msg_verbose) {
481 	    escape(client->decoded, clientout, clientoutlen);
482 	    msg_info("%s: uncoded initial reply: %s",
483 		     myname, STR(client->decoded));
484 	}
485 	enc_length = ENCODE64_LENGTH(clientoutlen) + 1;
486 	VSTRING_RESET(init_resp);		/* Fix 200512 */
487 	VSTRING_SPACE(init_resp, enc_length);
488 	if ((sasl_status = sasl_encode64(clientout, clientoutlen,
489 					 STR(init_resp),
490 					 vstring_avail(init_resp),
491 					 &enc_length_out)) != SASL_OK)
492 	    msg_panic("%s: sasl_encode64 botch: %s",
493 		      myname, xsasl_cyrus_strerror(sasl_status));
494 	vstring_set_payload_size(init_resp, enc_length_out);
495 #if SASL_VERSION_MAJOR < 2
496 	/* SASL version 1 doesn't free memory that it allocates. */
497 	free(clientout);
498 #endif
499     } else {
500 	vstring_strcpy(init_resp, "");
501     }
502     return (XSASL_AUTH_OK);
503 }
504 
505 /* xsasl_cyrus_client_next - continue authentication */
506 
xsasl_cyrus_client_next(XSASL_CLIENT * xp,const char * server_reply,VSTRING * client_reply)507 static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply,
508 				           VSTRING *client_reply)
509 {
510     const char *myname = "xsasl_cyrus_client_next";
511     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
512     unsigned enc_length;
513     unsigned enc_length_out;
514     CLIENTOUT_TYPE clientout;
515     unsigned clientoutlen;
516     unsigned serverinlen;
517     int     sasl_status;
518 
519     /*
520      * Process a server challenge.
521      */
522     serverinlen = strlen(server_reply);
523     VSTRING_RESET(client->decoded);		/* Fix 200512 */
524     VSTRING_SPACE(client->decoded, serverinlen);
525     if ((sasl_status = SASL_DECODE64(server_reply, serverinlen,
526 				     STR(client->decoded),
527 				     vstring_avail(client->decoded),
528 				     &enc_length)) != SASL_OK) {
529 	vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status));
530 	return (XSASL_AUTH_FORM);
531     }
532     if (msg_verbose)
533 	msg_info("%s: decoded challenge: %.*s",
534 		 myname, (int) enc_length, STR(client->decoded));
535     sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded),
536 				   enc_length, NO_SASL_INTERACTION,
537 				   &clientout, &clientoutlen);
538     if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) {
539 	vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status));
540 	return (XSASL_AUTH_FAIL);
541     }
542 
543     /*
544      * Send a client response.
545      */
546     if (clientoutlen > 0) {
547 	if (msg_verbose)
548 	    msg_info("%s: uncoded client response %.*s",
549 		     myname, (int) clientoutlen, clientout);
550 	enc_length = ENCODE64_LENGTH(clientoutlen) + 1;
551 	VSTRING_RESET(client_reply);		/* Fix 200512 */
552 	VSTRING_SPACE(client_reply, enc_length);
553 	if ((sasl_status = sasl_encode64(clientout, clientoutlen,
554 					 STR(client_reply),
555 					 vstring_avail(client_reply),
556 					 &enc_length_out)) != SASL_OK)
557 	    msg_panic("%s: sasl_encode64 botch: %s",
558 		      myname, xsasl_cyrus_strerror(sasl_status));
559 #if SASL_VERSION_MAJOR < 2
560 	/* SASL version 1 doesn't free memory that it allocates. */
561 	free(clientout);
562 #endif
563     } else {
564 	/* XXX Can't happen. */
565 	vstring_strcpy(client_reply, "");
566     }
567     return (XSASL_AUTH_OK);
568 }
569 
570 /* xsasl_cyrus_client_free - per-session cleanup */
571 
xsasl_cyrus_client_free(XSASL_CLIENT * xp)572 void    xsasl_cyrus_client_free(XSASL_CLIENT *xp)
573 {
574     XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
575 
576     if (client->username)
577 	myfree(client->username);
578     if (client->password)
579 	myfree(client->password);
580     if (client->sasl_conn)
581 	sasl_dispose(&client->sasl_conn);
582     myfree((void *) client->callbacks);
583     vstring_free(client->decoded);
584     myfree((void *) client);
585 }
586 
587 #endif
588