1/*	$NetBSD: xsasl_dovecot_server.c,v 1.4 2022/10/08 16:12:51 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  * Enforce 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  */
117static 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  */
131static 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  */
145typedef 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
151typedef 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  */
163typedef 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  */
181static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *);
182static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *,
183					        XSASL_SERVER_CREATE_ARGS *);
184static void xsasl_dovecot_server_free(XSASL_SERVER *);
185static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *,
186				              const char *, VSTRING *);
187static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *);
188static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *);
189static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *);
190
191/* xsasl_dovecot_server_mech_append - append server mechanism entry */
192
193static 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
211static 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
225static 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
257static 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 Dovecot auth socket '%s' failed: %m",
284		 xp->socket_path);
285	return (-1);
286    }
287    sasl_stream = vstream_fdopen(fd, O_RDWR);
288    vstream_control(sasl_stream,
289		    CA_VSTREAM_CTL_PATH(xp->socket_path),
290		    CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT),
291		    CA_VSTREAM_CTL_END);
292
293    /* XXX Encapsulate for logging. */
294    vstream_fprintf(sasl_stream,
295		    "VERSION\t%u\t%u\n"
296		    "CPID\t%u\n",
297		    AUTH_PROTOCOL_MAJOR_VERSION,
298		    AUTH_PROTOCOL_MINOR_VERSION,
299		    (unsigned int) getpid());
300    if (vstream_fflush(sasl_stream) == VSTREAM_EOF) {
301	msg_warn("SASL: Couldn't send handshake: %m");
302	return (-1);
303    }
304    success = 0;
305    have_mech_line = 0;
306    line_str = vstring_alloc(256);
307    /* XXX Encapsulate for logging. */
308    while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) {
309	line = vstring_str(line_str);
310
311	if (msg_verbose)
312	    msg_info("%s: auth reply: %s", myname, line);
313
314	cmd = line;
315	line = split_at(line, '\t');
316
317	if (strcmp(cmd, "VERSION") == 0) {
318	    if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) {
319		msg_warn("SASL: Protocol version error");
320		break;
321	    }
322	    if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) {
323		/* Major version is different from ours. */
324		msg_warn("SASL: Protocol version mismatch (%d vs. %d)",
325			 major_version, AUTH_PROTOCOL_MAJOR_VERSION);
326		break;
327	    }
328	} else if (strcmp(cmd, "MECH") == 0 && line != NULL) {
329	    mech_name = line;
330	    have_mech_line = 1;
331	    line = split_at(line, '\t');
332	    if (line != 0) {
333		sec_props =
334		    name_mask_delim_opt(myname,
335					xsasl_dovecot_serv_sec_props,
336					line, "\t",
337				     NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
338		if ((sec_props & SEC_PROPS_PRIVATE) != 0)
339		    continue;
340	    } else
341		sec_props = 0;
342	    xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name,
343					     sec_props);
344	} else if (strcmp(cmd, "SPID") == 0) {
345
346	    /*
347	     * Unfortunately the auth protocol handshake wasn't designed well
348	     * to differentiate between auth-client/userdb/master.
349	     * auth-userdb and auth-master send VERSION + SPID lines only and
350	     * nothing afterwards, while auth-client sends VERSION + MECH +
351	     * SPID + CUID + more. The simplest way that we can determine if
352	     * we've connected to the correct socket is to see if MECH line
353	     * exists or not (alternatively we'd have to have a small timeout
354	     * after SPID to see if CUID is sent or not).
355	     */
356	    if (!have_mech_line) {
357		msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)");
358		break;
359	    }
360	} else if (strcmp(cmd, "DONE") == 0) {
361	    /* Handshake finished. */
362	    success = 1;
363	    break;
364	} else {
365	    /* ignore any unknown commands */
366	}
367    }
368    vstring_free(line_str);
369
370    if (!success) {
371	/* handshake failed */
372	(void) vstream_fclose(sasl_stream);
373	return (-1);
374    }
375    xp->sasl_stream = sasl_stream;
376    return (0);
377}
378
379/* xsasl_dovecot_server_disconnect - dispose of server connection state */
380
381static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp)
382{
383    if (xp->sasl_stream) {
384	(void) vstream_fclose(xp->sasl_stream);
385	xp->sasl_stream = 0;
386    }
387    if (xp->mechanism_list) {
388	xsasl_dovecot_server_mech_free(xp->mechanism_list);
389	xp->mechanism_list = 0;
390    }
391}
392
393/* xsasl_dovecot_server_init - create implementation handle */
394
395XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type,
396					             const char *path_info)
397{
398    XSASL_DOVECOT_SERVER_IMPL *xp;
399
400    xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp));
401    xp->xsasl.create = xsasl_dovecot_server_create;
402    xp->xsasl.done = xsasl_dovecot_server_done;
403    xp->socket_path = mystrdup(path_info);
404    xp->sasl_stream = 0;
405    xp->mechanism_list = 0;
406    xp->request_id_counter = 0;
407    return (&xp->xsasl);
408}
409
410/* xsasl_dovecot_server_done - dispose of implementation */
411
412static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl)
413{
414    XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl;
415
416    xsasl_dovecot_server_disconnect(xp);
417    myfree(xp->socket_path);
418    myfree((void *) impl);
419}
420
421/* xsasl_dovecot_server_create - create server instance */
422
423static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl,
424				             XSASL_SERVER_CREATE_ARGS *args)
425{
426    const char *myname = "xsasl_dovecot_server_create";
427    XSASL_DOVECOT_SERVER *server;
428    struct sockaddr_storage ss;
429    struct sockaddr *sa = (struct sockaddr *) &ss;
430    SOCKADDR_SIZE salen;
431    MAI_HOSTADDR_STR server_addr;
432
433    if (msg_verbose)
434	msg_info("%s: SASL service=%s, realm=%s",
435		 myname, args->service, args->user_realm ?
436		 args->user_realm : "(null)");
437
438    /*
439     * Extend the XSASL_SERVER_IMPL object with our own data. We use
440     * long-lived conversion buffers rather than local variables to avoid
441     * memory leaks in case of read/write timeout or I/O error.
442     */
443    server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server));
444    server->xsasl.free = xsasl_dovecot_server_free;
445    server->xsasl.first = xsasl_dovecot_server_first;
446    server->xsasl.next = xsasl_dovecot_server_next;
447    server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list;
448    server->xsasl.get_username = xsasl_dovecot_server_get_username;
449    server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl;
450    server->sasl_line = vstring_alloc(256);
451    server->username = 0;
452    server->service = mystrdup(args->service);
453    server->last_request_id = 0;
454    server->mechanism_list = 0;
455    server->mechanism_argv = 0;
456    server->tls_flag = args->tls_flag;
457    server->sec_props =
458	name_mask_opt(myname, xsasl_dovecot_conf_sec_props,
459		      args->security_options,
460		      NAME_MASK_ANY_CASE | NAME_MASK_FATAL);
461    server->client_addr = mystrdup(args->client_addr);
462
463    /*
464     * XXX Temporary code until smtpd_peer.c is updated.
465     */
466    if (args->server_addr && *args->server_addr) {
467	server->server_addr = mystrdup(args->server_addr);
468    } else {
469	salen = sizeof(ss);
470	if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0
471	    || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0)
472	    server_addr.buf[0] = 0;
473	server->server_addr = mystrdup(server_addr.buf);
474    }
475
476    return (&server->xsasl);
477}
478
479/* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */
480
481static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp)
482{
483    XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
484
485    if (!server->impl->sasl_stream) {
486	if (xsasl_dovecot_server_connect(server->impl) < 0)
487	    return (0);
488    }
489    if (server->mechanism_list == 0) {
490	server->mechanism_argv = argv_alloc(2);
491	server->mechanism_list =
492	    xsasl_dovecot_server_mech_filter(server->mechanism_argv,
493					     server->impl->mechanism_list,
494					     server->sec_props);
495    }
496    return (server->mechanism_list[0] ? server->mechanism_list : 0);
497}
498
499/* xsasl_dovecot_server_free - destroy server instance */
500
501static void xsasl_dovecot_server_free(XSASL_SERVER *xp)
502{
503    XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
504
505    vstring_free(server->sasl_line);
506    if (server->username)
507	myfree(server->username);
508    if (server->mechanism_list) {
509	myfree(server->mechanism_list);
510	argv_free(server->mechanism_argv);
511    }
512    myfree(server->service);
513    myfree(server->server_addr);
514    myfree(server->client_addr);
515    myfree((void *) server);
516}
517
518/* xsasl_dovecot_server_auth_response - encode server first/next response */
519
520static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line)
521{
522    char   *id;
523
524    if (*line == NULL) {
525	msg_warn("SASL: Protocol error");
526	return -1;
527    }
528    id = *line;
529    *line = split_at(*line, '\t');
530
531    if (strtoul(id, NULL, 0) != server->last_request_id) {
532	/* reply to another request, shouldn't really happen.. */
533	return -1;
534    }
535    return 0;
536}
537
538static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server,
539					         char *line, VSTRING *reply,
540					           int success)
541{
542    char   *next;
543
544    if (server->username) {
545	myfree(server->username);
546	server->username = 0;
547    }
548
549    /*
550     * Note: TAB is part of the Dovecot protocol and must not appear in
551     * legitimate Dovecot usernames, otherwise the protocol would break.
552     */
553    for (; line != NULL; line = next) {
554	next = split_at(line, '\t');
555	if (strncmp(line, "user=", 5) == 0) {
556	    server->username = mystrdup(line + 5);
557	    printable(server->username, '?');
558	} else if (strncmp(line, "reason=", 7) == 0) {
559	    if (!success) {
560		printable(line + 7, '?');
561		vstring_strcpy(reply, line + 7);
562	    }
563	}
564    }
565}
566
567/* xsasl_dovecot_handle_reply - receive and process auth reply */
568
569static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server,
570				              VSTRING *reply)
571{
572    const char *myname = "xsasl_dovecot_handle_reply";
573    char   *line, *cmd;
574
575    /* XXX Encapsulate for logging. */
576    while (vstring_get_nonl(server->sasl_line,
577			    server->impl->sasl_stream) != VSTREAM_EOF) {
578	line = vstring_str(server->sasl_line);
579
580	if (msg_verbose)
581	    msg_info("%s: auth reply: %s", myname, line);
582
583	cmd = line;
584	line = split_at(line, '\t');
585
586	if (strcmp(cmd, "OK") == 0) {
587	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
588		/* authentication successful */
589		xsasl_dovecot_parse_reply_args(server, line, reply, 1);
590		if (server->username == 0) {
591		    msg_warn("missing Dovecot server %s username field", cmd);
592		    vstring_strcpy(reply, "Authentication backend error");
593		    return XSASL_AUTH_FAIL;
594		}
595		return XSASL_AUTH_DONE;
596	    }
597	} else if (strcmp(cmd, "CONT") == 0) {
598	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
599		if (line == 0) {
600		    msg_warn("missing Dovecot server %s reply field", cmd);
601		    vstring_strcpy(reply, "Authentication backend error");
602		    return XSASL_AUTH_FAIL;
603		}
604		vstring_strcpy(reply, line);
605		return XSASL_AUTH_MORE;
606	    }
607	} else if (strcmp(cmd, "FAIL") == 0) {
608	    if (xsasl_dovecot_parse_reply(server, &line) == 0) {
609		/* authentication failure */
610		xsasl_dovecot_parse_reply_args(server, line, reply, 0);
611		return XSASL_AUTH_FAIL;
612	    }
613	} else {
614	    /* ignore */
615	}
616    }
617
618    vstring_strcpy(reply, "Connection lost to authentication server");
619    return XSASL_AUTH_TEMP;
620}
621
622/* is_valid_base64 - input sanitized */
623
624static int is_valid_base64(const char *data)
625{
626
627    /*
628     * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent).
629     */
630    for (; *data != '\0'; data++) {
631	if (!((*data >= '0' && *data <= '9') ||
632	      (*data >= 'a' && *data <= 'z') ||
633	      (*data >= 'A' && *data <= 'Z') ||
634	      *data == '+' || *data == '/' || *data == '='))
635	    return 0;
636    }
637    return 1;
638}
639
640/* xsasl_dovecot_server_first - per-session authentication */
641
642int     xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method,
643			          const char *init_response, VSTRING *reply)
644{
645    const char *myname = "xsasl_dovecot_server_first";
646    XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
647    int     i;
648    char  **cpp;
649
650#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
651
652    if (msg_verbose)
653	msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
654		 IFELSE(init_response, ", init_response ", ""),
655		 IFELSE(init_response, init_response, ""));
656
657    if (server->mechanism_argv == 0)
658	msg_panic("%s: no mechanism list", myname);
659
660    for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) {
661	if (*cpp == 0) {
662	    vstring_strcpy(reply, "Invalid authentication mechanism");
663	    return XSASL_AUTH_FAIL;
664	}
665	if (strcasecmp(sasl_method, *cpp) == 0)
666	    break;
667    }
668    if (init_response)
669	if (!is_valid_base64(init_response)) {
670	    vstring_strcpy(reply, "Invalid base64 data in initial response");
671	    return XSASL_AUTH_FAIL;
672	}
673    for (i = 0; i < 2; i++) {
674	if (!server->impl->sasl_stream) {
675	    if (xsasl_dovecot_server_connect(server->impl) < 0)
676		return XSASL_AUTH_TEMP;
677	}
678	/* send the request */
679	server->last_request_id = ++server->impl->request_id_counter;
680	/* XXX Encapsulate for logging. */
681	vstream_fprintf(server->impl->sasl_stream,
682			"AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s",
683			server->last_request_id, sasl_method,
684			server->service, server->server_addr,
685			server->client_addr);
686	if (server->tls_flag)
687	    /* XXX Encapsulate for logging. */
688	    vstream_fputs("\tsecured", server->impl->sasl_stream);
689	if (init_response) {
690
691	    /*
692	     * initial response is already base64 encoded, so we can send it
693	     * directly.
694	     */
695	    /* XXX Encapsulate for logging. */
696	    vstream_fprintf(server->impl->sasl_stream,
697			    "\tresp=%s", init_response);
698	}
699	/* XXX Encapsulate for logging. */
700	VSTREAM_PUTC('\n', server->impl->sasl_stream);
701
702	if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF)
703	    break;
704
705	if (i == 1) {
706	    vstring_strcpy(reply, "Can't connect to authentication server");
707	    return XSASL_AUTH_TEMP;
708	}
709
710	/*
711	 * Reconnect and try again.
712	 */
713	xsasl_dovecot_server_disconnect(server->impl);
714    }
715
716    return xsasl_dovecot_handle_reply(server, reply);
717}
718
719/* xsasl_dovecot_server_next - continue authentication */
720
721static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request,
722				             VSTRING *reply)
723{
724    XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
725
726    if (!is_valid_base64(request)) {
727	vstring_strcpy(reply, "Invalid base64 data in continued response");
728	return XSASL_AUTH_FAIL;
729    }
730    /* XXX Encapsulate for logging. */
731    vstream_fprintf(server->impl->sasl_stream,
732		    "CONT\t%u\t%s\n", server->last_request_id, request);
733    if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) {
734	vstring_strcpy(reply, "Connection lost to authentication server");
735	return XSASL_AUTH_TEMP;
736    }
737    return xsasl_dovecot_handle_reply(server, reply);
738}
739
740/* xsasl_dovecot_server_get_username - get authenticated username */
741
742static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp)
743{
744    XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
745
746    return (server->username);
747}
748
749#endif
750