constification
[exim.git] / src / src / auths / dovecot.c
CommitLineData
14aa5a05
PH
1/*
2 * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
9242a7e8 3 * Copyright (c) 2006-2017 The Exim Maintainers
14aa5a05
PH
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 */
10
57c2c631
PH
11/* A number of modifications have been made to the original code. Originally I
12commented them specially, but now they are getting quite extensive, so I have
13ceased doing that. The biggest change is to use unbuffered I/O on the socket
14because using C buffered I/O gives problems on some operating systems. PH */
15
3f1df0e3
PP
16/* Protocol specifications:
17 * Dovecot 1, protocol version 1.1
18 * http://wiki.dovecot.org/Authentication%20Protocol
19 *
20 * Dovecot 2, protocol version 1.1
21 * http://wiki2.dovecot.org/Design/AuthProtocol
22 */
23
14aa5a05
PH
24#include "../exim.h"
25#include "dovecot.h"
26
27#define VERSION_MAJOR 1
28#define VERSION_MINOR 0
29
3f1df0e3
PP
30/* http://wiki.dovecot.org/Authentication%20Protocol
31"The maximum line length isn't defined,
32 but it's currently expected to fit into 8192 bytes"
33*/
34#define DOVECOT_AUTH_MAXLINELEN 8192
35
36/* This was hard-coded as 8.
37AUTH req C->S sends {"AUTH", id, mechanism, service } + params, 5 defined for
38Dovecot 1; Dovecot 2 (same protocol version) defines 9.
39
40Master->Server sends {"USER", id, userid} + params, 6 defined.
41Server->Client only gives {"OK", id} + params, unspecified, only 1 guaranteed.
42
43We only define here to accept S->C; max seen is 3+<unspecified>, plus the two
44for the command and id, where unspecified might include _at least_ user=...
45
46So: allow for more fields than we ever expect to see, while aware that count
47can go up without changing protocol version.
48The cost is the length of an array of pointers on the stack.
49*/
50#define DOVECOT_AUTH_MAXFIELDCOUNT 16
51
14aa5a05
PH
52/* Options specific to the authentication mechanism. */
53optionlist auth_dovecot_options[] = {
54 {
57c2c631
PH
55 "server_socket",
56 opt_stringptr,
57 (void *)(offsetof(auth_dovecot_options_block, server_socket))
14aa5a05
PH
58 },
59};
60
61/* Size of the options list. An extern variable has to be used so that its
62address can appear in the tables drtables.c. */
57c2c631 63
14aa5a05
PH
64int auth_dovecot_options_count =
65 sizeof(auth_dovecot_options) / sizeof(optionlist);
66
67/* Default private options block for the authentication method. */
57c2c631 68
14aa5a05
PH
69auth_dovecot_options_block auth_dovecot_option_defaults = {
70 NULL, /* server_socket */
71};
72
57c2c631 73
d185889f
JH
74
75
76#ifdef MACRO_PREDEF
77
78/* Dummy values */
79void auth_dovecot_init(auth_instance *ablock) {}
80int auth_dovecot_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
81int auth_dovecot_client(auth_instance *ablock, void * sx,
82 int timeout, uschar *buffer, int buffsize) {return 0;}
d185889f
JH
83
84#else /*!MACRO_PREDEF*/
85
86
57c2c631
PH
87/* Static variables for reading from the socket */
88
89static uschar sbuffer[256];
3f1df0e3 90static int socket_buffer_left;
57c2c631
PH
91
92
93
14aa5a05
PH
94/*************************************************
95 * Initialization entry point *
96 *************************************************/
97
98/* Called for each instance, after its options have been read, to
99enable consistency checks to be done, or anything else that needs
100to be set up. */
57c2c631 101
14aa5a05
PH
102void auth_dovecot_init(auth_instance *ablock)
103{
104 auth_dovecot_options_block *ob =
105 (auth_dovecot_options_block *)(ablock->options_block);
106
107 if (ablock->public_name == NULL)
108 ablock->public_name = ablock->name;
109 if (ob->server_socket != NULL)
110 ablock->server = TRUE;
111 ablock->client = FALSE;
112}
113
3f1df0e3
PP
114/*************************************************
115 * "strcut" to split apart server lines *
116 *************************************************/
117
118/* Dovecot auth protocol uses TAB \t as delimiter; a line consists
119of a command-name, TAB, and then any parameters, each separated by a TAB.
120A parameter can be param=value or a bool, just param.
121
122This function modifies the original str in-place, inserting NUL characters.
123It initialises ptrs entries, setting all to NULL and only setting
124non-NULL N entries, where N is the return value, the number of fields seen
125(one more than the number of tabs).
126
127Note that the return value will always be at least 1, is the count of
128actual fields (so last valid offset into ptrs is one less).
129*/
130
131static int
132strcut(uschar *str, uschar **ptrs, int nptrs)
14aa5a05 133{
d7978c0f
JH
134uschar *last_sub_start = str;
135int n;
136
137for (n = 0; n < nptrs; n++)
138 ptrs[n] = NULL;
139n = 1;
140
141while (*str)
142 {
143 if (*str == '\t')
144 {
145 if (n <= nptrs)
146 {
147 *ptrs++ = last_sub_start;
148 last_sub_start = str + 1;
149 *str = '\0';
150 }
151 n++;
152 }
153 str++;
154 }
155
156/* It's acceptable for the string to end with a tab character. We see
157this in AUTH PLAIN without an initial response from the client, which
158causing us to send "334 " and get the data from the client. */
159if (n <= nptrs)
160 *ptrs = last_sub_start;
161else
162 {
163 HDEBUG(D_auth) debug_printf("dovecot: warning: too many results from tab-splitting; saw %d fields, room for %d\n", n, nptrs);
164 n = nptrs;
165 }
166
167return n <= nptrs ? n : nptrs;
3f1df0e3 168}
14aa5a05 169
3f1df0e3
PP
170static void debug_strcut(uschar **ptrs, int nlen, int alen) ARG_UNUSED;
171static void
172debug_strcut(uschar **ptrs, int nlen, int alen)
173{
d7978c0f
JH
174int i;
175debug_printf("%d read but unreturned bytes; strcut() gave %d results: ",
176 socket_buffer_left, nlen);
177for (i = 0; i < nlen; i++)
178 debug_printf(" {%s}", ptrs[i]);
179if (nlen < alen)
180 debug_printf(" last is %s\n", ptrs[i] ? ptrs[i] : US"<null>");
181else
182 debug_printf(" (max for capacity)\n");
14aa5a05
PH
183}
184
185#define CHECK_COMMAND(str, arg_min, arg_max) do { \
57c2c631 186 if (strcmpic(US(str), args[0]) != 0) \
14aa5a05
PH
187 goto out; \
188 if (nargs - 1 < (arg_min)) \
189 goto out; \
6c588e74 190 if ( (arg_max != -1) && (nargs - 1 > (arg_max)) ) \
14aa5a05
PH
191 goto out; \
192} while (0)
193
194#define OUT(msg) do { \
195 auth_defer_msg = (US msg); \
196 goto out; \
197} while(0)
198
7befa435
PH
199
200
14aa5a05 201/*************************************************
57c2c631
PH
202* "fgets" to read directly from socket *
203*************************************************/
204
205/* Added by PH after a suggestion by Steve Usher because the previous use of
206C-style buffered I/O gave trouble. */
207
208static uschar *
209dc_gets(uschar *s, int n, int fd)
210{
211int p = 0;
212int count = 0;
213
214for (;;)
215 {
3f1df0e3 216 if (socket_buffer_left == 0)
57c2c631 217 {
3f1df0e3
PP
218 socket_buffer_left = read(fd, sbuffer, sizeof(sbuffer));
219 if (socket_buffer_left == 0) { if (count == 0) return NULL; else break; }
51473862 220 p = 0;
57c2c631
PH
221 }
222
3f1df0e3 223 while (p < socket_buffer_left)
57c2c631
PH
224 {
225 if (count >= n - 1) break;
226 s[count++] = sbuffer[p];
227 if (sbuffer[p++] == '\n') break;
228 }
229
3f1df0e3
PP
230 memmove(sbuffer, sbuffer + p, socket_buffer_left - p);
231 socket_buffer_left -= p;
57c2c631
PH
232
233 if (s[count-1] == '\n' || count >= n - 1) break;
234 }
235
3f1df0e3 236s[count] = '\0';
57c2c631
PH
237return s;
238}
239
240
241
242
243/*************************************************
244* Server entry point *
245*************************************************/
14aa5a05 246
96f5fe4c
JH
247int
248auth_dovecot_server(auth_instance * ablock, uschar * data)
14aa5a05 249{
96f5fe4c
JH
250auth_dovecot_options_block *ob =
251 (auth_dovecot_options_block *) ablock->options_block;
252struct sockaddr_un sa;
253uschar buffer[DOVECOT_AUTH_MAXLINELEN];
254uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT];
255uschar *auth_command;
256uschar *auth_extra_data = US"";
257uschar *p;
258int nargs, tmp;
77560253 259int crequid = 1, cont = 1, fd = -1, ret = DEFER;
96f5fe4c
JH
260BOOL found = FALSE, have_mech_line = FALSE;
261
262HDEBUG(D_auth) debug_printf("dovecot authentication\n");
263
264if (!data)
265 {
266 ret = FAIL;
267 goto out;
268 }
14aa5a05 269
96f5fe4c
JH
270memset(&sa, 0, sizeof(sa));
271sa.sun_family = AF_UNIX;
14aa5a05 272
96f5fe4c
JH
273/* This was the original code here: it is nonsense because strncpy()
274does not return an integer. I have converted this to use the function
275that formats and checks length. PH */
14aa5a05 276
96f5fe4c
JH
277/*
278if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
279}
280*/
14aa5a05 281
96f5fe4c
JH
282if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
283 ob->server_socket))
284 {
285 auth_defer_msg = US"authentication socket path too long";
286 return DEFER;
287 }
7befa435 288
96f5fe4c 289auth_defer_msg = US"authentication socket connection error";
7befa435 290
96f5fe4c
JH
291if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
292 return DEFER;
7befa435 293
96f5fe4c
JH
294if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
295 goto out;
7befa435 296
96f5fe4c 297auth_defer_msg = US"authentication socket protocol error";
14aa5a05 298
96f5fe4c
JH
299socket_buffer_left = 0; /* Global, used to read more than a line but return by line */
300while (cont)
301 {
302 if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
303 OUT("authentication socket read error or premature eof");
304 p = buffer + Ustrlen(buffer) - 1;
305 if (*p != '\n')
306 OUT("authentication socket protocol line too long");
307
308 *p = '\0';
309 HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
310
311 nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
312
313 /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */
314
315 /* Code below rewritten by Kirill Miazine (km@krot.org). Only check commands that
316 Exim will need. Original code also failed if Dovecot server sent unknown
317 command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */
318 /* pdp: note that CUID is a per-connection identifier sent by the server,
319 which increments at server discretion.
320 By contrast, the "id" field of the protocol is a connection-specific request
321 identifier, which needs to be unique per request from the client and is not
322 connected to the CUID value, so we ignore CUID from server. It's purely for
323 diagnostics. */
324
325 if (Ustrcmp(args[0], US"VERSION") == 0)
326 {
327 CHECK_COMMAND("VERSION", 2, 2);
328 if (Uatoi(args[1]) != VERSION_MAJOR)
329 OUT("authentication socket protocol version mismatch");
330 }
331 else if (Ustrcmp(args[0], US"MECH") == 0)
332 {
333 CHECK_COMMAND("MECH", 1, INT_MAX);
334 have_mech_line = TRUE;
335 if (strcmpic(US args[1], ablock->public_name) == 0)
336 found = TRUE;
337 }
338 else if (Ustrcmp(args[0], US"SPID") == 0)
339 {
340 /* Unfortunately the auth protocol handshake wasn't designed well
341 to differentiate between auth-client/userdb/master. auth-userdb
342 and auth-master send VERSION + SPID lines only and nothing
343 afterwards, while auth-client sends VERSION + MECH + SPID +
344 CUID + more. The simplest way that we can determine if we've
345 connected to the correct socket is to see if MECH line exists or
346 not (alternatively we'd have to have a small timeout after SPID
347 to see if CUID is sent or not). */
348
349 if (!have_mech_line)
350 OUT("authentication socket type mismatch"
351 " (connected to auth-master instead of auth-client)");
352 }
353 else if (Ustrcmp(args[0], US"DONE") == 0)
354 {
355 CHECK_COMMAND("DONE", 0, 0);
356 cont = 0;
357 }
358 }
14aa5a05 359
96f5fe4c
JH
360if (!found)
361 {
362 auth_defer_msg = string_sprintf(
363 "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name);
364 goto out;
365 }
14aa5a05 366
96f5fe4c
JH
367/* Added by PH: data must not contain tab (as it is
368b64 it shouldn't, but check for safety). */
57c2c631 369
96f5fe4c
JH
370if (Ustrchr(data, '\t') != NULL)
371 {
372 ret = FAIL;
373 goto out;
374 }
14aa5a05 375
96f5fe4c
JH
376/* Added by PH: extra fields when TLS is in use or if the TCP/IP
377connection is local. */
6c588e74 378
96f5fe4c
JH
379if (tls_in.cipher != NULL)
380 auth_extra_data = string_sprintf("secured\t%s%s",
381 tls_in.certificate_verified? "valid-client-cert" : "",
382 tls_in.certificate_verified? "\t" : "");
14aa5a05 383
96f5fe4c
JH
384else if ( interface_address != NULL
385 && Ustrcmp(sender_host_address, interface_address) == 0)
386 auth_extra_data = US"secured\t";
14aa5a05 387
14aa5a05 388
96f5fe4c
JH
389/****************************************************************************
390The code below was the original code here. It didn't work. A reading of the
391file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that
392this was not right. Maybe something changed. I changed it to move the
393service indication into the AUTH command, and it seems to be better. PH
394
395fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n"
396 "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
397 VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
398 ablock->public_name, sender_host_address, interface_address,
5903c6ff 399 data ? CS data : "");
96f5fe4c
JH
400
401Subsequently, the command was modified to add "secured" and "valid-client-
402cert" when relevant.
403****************************************************************************/
14aa5a05 404
96f5fe4c
JH
405auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
406 "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
407 VERSION_MAJOR, VERSION_MINOR, getpid(), crequid,
408 ablock->public_name, auth_extra_data, sender_host_address,
409 interface_address, data);
14aa5a05 410
96f5fe4c
JH
411if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
412 HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
413 strerror(errno));
7befa435 414
96f5fe4c 415HDEBUG(D_auth) debug_printf("sent: %s", auth_command);
7befa435 416
96f5fe4c
JH
417while (1)
418 {
419 uschar *temp;
420 uschar *auth_id_pre = NULL;
421 int i;
14aa5a05 422
96f5fe4c
JH
423 if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
424 {
425 auth_defer_msg = US"authentication socket read error or premature eof";
426 goto out;
427 }
6c588e74 428
96f5fe4c
JH
429 buffer[Ustrlen(buffer) - 1] = 0;
430 HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
431 nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
6c588e74 432
96f5fe4c
JH
433 if (Uatoi(args[1]) != crequid)
434 OUT("authentication socket connection id mismatch");
14aa5a05 435
96f5fe4c
JH
436 switch (toupper(*args[0]))
437 {
438 case 'C':
439 CHECK_COMMAND("CONT", 1, 2);
440
441 if ((tmp = auth_get_no64_data(&data, US args[2])) != OK)
442 {
443 ret = tmp;
444 goto out;
445 }
446
447 /* Added by PH: data must not contain tab (as it is
448 b64 it shouldn't, but check for safety). */
449
450 if (Ustrchr(data, '\t') != NULL)
451 {
452 ret = FAIL;
453 goto out;
454 }
455
456 temp = string_sprintf("CONT\t%d\t%s\n", crequid, data);
457 if (write(fd, temp, Ustrlen(temp)) < 0)
458 OUT("authentication socket write error");
459 break;
460
461 case 'F':
462 CHECK_COMMAND("FAIL", 1, -1);
463
464 for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
465 {
466 if ( Ustrncmp(args[i], US"user=", 5) == 0 )
467 {
468 auth_id_pre = args[i]+5;
469 expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */
470 expand_nlength[1] = Ustrlen(auth_id_pre);
471 expand_nmax = 1;
472 }
473 }
474
475 ret = FAIL;
476 goto out;
477
478 case 'O':
479 CHECK_COMMAND("OK", 2, -1);
480
481 /* Search for the "user=$USER" string in the args array
482 and return the proper value. */
483
484 for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
485 {
486 if ( Ustrncmp(args[i], US"user=", 5) == 0 )
487 {
488 auth_id_pre = args[i]+5;
489 expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */
490 expand_nlength[1] = Ustrlen(auth_id_pre);
491 expand_nmax = 1;
492 }
493 }
494
495 if (auth_id_pre == NULL)
496 OUT("authentication socket protocol error, username missing");
497
498 ret = OK;
499 /* fallthrough */
500
501 default:
502 goto out;
503 }
504 }
14aa5a05 505
57c2c631 506out:
96f5fe4c
JH
507/* close the socket used by dovecot */
508if (fd >= 0)
509 close(fd);
16ff981e 510
96f5fe4c
JH
511/* Expand server_condition as an authorization check */
512return ret == OK ? auth_check_serv_cond(ablock) : ret;
14aa5a05 513}
d185889f
JH
514
515
516#endif /*!MACRO_PREDEF*/