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