5 /* xsasl_cyrus_server 3
7 /* Cyrus SASL server-side plug-in
9 /* #include <xsasl_cyrus_server.h>
11 /* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info)
12 /* const char *server_type;
13 /* const char *path_info;
15 /* This module implements the Cyrus SASL server-side authentication
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.
24 /* The server type (cyrus). This argument is ignored, but it
25 /* could be used when one implementation provides multiple
28 /* The base name of the SASL server configuration file (example:
29 /* smtpd becomes /usr/lib/sasl2/smtpd.conf).
31 /* Fatal: out of memory.
33 /* Panic: interface violation.
35 /* Other: the routines log a warning and return an error result
36 /* as specified in xsasl_server(3).
40 /* The Secure Mailer license must be distributed with this software.
42 /* Initial implementation by:
45 /* 65760 Eschborn, Germany
49 /* IBM T.J. Watson Research
51 /* Yorktown Heights, NY 10598, USA
60 /* Utility library. */
64 #include <name_mask.h>
65 #include <stringops.h>
69 #include <mail_params.h>
71 /* Application-specific. */
74 #include <xsasl_cyrus.h>
75 #include <xsasl_cyrus_common.h>
77 #if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
83 * Silly little macros.
85 #define STR(s) vstring_str(s)
88 * Macros to handle API differences between SASLv1 and SASLv2. Specifics:
90 * The SASL_LOG_* constants were renamed in SASLv2.
92 * SASLv2's sasl_server_new takes two new parameters to specify local and
93 * remote IP addresses for auth mechs that use them.
95 * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr
98 * SASLv2's sasl_decode64 function takes an extra parameter for the length of
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.
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)
117 typedef char *MECHANISM_TYPE
;
118 typedef unsigned MECHANISM_COUNT_TYPE
;
119 typedef char *SERVEROUT_TYPE
;
120 typedef void *VOID_SERVEROUT_TYPE
;
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)
134 typedef const char *MECHANISM_TYPE
;
135 typedef int MECHANISM_COUNT_TYPE
;
136 typedef const char *SERVEROUT_TYPE
;
137 typedef const void *VOID_SERVEROUT_TYPE
;
142 * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER
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
;
155 * Forward declarations.
157 static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL
*);
158 static XSASL_SERVER
*xsasl_cyrus_server_create(XSASL_SERVER_IMPL
*,
159 XSASL_SERVER_CREATE_ARGS
*);
160 static void xsasl_cyrus_server_free(XSASL_SERVER
*);
161 static int xsasl_cyrus_server_first(XSASL_SERVER
*, const char *,
162 const char *, VSTRING
*);
163 static int xsasl_cyrus_server_next(XSASL_SERVER
*, const char *, VSTRING
*);
164 static int xsasl_cyrus_server_set_security(XSASL_SERVER
*, const char *);
165 static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER
*);
166 static const char *xsasl_cyrus_server_get_username(XSASL_SERVER
*);
169 * SASL callback interface structure. These call-backs have no per-session
172 #define NO_CALLBACK_CONTEXT 0
174 static sasl_callback_t callbacks
[] = {
175 {SASL_CB_LOG
, &xsasl_cyrus_log
, NO_CALLBACK_CONTEXT
},
176 {SASL_CB_LIST_END
, 0, 0}
179 /* xsasl_cyrus_server_init - create implementation handle */
181 XSASL_SERVER_IMPL
*xsasl_cyrus_server_init(const char *unused_server_type
,
182 const char *path_info
)
184 const char *myname
= "xsasl_cyrus_server_init";
185 XSASL_SERVER_IMPL
*xp
;
188 #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \
189 || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19))
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
202 || sasl_minor
!= SASL_VERSION_MINOR
203 || sasl_step
!= SASL_VERSION_STEP
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
);
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
);
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
);
230 * Initialize the library: load SASL plug-in routines, etc.
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
));
241 * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it
242 * with our own methods or data.
244 xp
= (XSASL_SERVER_IMPL
*) mymalloc(sizeof(*xp
));
245 xp
->create
= xsasl_cyrus_server_create
;
246 xp
->done
= xsasl_cyrus_server_done
;
250 /* xsasl_cyrus_server_done - dispose of implementation */
252 static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL
*impl
)
254 myfree((char *) impl
);
258 /* xsasl_cyrus_server_create - create server instance */
260 static XSASL_SERVER
*xsasl_cyrus_server_create(XSASL_SERVER_IMPL
*unused_impl
,
261 XSASL_SERVER_CREATE_ARGS
*args
)
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;
271 msg_info("%s: SASL service=%s, realm=%s",
272 myname
, args
->service
, args
->user_realm
?
273 args
->user_realm
: "(null)");
276 * The optimizer will eliminate code duplication and/or dead code.
278 #define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \
281 xsasl_cyrus_server_free(&server->xsasl); \
284 sasl_dispose(&sasl_conn); \
290 * Set up a new server context.
292 #define NO_SECURITY_LAYERS (0)
293 #define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0)
294 #define NO_AUTH_REALM ((char *) 0)
296 #if SASL_VERSION_MAJOR >= 2 && defined(USE_SASL_IP_AUTH)
299 * Get IP addresses of local and remote endpoints for SASL.
301 #error "USE_SASL_IP_AUTH is not implemented"
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.
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);
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.
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;
341 if (xsasl_cyrus_server_set_security(&server
->xsasl
, args
->security_options
)
343 XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0);
345 return (&server
->xsasl
);
348 /* xsasl_cyrus_server_set_security - set security properties */
350 static int xsasl_cyrus_server_set_security(XSASL_SERVER
*xp
,
351 const char *sasl_opts_val
)
353 XSASL_CYRUS_SERVER
*server
= (XSASL_CYRUS_SERVER
*) xp
;
354 sasl_security_properties_t sec_props
;
358 * Security options. Some information can be found in the sasl.h include
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
365 if (*sasl_opts_val
== 0) {
366 sec_props
.security_flags
= 0;
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
);
375 sec_props
.maxbufsize
= 0;
376 sec_props
.property_names
= 0;
377 sec_props
.property_values
= 0;
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
);
385 return (XSASL_AUTH_OK
);
388 /* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */
390 static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER
*xp
)
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
;
399 * Get the list of authentication mechanisms.
401 #define UNSUPPORTED_USER ((char *) 0)
402 #define IGNORE_MECHANISM_LEN ((unsigned *) 0)
404 if ((sasl_status
= sasl_listmech(server
->sasl_conn
, UNSUPPORTED_USER
,
407 IGNORE_MECHANISM_LEN
,
408 &mechanism_count
)) != SASL_OK
) {
409 msg_warn("%s: %s", myname
, xsasl_cyrus_strerror(sasl_status
));
412 if (mechanism_count
<= 0) {
413 msg_warn("%s: no applicable SASL mechanisms", myname
);
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
);
421 return (server
->mechanism_list
);
424 /* xsasl_cyrus_server_free - destroy server instance */
426 static void xsasl_cyrus_server_free(XSASL_SERVER
*xp
)
428 XSASL_CYRUS_SERVER
*server
= (XSASL_CYRUS_SERVER
*) xp
;
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
);
439 /* xsasl_cyrus_server_auth_response - encode server first/next response */
441 static int xsasl_cyrus_server_auth_response(int sasl_status
,
442 SERVEROUT_TYPE serverout
,
443 unsigned serveroutlen
,
446 const char *myname
= "xsasl_cyrus_server_auth_response";
448 unsigned enc_length_out
;
451 * Encode the server first/next non-error response; otherwise return the
452 * unencoded error text that corresponds to the SASL error status.
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
459 if (sasl_status
== SASL_OK
) {
460 vstring_strcpy(reply
, "");
461 return (XSASL_AUTH_DONE
);
462 } else if (sasl_status
== SASL_CONTINUE
) {
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
);
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
);
483 /* xsasl_cyrus_server_first - per-session authentication */
485 int xsasl_cyrus_server_first(XSASL_SERVER
*xp
, const char *sasl_method
,
486 const char *init_response
, VSTRING
*reply
)
488 const char *myname
= "xsasl_cyrus_server_first";
489 XSASL_CYRUS_SERVER
*server
= (XSASL_CYRUS_SERVER
*) xp
;
493 unsigned serveroutlen
;
495 SERVEROUT_TYPE serverout
= 0;
498 #if SASL_VERSION_MAJOR < 2
499 const char *errstr
= 0;
503 #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
506 msg_info("%s: sasl_method %s%s%s", myname
, sasl_method
,
507 IFELSE(init_response
, ", init_response ", ""),
508 IFELSE(init_response
, init_response
, ""));
511 * SASL authentication protocol start-up. Process any initial client
512 * response that was sent along in the AUTH command.
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
);
526 msg_info("%s: decoded initial response %s", myname
, dec_buffer
);
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. */
540 return (xsasl_status
);
543 /* xsasl_cyrus_server_next - continue authentication */
545 static int xsasl_cyrus_server_next(XSASL_SERVER
*xp
, const char *request
,
548 const char *myname
= "xsasl_cyrus_server_next";
549 XSASL_CYRUS_SERVER
*server
= (XSASL_CYRUS_SERVER
*) xp
;
551 unsigned request_len
;
552 unsigned serveroutlen
;
554 SERVEROUT_TYPE serverout
= 0;
557 #if SASL_VERSION_MAJOR < 2
558 const char *errstr
= 0;
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
);
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. */
584 return (xsasl_status
);
587 /* xsasl_cyrus_server_get_username - get authenticated username */
589 static const char *xsasl_cyrus_server_get_username(XSASL_SERVER
*xp
)
591 const char *myname
= "xsasl_cyrus_server_get_username";
592 XSASL_CYRUS_SERVER
*server
= (XSASL_CYRUS_SERVER
*) xp
;
593 VOID_SERVEROUT_TYPE serverout
= 0;
597 * XXX Do not free(serverout).
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
));
605 if (server
->username
)
606 myfree(server
->username
);
607 server
->username
= mystrdup(serverout
);
608 printable(server
->username
, '?');
609 return (server
->username
);