0c9fb37fe8455b86d7e3e17dc2f7d0f878730710
[exim.git] / src / src / auths / get_data.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 #include "../exim.h"
11
12
13 /****************************************************************
14 *       Decode and split the argument of an AUTH command        *
15 ****************************************************************/
16
17 /* If data was supplied on the AUTH command, decode it, and split it up into
18 multiple items at binary zeros. The strings are put into $auth1, $auth2, etc,
19 up to a maximum. To retain backwards compatibility, they are also put int $1,
20 $2, etc. If the data consists of the string "=" it indicates a single, empty
21 string. */
22
23 int
24 auth_read_input(const uschar * data)
25 {
26 if (Ustrcmp(data, "=") == 0)
27   {
28   auth_vars[0] = expand_nstring[++expand_nmax] = US"";
29   expand_nlength[expand_nmax] = 0;
30   }
31 else
32   {
33   uschar * clear, * end;
34   int len;
35
36   if ((len = b64decode(data, &clear, GET_TAINTED)) < 0) return BAD64;
37   DEBUG(D_auth) debug_printf("auth input decode:");
38   for (end = clear + len; clear < end && expand_nmax < EXPAND_MAXN; )
39     {
40     DEBUG(D_auth) debug_printf(" '%s'", clear);
41     if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
42     expand_nstring[++expand_nmax] = clear;
43     while (*clear != 0) clear++;
44     expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
45     }
46   DEBUG(D_auth) debug_printf("\n");
47   }
48 return OK;
49 }
50
51
52
53
54 /*************************************************
55 *      Issue a challenge and get a response      *
56 *************************************************/
57
58 /* This function is used by authentication drivers to b64-encode and
59 output a challenge to the SMTP client, and read the response line.
60
61 Arguments:
62    aptr       set to point to the response (which is in big_buffer)
63    challenge  the challenge data (unencoded, may be binary)
64    challen    the length of the challenge data, in bytes
65
66 Returns:      OK on success
67               BAD64 if response too large for buffer
68               CANCELLED if response is "*"
69
70 NOTE: the data came from the wire so should be tainted - but
71 big_buffer is not taint-tracked.  EVERY CALLER needs to apply
72 tainting.
73 */
74
75 int
76 auth_get_data(uschar ** aptr, const uschar * challenge, int challen)
77 {
78 int c;
79 int p = 0;
80 smtp_printf("334 %s\r\n", SP_NO_MORE, b64encode(challenge, challen));
81 while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
82   {
83   if (p >= big_buffer_size - 1) return BAD64;
84   big_buffer[p++] = c;
85   }
86 if (p > 0 && big_buffer[p-1] == '\r') p--;
87 big_buffer[p] = 0;
88 DEBUG(D_receive) debug_printf("SMTP<< %s\n", big_buffer);
89 if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
90 *aptr = big_buffer;
91 return OK;
92 }
93
94
95
96 int
97 auth_prompt(const uschar * challenge)
98 {
99 int rc, len;
100 uschar * resp, * clear, * end;
101
102 if ((rc = auth_get_data(&resp, challenge, Ustrlen(challenge))) != OK)
103   return rc;
104 if ((len = b64decode(resp, &clear, GET_TAINTED)) < 0)
105   return BAD64;
106 end = clear + len;
107
108 /* This loop must run at least once, in case the length is zero */
109 do
110   {
111   if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
112   expand_nstring[++expand_nmax] = clear;
113   while (*clear != 0) clear++;
114   expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
115   }
116 while (clear < end && expand_nmax < EXPAND_MAXN);
117 return OK;
118 }
119
120
121 /***********************************************
122 *       Send an AUTH-negotiation item           *
123 ************************************************/
124
125 /* Expand and send one client auth item and read the response.
126 Include the AUTH command and method if tagged as "first".  Use the given buffer
127 for receiving the b6-encoded reply; decode it it return it in the string arg.
128
129 Return:
130   OK          success
131   FAIL_SEND   error after writing a command; errno is set
132   FAIL        failed after reading a response;
133               either errno is set (for timeouts, I/O failures) or
134               the buffer contains the SMTP response line
135   CANCELLED   the client cancelled authentication (often "fail" in expansion)
136               the buffer may contain a message; if not, *buffer = 0
137   ERROR       local problem (typically expansion error); message in buffer
138   DEFER       more items expected
139 */
140
141 int
142 auth_client_item(void * sx, auth_instance * ablock, const uschar ** inout,
143   unsigned flags, int timeout, uschar * buffer, int buffsize)
144 {
145 int len, clear_len;
146 uschar * ss, * clear;
147
148 ss = US expand_cstring(*inout);
149 if (ss == *inout) ss = string_copy(ss);
150
151 /* Forced expansion failure is not an error; authentication is abandoned. On
152 all but the first string, we have to abandon the authentication attempt by
153 sending a line containing "*". Save the failed expansion string, because it
154 is in big_buffer, and that gets used by the sending function. */
155
156 if (!ss)
157   {
158   if (!(flags & AUTH_ITEM_FIRST))
159     {
160     if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
161       (void) smtp_read_response(sx, US buffer, buffsize, '2', timeout);
162     }
163   if (f.expand_string_forcedfail)
164     {
165     *buffer = 0;       /* No message */
166     return CANCELLED;
167     }
168   string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
169     "authenticator: %s", *inout, ablock->name, expand_string_message);
170   return ERROR;
171   }
172
173 len = Ustrlen(ss);
174
175 /* The character ^ is used as an escape for a binary zero character, which is
176 needed for the PLAIN mechanism. It must be doubled if really needed.
177
178 The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is
179 no way to get a leading ^ after a NUL.  We would need to intro new syntax to
180 support that (probably preferring to take a more-standard exim list as a source
181 and concat the elements with intervening NULs.  Either a magic marker on the
182 source string for client_send, or a new option). */
183
184 for (int i = 0; i < len; i++)
185   if (ss[i] == '^')
186     if (ss[i+1] != '^')
187       ss[i] = 0;
188     else
189       if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i);
190
191 /* The first string is attached to the AUTH command; others are sent
192 unembellished. */
193
194 if (flags & AUTH_ITEM_FIRST)
195   {
196   if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
197        ablock->public_name, len == 0 ? "" : " ", b64encode(CUS ss, len)) < 0)
198     return FAIL_SEND;
199   }
200 else
201   if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS ss, len)) < 0)
202     return FAIL_SEND;
203
204 /* If we receive a success response from the server, authentication
205 has succeeded. There may be more data to send, but is there any point
206 in provoking an error here? */
207
208 if (smtp_read_response(sx, buffer, buffsize, '2', timeout))
209   {
210   *inout = NULL;
211   return OK;
212   }
213
214 /* Not a success response. If errno != 0 there is some kind of transmission
215 error. Otherwise, check the response code in the buffer. If it starts with
216 '3', more data is expected. */
217
218 if (errno != 0 || buffer[0] != '3') return FAIL;
219
220 /* If there is no more data to send, we have to cancel the authentication
221 exchange and return ERROR. */
222
223 if (flags & AUTH_ITEM_LAST)
224   {
225   if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
226     (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
227   string_format(buffer, buffsize, "Too few items in client_send in %s "
228     "authenticator", ablock->name);
229   return ERROR;
230   }
231
232 /* Now that we know we'll continue, we put the received data into $auth<n>,
233 if possible. First, decode it: buffer+4 skips over the SMTP status code. */
234
235 clear_len = b64decode(buffer+4, &clear, buffer+4);
236
237 /* If decoding failed, the default is to terminate the authentication, and
238 return FAIL, with the SMTP response still in the buffer. However, if client_
239 ignore_invalid_base64 is set, we ignore the error, and put an empty string
240 into $auth<n>. */
241
242 if (clear_len < 0)
243   {
244   uschar *save_bad = string_copy(buffer);
245   if (!(flags & AUTH_ITEM_IGN64))
246     {
247     if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
248       (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
249     string_format(buffer, buffsize, "Invalid base64 string in server "
250       "response \"%s\"", save_bad);
251     return CANCELLED;
252     }
253   DEBUG(D_auth) debug_printf("bad b64 decode for '%s';"
254        " ignoring due to client_ignore_invalid_base64\n", save_bad);
255   clear = string_copy(US"");
256   clear_len = 0;
257   }
258
259 *inout = clear;
260 return DEFER;
261 }
262   
263   
264 /* End of get_data.c */