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