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