Update Dovecot authenticator to (a) lock out tabs (b) add extra
[exim.git] / src / src / auths / plaintext.c
CommitLineData
4730f942 1/* $Cambridge: exim/src/src/auths/plaintext.c,v 1.5 2006/02/23 12:41:22 ph10 Exp $ */
0756eb3c
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
d7d7b7b9 7/* Copyright (c) University of Cambridge 1995 - 2006 */
0756eb3c
PH
8/* See the file NOTICE for conditions of use and distribution. */
9
10#include "../exim.h"
11#include "plaintext.h"
12
13
14/* Options specific to the plaintext authentication mechanism. */
15
16optionlist auth_plaintext_options[] = {
4730f942
PH
17 { "client_ignore_invalid_base64", opt_bool,
18 (void *)(offsetof(auth_plaintext_options_block, client_ignore_invalid_base64)) },
0756eb3c
PH
19 { "client_send", opt_stringptr,
20 (void *)(offsetof(auth_plaintext_options_block, client_send)) },
21 { "server_condition", opt_stringptr,
22 (void *)(offsetof(auth_plaintext_options_block, server_condition)) },
23 { "server_prompts", opt_stringptr,
24 (void *)(offsetof(auth_plaintext_options_block, server_prompts)) }
25};
26
27/* Size of the options list. An extern variable has to be used so that its
28address can appear in the tables drtables.c. */
29
30int auth_plaintext_options_count =
31 sizeof(auth_plaintext_options)/sizeof(optionlist);
32
33/* Default private options block for the plaintext authentication method. */
34
35auth_plaintext_options_block auth_plaintext_option_defaults = {
36 NULL, /* server_condition */
37 NULL, /* server_prompts */
4730f942
PH
38 NULL, /* client_send */
39 FALSE /* client_ignore_invalid_base64 */
0756eb3c
PH
40};
41
42
43/*************************************************
44* Initialization entry point *
45*************************************************/
46
47/* Called for each instance, after its options have been read, to
48enable consistency checks to be done, or anything else that needs
49to be set up. */
50
51void
52auth_plaintext_init(auth_instance *ablock)
53{
54auth_plaintext_options_block *ob =
55 (auth_plaintext_options_block *)(ablock->options_block);
56if (ablock->public_name == NULL) ablock->public_name = ablock->name;
57if (ob->server_condition != NULL) ablock->server = TRUE;
58if (ob->client_send != NULL) ablock->client = TRUE;
59}
60
61
62
63/*************************************************
64* Server entry point *
65*************************************************/
66
67/* For interface, see auths/README */
68
69int
70auth_plaintext_server(auth_instance *ablock, uschar *data)
71{
72auth_plaintext_options_block *ob =
73 (auth_plaintext_options_block *)(ablock->options_block);
74uschar *prompts = ob->server_prompts;
75uschar *clear, *cond, *end, *s;
76int number = 1;
77int len, rc;
78int sep = 0;
79
80/* Expand a non-empty list of prompt strings */
81
82if (prompts != NULL)
83 {
84 prompts = expand_string(prompts);
85 if (prompts == NULL)
86 {
87 auth_defer_msg = expand_string_message;
88 return DEFER;
89 }
90 }
91
92/* If data was supplied on the AUTH command, decode it, and split it up into
f78eb7c6
PH
93multiple items at binary zeros. The strings are put into $auth1, $auth2, etc,
94up to a maximum. To retain backwards compatibility, they are also put int $1,
95$2, etc. If the data consists of the string "=" it indicates a single, empty
96string. */
0756eb3c
PH
97
98if (*data != 0)
99 {
100 if (Ustrcmp(data, "=") == 0)
101 {
f78eb7c6 102 auth_vars[0] = expand_nstring[++expand_nmax] = US"";
0756eb3c
PH
103 expand_nlength[expand_nmax] = 0;
104 }
105 else
106 {
107 if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
108 end = clear + len;
109 while (clear < end && expand_nmax < EXPAND_MAXN)
110 {
f78eb7c6 111 if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
0756eb3c
PH
112 expand_nstring[++expand_nmax] = clear;
113 while (*clear != 0) clear++;
114 expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
115 }
116 }
117 }
118
119/* Now go through the list of prompt strings. Skip over any whose data has
120already been provided as part of the AUTH command. For the rest, send them
121out as prompts, and get a data item back. If the data item is "*", abandon the
122authentication attempt. Otherwise, split it into items as above. */
123
124while ((s = string_nextinlist(&prompts, &sep, big_buffer, big_buffer_size))
125 != NULL && expand_nmax < EXPAND_MAXN)
126 {
127 if (number++ <= expand_nmax) continue;
128 if ((rc = auth_get_data(&data, s, Ustrlen(s))) != OK) return rc;
129 if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
130 end = clear + len;
131
132 /* This loop must run at least once, in case the length is zero */
133 do
134 {
f78eb7c6 135 if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
0756eb3c
PH
136 expand_nstring[++expand_nmax] = clear;
137 while (*clear != 0) clear++;
138 expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
139 }
140 while (clear < end && expand_nmax < EXPAND_MAXN);
141 }
142
f78eb7c6
PH
143/* We now have a number of items of data in $auth1, $auth2, etc (and also, for
144compatibility, in $1, $2, etc). Match against the decoded data by expanding the
145condition. */
0756eb3c
PH
146
147cond = expand_string(ob->server_condition);
148
149HDEBUG(D_auth)
150 {
151 int i;
152 debug_printf("%s authenticator:\n", ablock->name);
f78eb7c6
PH
153 for (i = 0; i < AUTH_VARS; i++)
154 {
155 if (auth_vars[i] != NULL)
156 debug_printf(" $auth%d = %s\n", i + 1, auth_vars[i]);
157 }
0756eb3c
PH
158 for (i = 1; i <= expand_nmax; i++)
159 debug_printf(" $%d = %.*s\n", i, expand_nlength[i], expand_nstring[i]);
160 debug_print_string(ablock->server_debug_string); /* customized debug */
161 if (cond == NULL)
162 debug_printf("expansion failed: %s\n", expand_string_message);
163 else
164 debug_printf("expanded string: %s\n", cond);
165 }
166
167/* A forced expansion failure causes authentication to fail. Other expansion
168failures yield DEFER, which will cause a temporary error code to be returned to
169the AUTH command. The problem is at the server end, so the client should try
170again later. */
171
172if (cond == NULL)
173 {
174 if (expand_string_forcedfail) return FAIL;
175 auth_defer_msg = expand_string_message;
176 return DEFER;
177 }
178
179/* Return FAIL for empty string, "0", "no", and "false"; return OK for
180"1", "yes", and "true"; return DEFER for anything else, with the string
181available as an error text for the user. */
182
183if (*cond == 0 ||
184 Ustrcmp(cond, "0") == 0 ||
185 strcmpic(cond, US"no") == 0 ||
186 strcmpic(cond, US"false") == 0)
187 return FAIL;
188
189if (Ustrcmp(cond, "1") == 0 ||
190 strcmpic(cond, US"yes") == 0 ||
191 strcmpic(cond, US"true") == 0)
192 return OK;
193
194auth_defer_msg = cond;
195auth_defer_user_msg = string_sprintf(": %s", cond);
196return DEFER;
197}
198
199
200
201/*************************************************
202* Client entry point *
203*************************************************/
204
205/* For interface, see auths/README */
206
207int
208auth_plaintext_client(
209 auth_instance *ablock, /* authenticator block */
210 smtp_inblock *inblock, /* connection inblock */
211 smtp_outblock *outblock, /* connection outblock */
212 int timeout, /* command timeout */
213 uschar *buffer, /* buffer for reading response */
214 int buffsize) /* size of buffer */
215{
216auth_plaintext_options_block *ob =
217 (auth_plaintext_options_block *)(ablock->options_block);
218uschar *text = ob->client_send;
219uschar *s;
220BOOL first = TRUE;
221int sep = 0;
4730f942 222int auth_var_idx = 0;
0756eb3c
PH
223
224/* The text is broken up into a number of different data items, which are
225sent one by one. The first one is sent with the AUTH command; the remainder are
226sent in response to subsequent prompts. Each is expanded before being sent. */
227
228while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL)
229 {
4730f942 230 int i, len, clear_len;
0756eb3c 231 uschar *ss = expand_string(s);
4730f942 232 uschar *clear;
0756eb3c
PH
233
234 /* Forced expansion failure is not an error; authentication is abandoned. On
235 all but the first string, we have to abandon the authentication attempt by
236 sending a line containing "*". Save the failed expansion string, because it
237 is in big_buffer, and that gets used by the sending function. */
238
239 if (ss == NULL)
240 {
241 uschar *ssave = string_copy(s);
242 if (!first)
243 {
244 if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
245 (void) smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
246 }
4730f942
PH
247 if (expand_string_forcedfail)
248 {
249 *buffer = 0; /* No message */
250 return CANCELLED;
251 }
0756eb3c
PH
252 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
253 "authenticator: %s", ssave, ablock->name, expand_string_message);
254 return ERROR;
255 }
256
257 len = Ustrlen(ss);
258
259 /* The character ^ is used as an escape for a binary zero character, which is
260 needed for the PLAIN mechanism. It must be doubled if really needed. */
261
262 for (i = 0; i < len; i++)
263 {
264 if (ss[i] == '^')
265 {
266 if (ss[i+1] != '^') ss[i] = 0; else
267 {
268 i++;
269 len--;
270 memmove(ss + i, ss + i + 1, len - i);
271 }
272 }
273 }
274
275 /* The first string is attached to the AUTH command; others are sent
276 unembelished. */
277
278 if (first)
279 {
280 first = FALSE;
281 if (smtp_write_command(outblock, FALSE, "AUTH %s%s%s\r\n",
282 ablock->public_name, (len == 0)? "" : " ",
283 auth_b64encode(ss, len)) < 0)
284 return FAIL_SEND;
285 }
286 else
287 {
288 if (smtp_write_command(outblock, FALSE, "%s\r\n",
289 auth_b64encode(ss, len)) < 0)
290 return FAIL_SEND;
291 }
292
293 /* If we receive a success response from the server, authentication
294 has succeeded. There may be more data to send, but is there any point
295 in provoking an error here? */
296
297 if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout)) return OK;
298
299 /* Not a success response. If errno != 0 there is some kind of transmission
300 error. Otherwise, check the response code in the buffer. If it starts with
301 '3', more data is expected. */
302
303 if (errno != 0 || buffer[0] != '3') return FAIL;
304
305 /* If there is no more data to send, we have to cancel the authentication
306 exchange and return ERROR. */
307
308 if (text == NULL)
309 {
310 if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
311 (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
312 string_format(buffer, buffsize, "Too few items in client_send in %s "
313 "authenticator", ablock->name);
314 return ERROR;
315 }
4730f942
PH
316
317 /* Now that we know we'll continue, we put the received data into $auth<n>,
318 if possible. First, decode it: buffer+4 skips over the SMTP status code. */
319
320 clear_len = auth_b64decode(buffer+4, &clear);
321
322 /* If decoding failed, the default is to terminate the authentication, and
323 return FAIL, with the SMTP response still in the buffer. However, if client_
324 ignore_invalid_base64 is set, we ignore the error, and put an empty string
325 into $auth<n>. */
326
327 if (clear_len < 0)
328 {
329 uschar *save_bad = string_copy(buffer);
330 if (!ob->client_ignore_invalid_base64)
331 {
332 if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
333 (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
334 string_format(buffer, buffsize, "Invalid base64 string in server "
335 "response \"%s\"", save_bad);
336 return CANCELLED;
337 }
338 clear = US"";
339 clear_len = 0;
340 }
341
342 if (auth_var_idx < AUTH_VARS)
343 auth_vars[auth_var_idx++] = string_copy(clear);
0756eb3c
PH
344 }
345
346/* Control should never actually get here. */
347
348return FAIL;
349}
350
351/* End of plaintext.c */