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