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 */
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
xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH ** mech_list,const char * mech_name,int sec_props)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
xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH * mech_list)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
xsasl_dovecot_server_mech_filter(ARGV * mechanism_argv,XSASL_DCSRV_MECH * mechanism_list,unsigned int conf_props)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
xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL * xp)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 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
xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL * xp)381 static 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
xsasl_dovecot_server_init(const char * server_type,const char * path_info)395 XSASL_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
xsasl_dovecot_server_done(XSASL_SERVER_IMPL * impl)412 static 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
xsasl_dovecot_server_create(XSASL_SERVER_IMPL * impl,XSASL_SERVER_CREATE_ARGS * args)423 static 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
xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER * xp)481 static 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
xsasl_dovecot_server_free(XSASL_SERVER * xp)501 static 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
xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER * server,char ** line)520 static 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
xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER * server,char * line,VSTRING * reply,int success)538 static 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
xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER * server,VSTRING * reply)569 static 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
is_valid_base64(const char * data)624 static 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
xsasl_dovecot_server_first(XSASL_SERVER * xp,const char * sasl_method,const char * init_response,VSTRING * reply)642 int 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
xsasl_dovecot_server_next(XSASL_SERVER * xp,const char * request,VSTRING * reply)721 static 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
xsasl_dovecot_server_get_username(XSASL_SERVER * xp)742 static 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