xref: /netbsd-src/external/ibm-public/postfix/dist/src/xsasl/xsasl_cyrus_client.c (revision 1b9578b8c2c1f848eeb16dabbfd7d1f0d9fdefbd)
1 /*	$NetBSD: xsasl_cyrus_client.c,v 1.1.1.1 2009/06/23 10:09:02 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)
118 typedef 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)
130 typedef 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   */
138 typedef 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   */
151 static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *);
152 static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *,
153 					        XSASL_CLIENT_CREATE_ARGS *);
154 static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *);
155 static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *,
156 			            const char *, const char **, VSTRING *);
157 static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *);
158 static void xsasl_cyrus_client_free(XSASL_CLIENT *);
159 
160 /* xsasl_cyrus_client_get_user - username lookup call-back routine */
161 
162 static 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 
186 static 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 
218 XSASL_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_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 
294 static 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 
302 XSASL_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_client_get_user, 0},
308 	{SASL_CB_AUTHNAME, &xsasl_cyrus_client_get_user, 0},
309 	{SASL_CB_PASS, &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 
388 static 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 
427 static 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 
502 static 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 
567 void    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