xref: /netbsd-src/external/ibm-public/postfix/dist/src/xsasl/xsasl_dovecot_server.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: xsasl_dovecot_server.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	xsasl_dovecot_server 3
6 /* SUMMARY
7 /*	Dovecot SASL server-side plug-in
8 /* SYNOPSIS
9 /*	XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name)
10 /*	const char *server_type;
11 /*	const char *appl_name;
12 /* DESCRIPTION
13 /*	This module implements the Dovecot SASL server-side authentication
14 /*	plug-in.
15 /*
16 /* .IP server_type
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.
20 /* .IP path_info
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.
24 /* DIAGNOSTICS
25 /*	Fatal: out of memory.
26 /*
27 /*	Panic: interface violation.
28 /*
29 /*	Other: the routines log a warning and return an error result
30 /*	as specified in xsasl_server(3).
31 /* LICENSE
32 /* .ad
33 /* .fi
34 /*	The Secure Mailer license must be distributed with this software.
35 /* AUTHOR(S)
36 /*	Initial implementation by:
37 /*	Timo Sirainen
38 /*	Procontrol
39 /*	Finland
40 /*
41 /*	Adopted by:
42 /*	Wietse Venema
43 /*	IBM T.J. Watson Research
44 /*	P.O. Box 704
45 /*	Yorktown Heights, NY 10598, USA
46 /*
47 /*	Wietse Venema
48 /*	Google, Inc.
49 /*	111 8th Avenue
50 /*	New York, NY 10011, USA
51 /*--*/
52 
53 /* System library. */
54 
55 #include <sys_defs.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 
60 #ifdef STRCASECMP_IN_STRINGS_H
61 #include <strings.h>
62 #endif
63 
64 /* Utility library. */
65 
66 #include <msg.h>
67 #include <mymalloc.h>
68 #include <connect.h>
69 #include <split_at.h>
70 #include <stringops.h>
71 #include <vstream.h>
72 #include <vstring_vstream.h>
73 #include <name_mask.h>
74 #include <argv.h>
75 #include <myaddrinfo.h>
76 
77 /* Global library. */
78 
79 #include <mail_params.h>
80 
81 /* Application-specific. */
82 
83 #include <xsasl.h>
84 #include <xsasl_dovecot.h>
85 
86 #ifdef USE_SASL_AUTH
87 
88 /* Major version changes are not backwards compatible,
89    minor version numbers can be ignored. */
90 #define AUTH_PROTOCOL_MAJOR_VERSION 1
91 #define AUTH_PROTOCOL_MINOR_VERSION 0
92 
93  /*
94   * Encorce read/write time limits, so that we can produce accurate
95   * diagnostics instead of getting killed by the watchdog timer.
96   */
97 #define AUTH_TIMEOUT	10
98 
99  /*
100   * Security property bitmasks.
101   */
102 #define SEC_PROPS_NOPLAINTEXT	(1 << 0)
103 #define SEC_PROPS_NOACTIVE	(1 << 1)
104 #define SEC_PROPS_NODICTIONARY	(1 << 2)
105 #define SEC_PROPS_NOANONYMOUS	(1 << 3)
106 #define SEC_PROPS_FWD_SECRECY	(1 << 4)
107 #define SEC_PROPS_MUTUAL_AUTH	(1 << 5)
108 #define SEC_PROPS_PRIVATE	(1 << 6)
109 
110 #define SEC_PROPS_POS_MASK	(SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY)
111 #define SEC_PROPS_NEG_MASK	(SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \
112 				SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS)
113 
114  /*
115   * Security properties as specified in the Postfix main.cf file.
116   */
117 static const NAME_MASK xsasl_dovecot_conf_sec_props[] = {
118     "noplaintext", SEC_PROPS_NOPLAINTEXT,
119     "noactive", SEC_PROPS_NOACTIVE,
120     "nodictionary", SEC_PROPS_NODICTIONARY,
121     "noanonymous", SEC_PROPS_NOANONYMOUS,
122     "forward_secrecy", SEC_PROPS_FWD_SECRECY,
123     "mutual_auth", SEC_PROPS_MUTUAL_AUTH,
124     0, 0,
125 };
126 
127  /*
128   * Security properties as specified in the Dovecot protocol. See
129   * http://wiki.dovecot.org/Authentication_Protocol.
130   */
131 static const NAME_MASK xsasl_dovecot_serv_sec_props[] = {
132     "plaintext", SEC_PROPS_NOPLAINTEXT,
133     "active", SEC_PROPS_NOACTIVE,
134     "dictionary", SEC_PROPS_NODICTIONARY,
135     "anonymous", SEC_PROPS_NOANONYMOUS,
136     "forward-secrecy", SEC_PROPS_FWD_SECRECY,
137     "mutual-auth", SEC_PROPS_MUTUAL_AUTH,
138     "private", SEC_PROPS_PRIVATE,
139     0, 0,
140 };
141 
142  /*
143   * Class variables.
144   */
145 typedef struct XSASL_DCSRV_MECH {
146     char   *mech_name;			/* mechanism name */
147     int     sec_props;			/* mechanism properties */
148     struct XSASL_DCSRV_MECH *next;
149 } XSASL_DCSRV_MECH;
150 
151 typedef struct {
152     XSASL_SERVER_IMPL xsasl;
153     VSTREAM *sasl_stream;
154     char   *socket_path;
155     XSASL_DCSRV_MECH *mechanism_list;	/* unfiltered mechanism list */
156     unsigned int request_id_counter;
157 } XSASL_DOVECOT_SERVER_IMPL;
158 
159  /*
160   * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER
161   * object.
162   */
163 typedef struct {
164     XSASL_SERVER xsasl;			/* generic members, must be first */
165     XSASL_DOVECOT_SERVER_IMPL *impl;
166     unsigned int last_request_id;
167     char   *service;
168     char   *username;			/* authenticated user */
169     VSTRING *sasl_line;
170     unsigned int sec_props;		/* Postfix mechanism filter */
171     int     tls_flag;			/* TLS enabled in this session */
172     char   *mechanism_list;		/* filtered mechanism list */
173     ARGV   *mechanism_argv;		/* ditto */
174     char   *client_addr;		/* remote IP address */
175     char   *server_addr;		/* remote IP address */
176 } XSASL_DOVECOT_SERVER;
177 
178  /*
179   * Forward declarations.
180   */
181 static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *);
182 static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *,
183 					        XSASL_SERVER_CREATE_ARGS *);
184 static void xsasl_dovecot_server_free(XSASL_SERVER *);
185 static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *,
186 				              const char *, VSTRING *);
187 static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *);
188 static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *);
189 static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *);
190 
191 /* xsasl_dovecot_server_mech_append - append server mechanism entry */
192 
193 static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH **mech_list,
194 			               const char *mech_name, int sec_props)
195 {
196     XSASL_DCSRV_MECH **mpp;
197     XSASL_DCSRV_MECH *mp;
198 
199     for (mpp = mech_list; *mpp != 0; mpp = &mpp[0]->next)
200 	 /* void */ ;
201 
202     mp = (XSASL_DCSRV_MECH *) mymalloc(sizeof(*mp));
203     mp->mech_name = mystrdup(mech_name);
204     mp->sec_props = sec_props;
205     mp->next = 0;
206     *mpp = mp;
207 }
208 
209 /* xsasl_dovecot_server_mech_free - destroy server mechanism list */
210 
211 static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH *mech_list)
212 {
213     XSASL_DCSRV_MECH *mp;
214     XSASL_DCSRV_MECH *next;
215 
216     for (mp = mech_list; mp != 0; mp = next) {
217 	myfree(mp->mech_name);
218 	next = mp->next;
219 	myfree((void *) mp);
220     }
221 }
222 
223 /* xsasl_dovecot_server_mech_filter - filter server mechanism list */
224 
225 static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv,
226 				           XSASL_DCSRV_MECH *mechanism_list,
227 					            unsigned int conf_props)
228 {
229     const char *myname = "xsasl_dovecot_server_mech_filter";
230     unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK);
231     unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK);
232     VSTRING *mechanisms_str = vstring_alloc(10);
233     XSASL_DCSRV_MECH *mp;
234 
235     /*
236      * Match Postfix properties against Dovecot server properties.
237      */
238     for (mp = mechanism_list; mp != 0; mp = mp->next) {
239 	if ((mp->sec_props & pos_conf_props) == pos_conf_props
240 	    && (mp->sec_props & neg_conf_props) == 0) {
241 	    if (VSTRING_LEN(mechanisms_str) > 0)
242 		VSTRING_ADDCH(mechanisms_str, ' ');
243 	    vstring_strcat(mechanisms_str, mp->mech_name);
244 	    argv_add(mechanism_argv, mp->mech_name, (char *) 0);
245 	    if (msg_verbose)
246 		msg_info("%s: keep mechanism: %s", myname, mp->mech_name);
247 	} else {
248 	    if (msg_verbose)
249 		msg_info("%s: skip mechanism: %s", myname, mp->mech_name);
250 	}
251     }
252     return (vstring_export(mechanisms_str));
253 }
254 
255 /* xsasl_dovecot_server_connect - initial auth server handshake */
256 
257 static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp)
258 {
259     const char *myname = "xsasl_dovecot_server_connect";
260     VSTRING *line_str;
261     VSTREAM *sasl_stream;
262     char   *line, *cmd, *mech_name;
263     unsigned int major_version, minor_version;
264     int     fd, success, have_mech_line;
265     int     sec_props;
266     const char *path;
267 
268     if (msg_verbose)
269 	msg_info("%s: Connecting", myname);
270 
271     /*
272      * Not documented, but necessary for testing.
273      */
274     path = xp->socket_path;
275     if (strncmp(path, "inet:", 5) == 0) {
276 	fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT);
277     } else {
278 	if (strncmp(path, "unix:", 5) == 0)
279 	    path += 5;
280 	fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT);
281     }
282     if (fd < 0) {
283 	msg_warn("SASL: Connect to %s failed: %m", xp->socket_path);
284 	return (-1);
285     }
286     sasl_stream = vstream_fdopen(fd, O_RDWR);
287     vstream_control(sasl_stream,
288 		    CA_VSTREAM_CTL_PATH(xp->socket_path),
289 		    CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT),
290 		    CA_VSTREAM_CTL_END);
291 
292     /* XXX Encapsulate for logging. */
293     vstream_fprintf(sasl_stream,
294 		    "VERSION\t%u\t%u\n"
295 		    "CPID\t%u\n",
296 		    AUTH_PROTOCOL_MAJOR_VERSION,
297 		    AUTH_PROTOCOL_MINOR_VERSION,
298 		    (unsigned int) getpid());
299     if (vstream_fflush(sasl_stream) == VSTREAM_EOF) {
300 	msg_warn("SASL: Couldn't send handshake: %m");
301 	return (-1);
302     }
303     success = 0;
304     have_mech_line = 0;
305     line_str = vstring_alloc(256);
306     /* XXX Encapsulate for logging. */
307     while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) {
308 	line = vstring_str(line_str);
309 
310 	if (msg_verbose)
311 	    msg_info("%s: auth reply: %s", myname, line);
312 
313 	cmd = line;
314 	line = split_at(line, '\t');
315 
316 	if (strcmp(cmd, "VERSION") == 0) {
317 	    if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) {
318 		msg_warn("SASL: Protocol version error");
319 		break;
320 	    }
321 	    if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) {
322 		/* Major version is different from ours. */
323 		msg_warn("SASL: Protocol version mismatch (%d vs. %d)",
324 			 major_version, AUTH_PROTOCOL_MAJOR_VERSION);
325 		break;
326 	    }
327 	} else if (strcmp(cmd, "MECH") == 0 && line != NULL) {
328 	    mech_name = line;
329 	    have_mech_line = 1;
330 	    line = split_at(line, '\t');
331 	    if (line != 0) {
332 		sec_props =
333 		    name_mask_delim_opt(myname,
334 					xsasl_dovecot_serv_sec_props,
335 					line, "\t",
336 				     NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
337 		if ((sec_props & SEC_PROPS_PRIVATE) != 0)
338 		    continue;
339 	    } else
340 		sec_props = 0;
341 	    xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name,
342 					     sec_props);
343 	} else if (strcmp(cmd, "SPID") == 0) {
344 
345 	    /*
346 	     * Unfortunately the auth protocol handshake wasn't designed well
347 	     * to differentiate between auth-client/userdb/master.
348 	     * auth-userdb and auth-master send VERSION + SPID lines only and
349 	     * nothing afterwards, while auth-client sends VERSION + MECH +
350 	     * SPID + CUID + more. The simplest way that we can determine if
351 	     * we've connected to the correct socket is to see if MECH line
352 	     * exists or not (alternatively we'd have to have a small timeout
353 	     * after SPID to see if CUID is sent or not).
354 	     */
355 	    if (!have_mech_line) {
356 		msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)");
357 		break;
358 	    }
359 	} else if (strcmp(cmd, "DONE") == 0) {
360 	    /* Handshake finished. */
361 	    success = 1;
362 	    break;
363 	} else {
364 	    /* ignore any unknown commands */
365 	}
366     }
367     vstring_free(line_str);
368 
369     if (!success) {
370 	/* handshake failed */
371 	(void) vstream_fclose(sasl_stream);
372 	return (-1);
373     }
374     xp->sasl_stream = sasl_stream;
375     return (0);
376 }
377 
378 /* xsasl_dovecot_server_disconnect - dispose of server connection state */
379 
380 static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp)
381 {
382     if (xp->sasl_stream) {
383 	(void) vstream_fclose(xp->sasl_stream);
384 	xp->sasl_stream = 0;
385     }
386     if (xp->mechanism_list) {
387 	xsasl_dovecot_server_mech_free(xp->mechanism_list);
388 	xp->mechanism_list = 0;
389     }
390 }
391 
392 /* xsasl_dovecot_server_init - create implementation handle */
393 
394 XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type,
395 					             const char *path_info)
396 {
397     XSASL_DOVECOT_SERVER_IMPL *xp;
398 
399     xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp));
400     xp->xsasl.create = xsasl_dovecot_server_create;
401     xp->xsasl.done = xsasl_dovecot_server_done;
402     xp->socket_path = mystrdup(path_info);
403     xp->sasl_stream = 0;
404     xp->mechanism_list = 0;
405     xp->request_id_counter = 0;
406     return (&xp->xsasl);
407 }
408 
409 /* xsasl_dovecot_server_done - dispose of implementation */
410 
411 static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl)
412 {
413     XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl;
414 
415     xsasl_dovecot_server_disconnect(xp);
416     myfree(xp->socket_path);
417     myfree((void *) impl);
418 }
419 
420 /* xsasl_dovecot_server_create - create server instance */
421 
422 static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl,
423 				             XSASL_SERVER_CREATE_ARGS *args)
424 {
425     const char *myname = "xsasl_dovecot_server_create";
426     XSASL_DOVECOT_SERVER *server;
427     struct sockaddr_storage ss;
428     struct sockaddr *sa = (struct sockaddr *) &ss;
429     SOCKADDR_SIZE salen;
430     MAI_HOSTADDR_STR server_addr;
431 
432     if (msg_verbose)
433 	msg_info("%s: SASL service=%s, realm=%s",
434 		 myname, args->service, args->user_realm ?
435 		 args->user_realm : "(null)");
436 
437     /*
438      * Extend the XSASL_SERVER_IMPL object with our own data. We use
439      * long-lived conversion buffers rather than local variables to avoid
440      * memory leaks in case of read/write timeout or I/O error.
441      */
442     server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server));
443     server->xsasl.free = xsasl_dovecot_server_free;
444     server->xsasl.first = xsasl_dovecot_server_first;
445     server->xsasl.next = xsasl_dovecot_server_next;
446     server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list;
447     server->xsasl.get_username = xsasl_dovecot_server_get_username;
448     server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl;
449     server->sasl_line = vstring_alloc(256);
450     server->username = 0;
451     server->service = mystrdup(args->service);
452     server->last_request_id = 0;
453     server->mechanism_list = 0;
454     server->mechanism_argv = 0;
455     server->tls_flag = args->tls_flag;
456     server->sec_props =
457 	name_mask_opt(myname, xsasl_dovecot_conf_sec_props,
458 		      args->security_options,
459 		      NAME_MASK_ANY_CASE | NAME_MASK_FATAL);
460     server->client_addr = mystrdup(args->client_addr);
461 
462     /*
463      * XXX Temporary code until smtpd_peer.c is updated.
464      */
465     if (args->server_addr && *args->server_addr) {
466 	server->server_addr = mystrdup(args->server_addr);
467     } else {
468 	salen = sizeof(ss);
469 	if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0
470 	    || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0)
471 	    server_addr.buf[0] = 0;
472 	server->server_addr = mystrdup(server_addr.buf);
473     }
474 
475     return (&server->xsasl);
476 }
477 
478 /* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */
479 
480 static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp)
481 {
482     XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
483 
484     if (!server->impl->sasl_stream) {
485 	if (xsasl_dovecot_server_connect(server->impl) < 0)
486 	    return (0);
487     }
488     if (server->mechanism_list == 0) {
489 	server->mechanism_argv = argv_alloc(2);
490 	server->mechanism_list =
491 	    xsasl_dovecot_server_mech_filter(server->mechanism_argv,
492 					     server->impl->mechanism_list,
493 					     server->sec_props);
494     }
495     return (server->mechanism_list[0] ? server->mechanism_list : 0);
496 }
497 
498 /* xsasl_dovecot_server_free - destroy server instance */
499 
500 static void xsasl_dovecot_server_free(XSASL_SERVER *xp)
501 {
502     XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
503 
504     vstring_free(server->sasl_line);
505     if (server->username)
506 	myfree(server->username);
507     if (server->mechanism_list) {
508 	myfree(server->mechanism_list);
509 	argv_free(server->mechanism_argv);
510     }
511     myfree(server->service);
512     myfree(server->server_addr);
513     myfree(server->client_addr);
514     myfree((void *) server);
515 }
516 
517 /* xsasl_dovecot_server_auth_response - encode server first/next response */
518 
519 static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line)
520 {
521     char   *id;
522 
523     if (*line == NULL) {
524 	msg_warn("SASL: Protocol error");
525 	return -1;
526     }
527     id = *line;
528     *line = split_at(*line, '\t');
529 
530     if (strtoul(id, NULL, 0) != server->last_request_id) {
531 	/* reply to another request, shouldn't really happen.. */
532 	return -1;
533     }
534     return 0;
535 }
536 
537 static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server,
538 					         char *line, VSTRING *reply,
539 					           int success)
540 {
541     char   *next;
542 
543     if (server->username) {
544 	myfree(server->username);
545 	server->username = 0;
546     }
547 
548     /*
549      * Note: TAB is part of the Dovecot protocol and must not appear in
550      * legitimate Dovecot usernames, otherwise the protocol would break.
551      */
552     for (; line != NULL; line = next) {
553 	next = split_at(line, '\t');
554 	if (strncmp(line, "user=", 5) == 0) {
555 	    server->username = mystrdup(line + 5);
556 	    printable(server->username, '?');
557 	} else if (strncmp(line, "reason=", 7) == 0) {
558 	    if (!success) {
559 		printable(line + 7, '?');
560 		vstring_strcpy(reply, line + 7);
561 	    }
562 	}
563     }
564 }
565 
566 /* xsasl_dovecot_handle_reply - receive and process auth reply */
567 
568 static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server,
569 				              VSTRING *reply)
570 {
571     const char *myname = "xsasl_dovecot_handle_reply";
572     char   *line, *cmd;
573 
574     /* XXX Encapsulate for logging. */
575     while (vstring_get_nonl(server->sasl_line,
576 			    server->impl->sasl_stream) != VSTREAM_EOF) {
577 	line = vstring_str(server->sasl_line);
578 
579 	if (msg_verbose)
580 	    msg_info("%s: auth reply: %s", myname, line);
581 
582 	cmd = line;
583 	line = split_at(line, '\t');
584 
585 	if (strcmp(cmd, "OK") == 0) {
586 	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
587 		/* authentication successful */
588 		xsasl_dovecot_parse_reply_args(server, line, reply, 1);
589 		return XSASL_AUTH_DONE;
590 	    }
591 	} else if (strcmp(cmd, "CONT") == 0) {
592 	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
593 		vstring_strcpy(reply, line);
594 		return XSASL_AUTH_MORE;
595 	    }
596 	} else if (strcmp(cmd, "FAIL") == 0) {
597 	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
598 		/* authentication failure */
599 		xsasl_dovecot_parse_reply_args(server, line, reply, 0);
600 		return XSASL_AUTH_FAIL;
601 	    }
602 	} else {
603 	    /* ignore */
604 	}
605     }
606 
607     vstring_strcpy(reply, "Connection lost to authentication server");
608     return XSASL_AUTH_TEMP;
609 }
610 
611 /* is_valid_base64 - input sanitized */
612 
613 static int is_valid_base64(const char *data)
614 {
615 
616     /*
617      * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent).
618      */
619     for (; *data != '\0'; data++) {
620 	if (!((*data >= '0' && *data <= '9') ||
621 	      (*data >= 'a' && *data <= 'z') ||
622 	      (*data >= 'A' && *data <= 'Z') ||
623 	      *data == '+' || *data == '/' || *data == '='))
624 	    return 0;
625     }
626     return 1;
627 }
628 
629 /* xsasl_dovecot_server_first - per-session authentication */
630 
631 int     xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method,
632 			          const char *init_response, VSTRING *reply)
633 {
634     const char *myname = "xsasl_dovecot_server_first";
635     XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
636     int     i;
637     char  **cpp;
638 
639 #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
640 
641     if (msg_verbose)
642 	msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
643 		 IFELSE(init_response, ", init_response ", ""),
644 		 IFELSE(init_response, init_response, ""));
645 
646     if (server->mechanism_argv == 0)
647 	msg_panic("%s: no mechanism list", myname);
648 
649     for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) {
650 	if (*cpp == 0) {
651 	    vstring_strcpy(reply, "Invalid authentication mechanism");
652 	    return XSASL_AUTH_FAIL;
653 	}
654 	if (strcasecmp(sasl_method, *cpp) == 0)
655 	    break;
656     }
657     if (init_response)
658 	if (!is_valid_base64(init_response)) {
659 	    vstring_strcpy(reply, "Invalid base64 data in initial response");
660 	    return XSASL_AUTH_FAIL;
661 	}
662     for (i = 0; i < 2; i++) {
663 	if (!server->impl->sasl_stream) {
664 	    if (xsasl_dovecot_server_connect(server->impl) < 0)
665 		return XSASL_AUTH_TEMP;
666 	}
667 	/* send the request */
668 	server->last_request_id = ++server->impl->request_id_counter;
669 	/* XXX Encapsulate for logging. */
670 	vstream_fprintf(server->impl->sasl_stream,
671 			"AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s",
672 			server->last_request_id, sasl_method,
673 			server->service, server->server_addr,
674 			server->client_addr);
675 	if (server->tls_flag)
676 	    /* XXX Encapsulate for logging. */
677 	    vstream_fputs("\tsecured", server->impl->sasl_stream);
678 	if (init_response) {
679 
680 	    /*
681 	     * initial response is already base64 encoded, so we can send it
682 	     * directly.
683 	     */
684 	    /* XXX Encapsulate for logging. */
685 	    vstream_fprintf(server->impl->sasl_stream,
686 			    "\tresp=%s", init_response);
687 	}
688 	/* XXX Encapsulate for logging. */
689 	VSTREAM_PUTC('\n', server->impl->sasl_stream);
690 
691 	if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF)
692 	    break;
693 
694 	if (i == 1) {
695 	    vstring_strcpy(reply, "Can't connect to authentication server");
696 	    return XSASL_AUTH_TEMP;
697 	}
698 
699 	/*
700 	 * Reconnect and try again.
701 	 */
702 	xsasl_dovecot_server_disconnect(server->impl);
703     }
704 
705     return xsasl_dovecot_handle_reply(server, reply);
706 }
707 
708 /* xsasl_dovecot_server_next - continue authentication */
709 
710 static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request,
711 				             VSTRING *reply)
712 {
713     XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
714 
715     if (!is_valid_base64(request)) {
716 	vstring_strcpy(reply, "Invalid base64 data in continued response");
717 	return XSASL_AUTH_FAIL;
718     }
719     /* XXX Encapsulate for logging. */
720     vstream_fprintf(server->impl->sasl_stream,
721 		    "CONT\t%u\t%s\n", server->last_request_id, request);
722     if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) {
723 	vstring_strcpy(reply, "Connection lost to authentication server");
724 	return XSASL_AUTH_TEMP;
725     }
726     return xsasl_dovecot_handle_reply(server, reply);
727 }
728 
729 /* xsasl_dovecot_server_get_username - get authenticated username */
730 
731 static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp)
732 {
733     XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
734 
735     return (server->username);
736 }
737 
738 #endif
739