1/*	$NetBSD: xsasl_cyrus_server.c,v 1.1.1.2 2012/06/09 11:27:28 tron Exp $	*/
2
3/*++
4/* NAME
5/*	xsasl_cyrus_server 3
6/* SUMMARY
7/*	Cyrus SASL server-side plug-in
8/* SYNOPSIS
9/*	#include <xsasl_cyrus_server.h>
10/*
11/*	XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info)
12/*	const char *server_type;
13/*	const char *path_info;
14/* DESCRIPTION
15/*	This module implements the Cyrus SASL server-side authentication
16/*	plug-in.
17/*
18/*	xsasl_cyrus_server_init() initializes the Cyrus SASL library and
19/*	returns an implementation handle that can be used to generate
20/*	SASL server instances.
21/*
22/*	Arguments:
23/* .IP server_type
24/*	The server type (cyrus). This argument is ignored, but it
25/*	could be used when one implementation provides multiple
26/*	variants.
27/* .IP path_info
28/*	The base name of the SASL server configuration file (example:
29/*	smtpd becomes /usr/lib/sasl2/smtpd.conf).
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_server(3).
37/* LICENSE
38/* .ad
39/* .fi
40/*	The Secure Mailer license must be distributed with this software.
41/* AUTHOR(S)
42/*	Initial implementation by:
43/*	Till Franke
44/*	SuSE Rhein/Main AG
45/*	65760 Eschborn, Germany
46/*
47/*	Adopted by:
48/*	Wietse Venema
49/*	IBM T.J. Watson Research
50/*	P.O. Box 704
51/*	Yorktown Heights, NY 10598, USA
52/*--*/
53
54/* System library. */
55
56#include <sys_defs.h>
57#include <stdlib.h>
58#include <string.h>
59
60/* Utility library. */
61
62#include <msg.h>
63#include <mymalloc.h>
64#include <name_mask.h>
65#include <stringops.h>
66
67/* Global library. */
68
69#include <mail_params.h>
70
71/* Application-specific. */
72
73#include <xsasl.h>
74#include <xsasl_cyrus.h>
75#include <xsasl_cyrus_common.h>
76
77#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
78
79#include <sasl.h>
80#include <saslutil.h>
81
82/*
83 * Silly little macros.
84 */
85#define STR(s)	vstring_str(s)
86
87 /*
88  * Macros to handle API differences between SASLv1 and SASLv2. Specifics:
89  *
90  * The SASL_LOG_* constants were renamed in SASLv2.
91  *
92  * SASLv2's sasl_server_new takes two new parameters to specify local and
93  * remote IP addresses for auth mechs that use them.
94  *
95  * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr
96  * parameter.
97  *
98  * SASLv2's sasl_decode64 function takes an extra parameter for the length of
99  * the output buffer.
100  *
101  * The other major change is that SASLv2 now takes more responsibility for
102  * deallocating memory that it allocates internally.  Thus, some of the
103  * function parameters are now 'const', to make sure we don't try to free
104  * them too.  This is dealt with in the code later on.
105  */
106
107#if SASL_VERSION_MAJOR < 2
108/* SASL version 1.x */
109#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \
110	sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn)
111#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \
112	sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err)
113#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \
114	sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err)
115#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
116	sasl_decode64(in, inlen, out, outlen)
117typedef char *MECHANISM_TYPE;
118typedef unsigned MECHANISM_COUNT_TYPE;
119typedef char *SERVEROUT_TYPE;
120typedef void *VOID_SERVEROUT_TYPE;
121
122#endif
123
124#if SASL_VERSION_MAJOR >= 2
125/* SASL version > 2.x */
126#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \
127	sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn)
128#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \
129	sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen)
130#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \
131	sasl_server_step(conn, clin, clinlen, srvout, srvoutlen)
132#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
133	sasl_decode64(in, inlen, out, outmaxlen, outlen)
134typedef const char *MECHANISM_TYPE;
135typedef int MECHANISM_COUNT_TYPE;
136typedef const char *SERVEROUT_TYPE;
137typedef const void *VOID_SERVEROUT_TYPE;
138
139#endif
140
141 /*
142  * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER
143  * object.
144  */
145typedef struct {
146    XSASL_SERVER xsasl;			/* generic members, must be first */
147    VSTREAM *stream;			/* client-server connection */
148    sasl_conn_t *sasl_conn;		/* SASL context */
149    VSTRING *decoded;			/* decoded challenge or response */
150    char   *username;			/* authenticated user */
151    char   *mechanism_list;		/* applicable mechanisms */
152} XSASL_CYRUS_SERVER;
153
154 /*
155  * Forward declarations.
156  */
157static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *);
158static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *,
159					        XSASL_SERVER_CREATE_ARGS *);
160static void xsasl_cyrus_server_free(XSASL_SERVER *);
161static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *,
162				            const char *, VSTRING *);
163static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *);
164static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *);
165static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *);
166static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *);
167
168 /*
169  * SASL callback interface structure. These call-backs have no per-session
170  * context.
171  */
172#define NO_CALLBACK_CONTEXT	0
173
174static sasl_callback_t callbacks[] = {
175    {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT},
176    {SASL_CB_LIST_END, 0, 0}
177};
178
179/* xsasl_cyrus_server_init - create implementation handle */
180
181XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type,
182					           const char *path_info)
183{
184    const char *myname = "xsasl_cyrus_server_init";
185    XSASL_SERVER_IMPL *xp;
186    int     sasl_status;
187
188#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \
189    || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19))
190    int     sasl_major;
191    int     sasl_minor;
192    int     sasl_step;
193
194    /*
195     * DLL hell guard.
196     */
197    sasl_version_info((const char **) 0, (const char **) 0,
198		      &sasl_major, &sasl_minor,
199		      &sasl_step, (int *) 0);
200    if (sasl_major != SASL_VERSION_MAJOR
201#if 0
202	|| sasl_minor != SASL_VERSION_MINOR
203	|| sasl_step != SASL_VERSION_STEP
204#endif
205	) {
206	msg_warn("incorrect SASL library version. "
207	      "Postfix was built with include files from version %d.%d.%d, "
208		 "but the run-time library version is %d.%d.%d",
209		 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
210		 sasl_major, sasl_minor, sasl_step);
211	return (0);
212    }
213#endif
214
215    if (*var_cyrus_conf_path) {
216#ifdef SASL_PATH_TYPE_CONFIG			/* Cyrus SASL 2.1.22 */
217	if (sasl_set_path(SASL_PATH_TYPE_CONFIG,
218			  var_cyrus_conf_path) != SASL_OK)
219	    msg_warn("failed to set Cyrus SASL configuration path: \"%s\"",
220		     var_cyrus_conf_path);
221#else
222	msg_warn("%s is not empty, but setting the Cyrus SASL configuration "
223		 "path is not supported with SASL library version %d.%d.%d",
224		 VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR,
225		 SASL_VERSION_MINOR, SASL_VERSION_STEP);
226#endif
227    }
228
229    /*
230     * Initialize the library: load SASL plug-in routines, etc.
231     */
232    if (msg_verbose)
233	msg_info("%s: SASL config file is %s.conf", myname, path_info);
234    if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) {
235	msg_warn("SASL per-process initialization failed: %s",
236		 xsasl_cyrus_strerror(sasl_status));
237	return (0);
238    }
239
240    /*
241     * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it
242     * with our own methods or data.
243     */
244    xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp));
245    xp->create = xsasl_cyrus_server_create;
246    xp->done = xsasl_cyrus_server_done;
247    return (xp);
248}
249
250/* xsasl_cyrus_server_done - dispose of implementation */
251
252static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl)
253{
254    myfree((char *) impl);
255    sasl_done();
256}
257
258/* xsasl_cyrus_server_create - create server instance */
259
260static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl,
261				             XSASL_SERVER_CREATE_ARGS *args)
262{
263    const char *myname = "xsasl_cyrus_server_create";
264    char   *server_address;
265    char   *client_address;
266    sasl_conn_t *sasl_conn = 0;
267    XSASL_CYRUS_SERVER *server = 0;
268    int     sasl_status;
269
270    if (msg_verbose)
271	msg_info("%s: SASL service=%s, realm=%s",
272		 myname, args->service, args->user_realm ?
273		 args->user_realm : "(null)");
274
275    /*
276     * The optimizer will eliminate code duplication and/or dead code.
277     */
278#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \
279    do { \
280	if (server) { \
281	    xsasl_cyrus_server_free(&server->xsasl); \
282	} else { \
283	    if (sasl_conn) \
284		sasl_dispose(&sasl_conn); \
285	} \
286	return (x); \
287    } while (0)
288
289    /*
290     * Set up a new server context.
291     */
292#define NO_SECURITY_LAYERS	(0)
293#define NO_SESSION_CALLBACKS	((sasl_callback_t *) 0)
294#define NO_AUTH_REALM		((char *) 0)
295
296#if SASL_VERSION_MAJOR >= 2 && defined(USE_SASL_IP_AUTH)
297
298    /*
299     * Get IP addresses of local and remote endpoints for SASL.
300     */
301#error "USE_SASL_IP_AUTH is not implemented"
302
303#else
304
305    /*
306     * Don't give any IP address information to SASL.  SASLv1 doesn't use it,
307     * and in SASLv2 this will disable any mechanisms that do.
308     */
309    server_address = 0;
310    client_address = 0;
311#endif
312
313    if ((sasl_status =
314	 SASL_SERVER_NEW(args->service, var_myhostname,
315			 args->user_realm ? args->user_realm : NO_AUTH_REALM,
316			 server_address, client_address,
317			 NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS,
318			 &sasl_conn)) != SASL_OK) {
319	msg_warn("SASL per-connection server initialization: %s",
320		 xsasl_cyrus_strerror(sasl_status));
321	XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0);
322    }
323
324    /*
325     * Extend the XSASL_SERVER object with our own data. We use long-lived
326     * conversion buffers rather than local variables to avoid memory leaks
327     * in case of read/write timeout or I/O error.
328     */
329    server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server));
330    server->xsasl.free = xsasl_cyrus_server_free;
331    server->xsasl.first = xsasl_cyrus_server_first;
332    server->xsasl.next = xsasl_cyrus_server_next;
333    server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list;
334    server->xsasl.get_username = xsasl_cyrus_server_get_username;
335    server->stream = args->stream;
336    server->sasl_conn = sasl_conn;
337    server->decoded = vstring_alloc(20);
338    server->username = 0;
339    server->mechanism_list = 0;
340
341    if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options)
342	!= XSASL_AUTH_OK)
343	XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0);
344
345    return (&server->xsasl);
346}
347
348/* xsasl_cyrus_server_set_security - set security properties */
349
350static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp,
351					           const char *sasl_opts_val)
352{
353    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
354    sasl_security_properties_t sec_props;
355    int     sasl_status;
356
357    /*
358     * Security options. Some information can be found in the sasl.h include
359     * file.
360     */
361    memset(&sec_props, 0, sizeof(sec_props));
362    sec_props.min_ssf = 0;
363    sec_props.max_ssf = 0;			/* don't allow real SASL
364						 * security layer */
365    if (*sasl_opts_val == 0) {
366	sec_props.security_flags = 0;
367    } else {
368	sec_props.security_flags =
369	    xsasl_cyrus_security_parse_opts(sasl_opts_val);
370	if (sec_props.security_flags == 0) {
371	    msg_warn("bad per-session SASL security properties");
372	    return (XSASL_AUTH_FAIL);
373	}
374    }
375    sec_props.maxbufsize = 0;
376    sec_props.property_names = 0;
377    sec_props.property_values = 0;
378
379    if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS,
380				    &sec_props)) != SASL_OK) {
381	msg_warn("SASL per-connection security setup; %s",
382		 xsasl_cyrus_strerror(sasl_status));
383	return (XSASL_AUTH_FAIL);
384    }
385    return (XSASL_AUTH_OK);
386}
387
388/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */
389
390static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp)
391{
392    const char *myname = "xsasl_cyrus_server_get_mechanism_list";
393    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
394    MECHANISM_TYPE mechanism_list;
395    MECHANISM_COUNT_TYPE mechanism_count;
396    int     sasl_status;
397
398    /*
399     * Get the list of authentication mechanisms.
400     */
401#define UNSUPPORTED_USER	((char *) 0)
402#define IGNORE_MECHANISM_LEN	((unsigned *) 0)
403
404    if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER,
405				     "", " ", "",
406				     &mechanism_list,
407				     IGNORE_MECHANISM_LEN,
408				     &mechanism_count)) != SASL_OK) {
409	msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status));
410	return (0);
411    }
412    if (mechanism_count <= 0) {
413	msg_warn("%s: no applicable SASL mechanisms", myname);
414	return (0);
415    }
416    server->mechanism_list = mystrdup(mechanism_list);
417#if SASL_VERSION_MAJOR < 2
418    /* SASL version 1 doesn't free memory that it allocates. */
419    free(mechanism_list);
420#endif
421    return (server->mechanism_list);
422}
423
424/* xsasl_cyrus_server_free - destroy server instance */
425
426static void xsasl_cyrus_server_free(XSASL_SERVER *xp)
427{
428    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
429
430    sasl_dispose(&server->sasl_conn);
431    vstring_free(server->decoded);
432    if (server->username)
433	myfree(server->username);
434    if (server->mechanism_list)
435	myfree(server->mechanism_list);
436    myfree((char *) server);
437}
438
439/* xsasl_cyrus_server_auth_response - encode server first/next response */
440
441static int xsasl_cyrus_server_auth_response(int sasl_status,
442					            SERVEROUT_TYPE serverout,
443					            unsigned serveroutlen,
444					            VSTRING *reply)
445{
446    const char *myname = "xsasl_cyrus_server_auth_response";
447    unsigned enc_length;
448    unsigned enc_length_out;
449
450    /*
451     * Encode the server first/next non-error response; otherwise return the
452     * unencoded error text that corresponds to the SASL error status.
453     *
454     * Regarding the hairy expression below: output from sasl_encode64() comes
455     * in multiples of four bytes for each triple of input bytes, plus four
456     * bytes for any incomplete last triple, plus one byte for the null
457     * terminator.
458     */
459    if (sasl_status == SASL_OK) {
460	vstring_strcpy(reply, "");
461	return (XSASL_AUTH_DONE);
462    } else if (sasl_status == SASL_CONTINUE) {
463	if (msg_verbose)
464	    msg_info("%s: uncoded server challenge: %.*s",
465		     myname, (int) serveroutlen, serverout);
466	enc_length = ((serveroutlen + 2) / 3) * 4 + 1;
467	VSTRING_RESET(reply);			/* Fix 200512 */
468	VSTRING_SPACE(reply, enc_length);
469	if ((sasl_status = sasl_encode64(serverout, serveroutlen,
470					 STR(reply), vstring_avail(reply),
471					 &enc_length_out)) != SASL_OK)
472	    msg_panic("%s: sasl_encode64 botch: %s",
473		      myname, xsasl_cyrus_strerror(sasl_status));
474	return (XSASL_AUTH_MORE);
475    } else {
476	if (sasl_status == SASL_NOUSER)		/* privacy */
477	    sasl_status = SASL_BADAUTH;
478	vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
479	return (XSASL_AUTH_FAIL);
480    }
481}
482
483/* xsasl_cyrus_server_first - per-session authentication */
484
485int     xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method,
486			          const char *init_response, VSTRING *reply)
487{
488    const char *myname = "xsasl_cyrus_server_first";
489    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
490    char   *dec_buffer;
491    unsigned dec_length;
492    unsigned reply_len;
493    unsigned serveroutlen;
494    int     sasl_status;
495    SERVEROUT_TYPE serverout = 0;
496    int     xsasl_status;
497
498#if SASL_VERSION_MAJOR < 2
499    const char *errstr = 0;
500
501#endif
502
503#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
504
505    if (msg_verbose)
506	msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
507		 IFELSE(init_response, ", init_response ", ""),
508		 IFELSE(init_response, init_response, ""));
509
510    /*
511     * SASL authentication protocol start-up. Process any initial client
512     * response that was sent along in the AUTH command.
513     */
514    if (init_response) {
515	reply_len = strlen(init_response);
516	VSTRING_RESET(server->decoded);		/* Fix 200512 */
517	VSTRING_SPACE(server->decoded, reply_len);
518	if ((sasl_status = SASL_DECODE64(init_response, reply_len,
519					 dec_buffer = STR(server->decoded),
520					 vstring_avail(server->decoded),
521					 &dec_length)) != SASL_OK) {
522	    vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
523	    return (XSASL_AUTH_FORM);
524	}
525	if (msg_verbose)
526	    msg_info("%s: decoded initial response %s", myname, dec_buffer);
527    } else {
528	dec_buffer = 0;
529	dec_length = 0;
530    }
531    sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer,
532				    dec_length, &serverout,
533				    &serveroutlen, &errstr);
534    xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout,
535						    serveroutlen, reply);
536#if SASL_VERSION_MAJOR < 2
537    /* SASL version 1 doesn't free memory that it allocates. */
538    free(serverout);
539#endif
540    return (xsasl_status);
541}
542
543/* xsasl_cyrus_server_next - continue authentication */
544
545static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request,
546				           VSTRING *reply)
547{
548    const char *myname = "xsasl_cyrus_server_next";
549    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
550    unsigned dec_length;
551    unsigned request_len;
552    unsigned serveroutlen;
553    int     sasl_status;
554    SERVEROUT_TYPE serverout = 0;
555    int     xsasl_status;
556
557#if SASL_VERSION_MAJOR < 2
558    const char *errstr = 0;
559
560#endif
561
562    request_len = strlen(request);
563    VSTRING_RESET(server->decoded);		/* Fix 200512 */
564    VSTRING_SPACE(server->decoded, request_len);
565    if ((sasl_status = SASL_DECODE64(request, request_len,
566				     STR(server->decoded),
567				     vstring_avail(server->decoded),
568				     &dec_length)) != SASL_OK) {
569	vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
570	return (XSASL_AUTH_FORM);
571    }
572    if (msg_verbose)
573	msg_info("%s: decoded response: %.*s",
574		 myname, (int) dec_length, STR(server->decoded));
575    sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded),
576				   dec_length, &serverout,
577				   &serveroutlen, &errstr);
578    xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout,
579						    serveroutlen, reply);
580#if SASL_VERSION_MAJOR < 2
581    /* SASL version 1 doesn't free memory that it allocates. */
582    free(serverout);
583#endif
584    return (xsasl_status);
585}
586
587/* xsasl_cyrus_server_get_username - get authenticated username */
588
589static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp)
590{
591    const char *myname = "xsasl_cyrus_server_get_username";
592    XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
593    VOID_SERVEROUT_TYPE serverout = 0;
594    int     sasl_status;
595
596    /*
597     * XXX Do not free(serverout).
598     */
599    sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout);
600    if (sasl_status != SASL_OK || serverout == 0) {
601	msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s",
602		 myname, xsasl_cyrus_strerror(sasl_status));
603	return (0);
604    }
605    if (server->username)
606	myfree(server->username);
607    server->username = mystrdup(serverout);
608    printable(server->username, '?');
609    return (server->username);
610}
611
612#endif
613