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