5 /* xsasl_dovecot_server 3
7 /* Dovecot SASL server-side plug-in
9 /* XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name)
10 /* const char *server_type;
11 /* const char *appl_name;
13 /* This module implements the Dovecot SASL server-side authentication
17 /* The plug-in type that was specified to xsasl_server_init().
18 /* The argument is ignored, because the Dovecot plug-in
19 /* implements only one plug-in type.
21 /* The location of the Dovecot authentication server's UNIX-domain
22 /* socket. Note: the Dovecot plug-in uses late binding, therefore
23 /* all connect operations are done with Postfix privileges.
25 /* Fatal: out of memory.
27 /* Panic: interface violation.
29 /* Other: the routines log a warning and return an error result
30 /* as specified in xsasl_server(3).
34 /* The Secure Mailer license must be distributed with this software.
36 /* Initial implementation by:
43 /* IBM T.J. Watson Research
45 /* Yorktown Heights, NY 10598, USA
55 #ifdef STRCASECMP_IN_STRINGS_H
59 /* Utility library. */
65 #include <stringops.h>
67 #include <vstring_vstream.h>
68 #include <name_mask.h>
70 #include <myaddrinfo.h>
74 #include <mail_params.h>
76 /* Application-specific. */
79 #include <xsasl_dovecot.h>
83 /* Major version changes are not backwards compatible,
84 minor version numbers can be ignored. */
85 #define AUTH_PROTOCOL_MAJOR_VERSION 1
86 #define AUTH_PROTOCOL_MINOR_VERSION 0
89 * Encorce read/write time limits, so that we can produce accurate
90 * diagnostics instead of getting killed by the watchdog timer.
92 #define AUTH_TIMEOUT 10
95 * Security property bitmasks.
97 #define SEC_PROPS_NOPLAINTEXT (1 << 0)
98 #define SEC_PROPS_NOACTIVE (1 << 1)
99 #define SEC_PROPS_NODICTIONARY (1 << 2)
100 #define SEC_PROPS_NOANONYMOUS (1 << 3)
101 #define SEC_PROPS_FWD_SECRECY (1 << 4)
102 #define SEC_PROPS_MUTUAL_AUTH (1 << 5)
103 #define SEC_PROPS_PRIVATE (1 << 6)
105 #define SEC_PROPS_POS_MASK (SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY)
106 #define SEC_PROPS_NEG_MASK (SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \
107 SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS)
110 * Security properties as specified in the Postfix main.cf file.
112 static const NAME_MASK xsasl_dovecot_conf_sec_props
[] = {
113 "noplaintext", SEC_PROPS_NOPLAINTEXT
,
114 "noactive", SEC_PROPS_NOACTIVE
,
115 "nodictionary", SEC_PROPS_NODICTIONARY
,
116 "noanonymous", SEC_PROPS_NOANONYMOUS
,
117 "forward_secrecy", SEC_PROPS_FWD_SECRECY
,
118 "mutual_auth", SEC_PROPS_MUTUAL_AUTH
,
123 * Security properties as specified in the Dovecot protocol. See
124 * http://wiki.dovecot.org/Authentication_Protocol.
126 static const NAME_MASK xsasl_dovecot_serv_sec_props
[] = {
127 "plaintext", SEC_PROPS_NOPLAINTEXT
,
128 "active", SEC_PROPS_NOACTIVE
,
129 "dictionary", SEC_PROPS_NODICTIONARY
,
130 "anonymous", SEC_PROPS_NOANONYMOUS
,
131 "forward-secrecy", SEC_PROPS_FWD_SECRECY
,
132 "mutual-auth", SEC_PROPS_MUTUAL_AUTH
,
133 "private", SEC_PROPS_PRIVATE
,
140 typedef struct XSASL_DCSRV_MECH
{
141 char *mech_name
; /* mechanism name */
142 int sec_props
; /* mechanism properties */
143 struct XSASL_DCSRV_MECH
*next
;
147 XSASL_SERVER_IMPL xsasl
;
148 VSTREAM
*sasl_stream
;
150 XSASL_DCSRV_MECH
*mechanism_list
; /* unfiltered mechanism list */
151 unsigned int request_id_counter
;
152 } XSASL_DOVECOT_SERVER_IMPL
;
155 * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER
159 XSASL_SERVER xsasl
; /* generic members, must be first */
160 XSASL_DOVECOT_SERVER_IMPL
*impl
;
161 unsigned int last_request_id
;
163 char *username
; /* authenticated user */
165 unsigned int sec_props
; /* Postfix mechanism filter */
166 int tls_flag
; /* TLS enabled in this session */
167 char *mechanism_list
; /* filtered mechanism list */
168 ARGV
*mechanism_argv
; /* ditto */
169 char *client_addr
; /* remote IP address */
170 char *server_addr
; /* remote IP address */
171 } XSASL_DOVECOT_SERVER
;
174 * Forward declarations.
176 static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL
*);
177 static XSASL_SERVER
*xsasl_dovecot_server_create(XSASL_SERVER_IMPL
*,
178 XSASL_SERVER_CREATE_ARGS
*);
179 static void xsasl_dovecot_server_free(XSASL_SERVER
*);
180 static int xsasl_dovecot_server_first(XSASL_SERVER
*, const char *,
181 const char *, VSTRING
*);
182 static int xsasl_dovecot_server_next(XSASL_SERVER
*, const char *, VSTRING
*);
183 static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER
*);
184 static const char *xsasl_dovecot_server_get_username(XSASL_SERVER
*);
186 /* xsasl_dovecot_server_mech_append - append server mechanism entry */
188 static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH
**mech_list
,
189 const char *mech_name
, int sec_props
)
191 XSASL_DCSRV_MECH
**mpp
;
192 XSASL_DCSRV_MECH
*mp
;
194 for (mpp
= mech_list
; *mpp
!= 0; mpp
= &mpp
[0]->next
)
197 mp
= (XSASL_DCSRV_MECH
*) mymalloc(sizeof(*mp
));
198 mp
->mech_name
= mystrdup(mech_name
);
199 mp
->sec_props
= sec_props
;
204 /* xsasl_dovecot_server_mech_free - destroy server mechanism list */
206 static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH
*mech_list
)
208 XSASL_DCSRV_MECH
*mp
;
209 XSASL_DCSRV_MECH
*next
;
211 for (mp
= mech_list
; mp
!= 0; mp
= next
) {
212 myfree(mp
->mech_name
);
218 /* xsasl_dovecot_server_mech_filter - filter server mechanism list */
220 static char *xsasl_dovecot_server_mech_filter(ARGV
*mechanism_argv
,
221 XSASL_DCSRV_MECH
*mechanism_list
,
222 unsigned int conf_props
)
224 const char *myname
= "xsasl_dovecot_server_mech_filter";
225 unsigned int pos_conf_props
= (conf_props
& SEC_PROPS_POS_MASK
);
226 unsigned int neg_conf_props
= (conf_props
& SEC_PROPS_NEG_MASK
);
227 VSTRING
*mechanisms_str
= vstring_alloc(10);
228 XSASL_DCSRV_MECH
*mp
;
231 * Match Postfix properties against Dovecot server properties.
233 for (mp
= mechanism_list
; mp
!= 0; mp
= mp
->next
) {
234 if ((mp
->sec_props
& pos_conf_props
) == pos_conf_props
235 && (mp
->sec_props
& neg_conf_props
) == 0) {
236 if (VSTRING_LEN(mechanisms_str
) > 0)
237 VSTRING_ADDCH(mechanisms_str
, ' ');
238 vstring_strcat(mechanisms_str
, mp
->mech_name
);
239 argv_add(mechanism_argv
, mp
->mech_name
, (char *) 0);
241 msg_info("%s: keep mechanism: %s", myname
, mp
->mech_name
);
244 msg_info("%s: skip mechanism: %s", myname
, mp
->mech_name
);
247 return (vstring_export(mechanisms_str
));
250 /* xsasl_dovecot_server_connect - initial auth server handshake */
252 static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL
*xp
)
254 const char *myname
= "xsasl_dovecot_server_connect";
256 VSTREAM
*sasl_stream
;
257 char *line
, *cmd
, *mech_name
;
258 unsigned int major_version
, minor_version
;
264 msg_info("%s: Connecting", myname
);
267 * Not documented, but necessary for testing.
269 path
= xp
->socket_path
;
270 if (strncmp(path
, "inet:", 5) == 0) {
271 fd
= inet_connect(path
+ 5, BLOCKING
, AUTH_TIMEOUT
);
273 if (strncmp(path
, "unix:", 5) == 0)
275 fd
= unix_connect(path
, BLOCKING
, AUTH_TIMEOUT
);
278 msg_warn("SASL: Connect to %s failed: %m", xp
->socket_path
);
281 sasl_stream
= vstream_fdopen(fd
, O_RDWR
);
282 vstream_control(sasl_stream
,
283 VSTREAM_CTL_PATH
, xp
->socket_path
,
284 VSTREAM_CTL_TIMEOUT
, AUTH_TIMEOUT
,
287 /* XXX Encapsulate for logging. */
288 vstream_fprintf(sasl_stream
,
291 AUTH_PROTOCOL_MAJOR_VERSION
,
292 AUTH_PROTOCOL_MINOR_VERSION
,
293 (unsigned int) getpid());
294 if (vstream_fflush(sasl_stream
) == VSTREAM_EOF
) {
295 msg_warn("SASL: Couldn't send handshake: %m");
299 line_str
= vstring_alloc(256);
300 /* XXX Encapsulate for logging. */
301 while (vstring_get_nonl(line_str
, sasl_stream
) != VSTREAM_EOF
) {
302 line
= vstring_str(line_str
);
305 msg_info("%s: auth reply: %s", myname
, line
);
308 line
= split_at(line
, '\t');
310 if (strcmp(cmd
, "VERSION") == 0) {
311 if (sscanf(line
, "%u\t%u", &major_version
, &minor_version
) != 2) {
312 msg_warn("SASL: Protocol version error");
315 if (major_version
!= AUTH_PROTOCOL_MAJOR_VERSION
) {
316 /* Major version is different from ours. */
317 msg_warn("SASL: Protocol version mismatch (%d vs. %d)",
318 major_version
, AUTH_PROTOCOL_MAJOR_VERSION
);
321 } else if (strcmp(cmd
, "MECH") == 0 && line
!= NULL
) {
323 line
= split_at(line
, '\t');
326 name_mask_delim_opt(myname
,
327 xsasl_dovecot_serv_sec_props
,
328 line
, "\t", NAME_MASK_ANY_CASE
);
329 if ((sec_props
& SEC_PROPS_PRIVATE
) != 0)
333 xsasl_dovecot_server_mech_append(&xp
->mechanism_list
, mech_name
,
335 } else if (strcmp(cmd
, "DONE") == 0) {
336 /* Handshake finished. */
340 /* ignore any unknown commands */
343 vstring_free(line_str
);
346 /* handshake failed */
347 (void) vstream_fclose(sasl_stream
);
350 xp
->sasl_stream
= sasl_stream
;
354 /* xsasl_dovecot_server_disconnect - dispose of server connection state */
356 static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL
*xp
)
358 if (xp
->sasl_stream
) {
359 (void) vstream_fclose(xp
->sasl_stream
);
362 if (xp
->mechanism_list
) {
363 xsasl_dovecot_server_mech_free(xp
->mechanism_list
);
364 xp
->mechanism_list
= 0;
368 /* xsasl_dovecot_server_init - create implementation handle */
370 XSASL_SERVER_IMPL
*xsasl_dovecot_server_init(const char *unused_server_type
,
371 const char *path_info
)
373 XSASL_DOVECOT_SERVER_IMPL
*xp
;
375 xp
= (XSASL_DOVECOT_SERVER_IMPL
*) mymalloc(sizeof(*xp
));
376 xp
->xsasl
.create
= xsasl_dovecot_server_create
;
377 xp
->xsasl
.done
= xsasl_dovecot_server_done
;
378 xp
->socket_path
= mystrdup(path_info
);
380 xp
->mechanism_list
= 0;
381 xp
->request_id_counter
= 0;
385 /* xsasl_dovecot_server_done - dispose of implementation */
387 static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL
*impl
)
389 XSASL_DOVECOT_SERVER_IMPL
*xp
= (XSASL_DOVECOT_SERVER_IMPL
*) impl
;
391 xsasl_dovecot_server_disconnect(xp
);
392 myfree(xp
->socket_path
);
393 myfree((char *) impl
);
396 /* xsasl_dovecot_server_create - create server instance */
398 static XSASL_SERVER
*xsasl_dovecot_server_create(XSASL_SERVER_IMPL
*impl
,
399 XSASL_SERVER_CREATE_ARGS
*args
)
401 const char *myname
= "xsasl_dovecot_server_create";
402 XSASL_DOVECOT_SERVER
*server
;
403 struct sockaddr_storage ss
;
404 struct sockaddr
*sa
= (struct sockaddr
*) & ss
;
406 MAI_HOSTADDR_STR server_addr
;
409 msg_info("%s: SASL service=%s, realm=%s",
410 myname
, args
->service
, args
->user_realm
?
411 args
->user_realm
: "(null)");
414 * Extend the XSASL_SERVER_IMPL object with our own data. We use
415 * long-lived conversion buffers rather than local variables to avoid
416 * memory leaks in case of read/write timeout or I/O error.
418 server
= (XSASL_DOVECOT_SERVER
*) mymalloc(sizeof(*server
));
419 server
->xsasl
.free
= xsasl_dovecot_server_free
;
420 server
->xsasl
.first
= xsasl_dovecot_server_first
;
421 server
->xsasl
.next
= xsasl_dovecot_server_next
;
422 server
->xsasl
.get_mechanism_list
= xsasl_dovecot_server_get_mechanism_list
;
423 server
->xsasl
.get_username
= xsasl_dovecot_server_get_username
;
424 server
->impl
= (XSASL_DOVECOT_SERVER_IMPL
*) impl
;
425 server
->sasl_line
= vstring_alloc(256);
426 server
->username
= 0;
427 server
->service
= mystrdup(args
->service
);
428 server
->last_request_id
= 0;
429 server
->mechanism_list
= 0;
430 server
->mechanism_argv
= 0;
431 server
->tls_flag
= args
->tls_flag
;
433 name_mask_opt(myname
, xsasl_dovecot_conf_sec_props
,
434 args
->security_options
,
435 NAME_MASK_ANY_CASE
| NAME_MASK_FATAL
);
436 server
->client_addr
= mystrdup(args
->client_addr
);
439 * XXX Temporary code until smtpd_peer.c is updated.
441 if (args
->server_addr
&& *args
->server_addr
) {
442 server
->server_addr
= mystrdup(args
->server_addr
);
445 if (getsockname(vstream_fileno(args
->stream
), sa
, &salen
) < 0
446 || sockaddr_to_hostaddr(sa
, salen
, &server_addr
, 0, 0) != 0)
447 server_addr
.buf
[0] = 0;
448 server
->server_addr
= mystrdup(server_addr
.buf
);
451 return (&server
->xsasl
);
454 /* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */
456 static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER
*xp
)
458 XSASL_DOVECOT_SERVER
*server
= (XSASL_DOVECOT_SERVER
*) xp
;
460 if (!server
->impl
->sasl_stream
) {
461 if (xsasl_dovecot_server_connect(server
->impl
) < 0)
464 if (server
->mechanism_list
== 0) {
465 server
->mechanism_argv
= argv_alloc(2);
466 server
->mechanism_list
=
467 xsasl_dovecot_server_mech_filter(server
->mechanism_argv
,
468 server
->impl
->mechanism_list
,
471 return (server
->mechanism_list
[0] ? server
->mechanism_list
: 0);
474 /* xsasl_dovecot_server_free - destroy server instance */
476 static void xsasl_dovecot_server_free(XSASL_SERVER
*xp
)
478 XSASL_DOVECOT_SERVER
*server
= (XSASL_DOVECOT_SERVER
*) xp
;
480 vstring_free(server
->sasl_line
);
481 if (server
->username
)
482 myfree(server
->username
);
483 if (server
->mechanism_list
) {
484 myfree(server
->mechanism_list
);
485 argv_free(server
->mechanism_argv
);
487 myfree(server
->service
);
488 myfree(server
->server_addr
);
489 myfree(server
->client_addr
);
490 myfree((char *) server
);
493 /* xsasl_dovecot_server_auth_response - encode server first/next response */
495 static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER
*server
, char **line
)
500 msg_warn("SASL: Protocol error");
504 *line
= split_at(*line
, '\t');
506 if (strtoul(id
, NULL
, 0) != server
->last_request_id
) {
507 /* reply to another request, shouldn't really happen.. */
513 static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER
*server
,
514 char *line
, VSTRING
*reply
,
519 if (server
->username
) {
520 myfree(server
->username
);
521 server
->username
= 0;
525 * Note: TAB is part of the Dovecot protocol and must not appear in
526 * legitimate Dovecot usernames, otherwise the protocol would break.
528 for (; line
!= NULL
; line
= next
) {
529 next
= split_at(line
, '\t');
530 if (strncmp(line
, "user=", 5) == 0) {
531 server
->username
= mystrdup(line
+ 5);
532 printable(server
->username
, '?');
533 } else if (strncmp(line
, "reason=", 7) == 0) {
535 printable(line
+ 7, '?');
536 vstring_strcpy(reply
, line
+ 7);
542 /* xsasl_dovecot_handle_reply - receive and process auth reply */
544 static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER
*server
,
547 const char *myname
= "xsasl_dovecot_handle_reply";
550 /* XXX Encapsulate for logging. */
551 while (vstring_get_nonl(server
->sasl_line
,
552 server
->impl
->sasl_stream
) != VSTREAM_EOF
) {
553 line
= vstring_str(server
->sasl_line
);
556 msg_info("%s: auth reply: %s", myname
, line
);
559 line
= split_at(line
, '\t');
561 if (strcmp(cmd
, "OK") == 0) {
562 if (xsasl_dovecot_parse_reply(server
, &line
) == 0) {
563 /* authentication successful */
564 xsasl_dovecot_parse_reply_args(server
, line
, reply
, 1);
565 return XSASL_AUTH_DONE
;
567 } else if (strcmp(cmd
, "CONT") == 0) {
568 if (xsasl_dovecot_parse_reply(server
, &line
) == 0) {
569 vstring_strcpy(reply
, line
);
570 return XSASL_AUTH_MORE
;
572 } else if (strcmp(cmd
, "FAIL") == 0) {
573 if (xsasl_dovecot_parse_reply(server
, &line
) == 0) {
574 /* authentication failure */
575 xsasl_dovecot_parse_reply_args(server
, line
, reply
, 0);
576 return XSASL_AUTH_FAIL
;
583 vstring_strcpy(reply
, "Connection lost to authentication server");
584 return XSASL_AUTH_FAIL
;
587 /* is_valid_base64 - input sanitized */
589 static int is_valid_base64(const char *data
)
593 * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent).
595 for (; *data
!= '\0'; data
++) {
596 if (!((*data
>= '0' && *data
<= '9') ||
597 (*data
>= 'a' && *data
<= 'z') ||
598 (*data
>= 'A' && *data
<= 'Z') ||
599 *data
== '+' || *data
== '/' || *data
== '='))
605 /* xsasl_dovecot_server_first - per-session authentication */
607 int xsasl_dovecot_server_first(XSASL_SERVER
*xp
, const char *sasl_method
,
608 const char *init_response
, VSTRING
*reply
)
610 const char *myname
= "xsasl_dovecot_server_first";
611 XSASL_DOVECOT_SERVER
*server
= (XSASL_DOVECOT_SERVER
*) xp
;
615 #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
618 msg_info("%s: sasl_method %s%s%s", myname
, sasl_method
,
619 IFELSE(init_response
, ", init_response ", ""),
620 IFELSE(init_response
, init_response
, ""));
622 if (server
->mechanism_argv
== 0)
623 msg_panic("%s: no mechanism list", myname
);
625 for (cpp
= server
->mechanism_argv
->argv
; /* see below */ ; cpp
++) {
627 vstring_strcpy(reply
, "Invalid authentication mechanism");
628 return XSASL_AUTH_FAIL
;
630 if (strcasecmp(sasl_method
, *cpp
) == 0)
634 if (!is_valid_base64(init_response
)) {
635 vstring_strcpy(reply
, "Invalid base64 data in initial response");
636 return XSASL_AUTH_FAIL
;
638 for (i
= 0; i
< 2; i
++) {
639 if (!server
->impl
->sasl_stream
) {
640 if (xsasl_dovecot_server_connect(server
->impl
) < 0)
643 /* send the request */
644 server
->last_request_id
= ++server
->impl
->request_id_counter
;
645 /* XXX Encapsulate for logging. */
646 vstream_fprintf(server
->impl
->sasl_stream
,
647 "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s",
648 server
->last_request_id
, sasl_method
,
649 server
->service
, server
->server_addr
,
650 server
->client_addr
);
651 if (server
->tls_flag
)
652 /* XXX Encapsulate for logging. */
653 vstream_fputs("\tsecured", server
->impl
->sasl_stream
);
657 * initial response is already base64 encoded, so we can send it
660 /* XXX Encapsulate for logging. */
661 vstream_fprintf(server
->impl
->sasl_stream
,
662 "\tresp=%s", init_response
);
664 /* XXX Encapsulate for logging. */
665 VSTREAM_PUTC('\n', server
->impl
->sasl_stream
);
667 if (vstream_fflush(server
->impl
->sasl_stream
) != VSTREAM_EOF
)
671 vstring_strcpy(reply
, "Can't connect to authentication server");
672 return XSASL_AUTH_FAIL
;
676 * Reconnect and try again.
678 xsasl_dovecot_server_disconnect(server
->impl
);
681 return xsasl_dovecot_handle_reply(server
, reply
);
684 /* xsasl_dovecot_server_next - continue authentication */
686 static int xsasl_dovecot_server_next(XSASL_SERVER
*xp
, const char *request
,
689 XSASL_DOVECOT_SERVER
*server
= (XSASL_DOVECOT_SERVER
*) xp
;
691 if (!is_valid_base64(request
)) {
692 vstring_strcpy(reply
, "Invalid base64 data in continued response");
693 return XSASL_AUTH_FAIL
;
695 /* XXX Encapsulate for logging. */
696 vstream_fprintf(server
->impl
->sasl_stream
,
697 "CONT\t%u\t%s\n", server
->last_request_id
, request
);
698 if (vstream_fflush(server
->impl
->sasl_stream
) == VSTREAM_EOF
) {
699 vstring_strcpy(reply
, "Connection lost to authentication server");
700 return XSASL_AUTH_FAIL
;
702 return xsasl_dovecot_handle_reply(server
, reply
);
705 /* xsasl_dovecot_server_get_username - get authenticated username */
707 static const char *xsasl_dovecot_server_get_username(XSASL_SERVER
*xp
)
709 XSASL_DOVECOT_SERVER
*server
= (XSASL_DOVECOT_SERVER
*) xp
;
711 return (server
->username
);