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