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
auth_read_input(const uschar * data)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
auth_get_data(uschar ** aptr,const uschar * challenge,int challen)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
auth_prompt(const uschar * challenge)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
auth_client_item(void * sx,auth_instance * ablock,const uschar ** inout,unsigned flags,int timeout,uschar * buffer,int buffsize)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 The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is
174 no way to get a leading ^ after a NUL. We would need to intro new syntax to
175 support that (probably preferring to take a more-standard exim list as a source
176 and concat the elements with intervening NULs. Either a magic marker on the
177 source string for client_send, or a new option). */
178
179 for (int i = 0; i < len; i++)
180 if (ss[i] == '^')
181 if (ss[i+1] != '^')
182 ss[i] = 0;
183 else
184 if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i);
185
186 /* The first string is attached to the AUTH command; others are sent
187 unembellished. */
188
189 if (flags & AUTH_ITEM_FIRST)
190 {
191 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
192 ablock->public_name, len == 0 ? "" : " ", b64encode(CUS ss, len)) < 0)
193 return FAIL_SEND;
194 }
195 else
196 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS ss, len)) < 0)
197 return FAIL_SEND;
198
199 /* If we receive a success response from the server, authentication
200 has succeeded. There may be more data to send, but is there any point
201 in provoking an error here? */
202
203 if (smtp_read_response(sx, buffer, buffsize, '2', timeout))
204 {
205 *inout = NULL;
206 return OK;
207 }
208
209 /* Not a success response. If errno != 0 there is some kind of transmission
210 error. Otherwise, check the response code in the buffer. If it starts with
211 '3', more data is expected. */
212
213 if (errno != 0 || buffer[0] != '3') return FAIL;
214
215 /* If there is no more data to send, we have to cancel the authentication
216 exchange and return ERROR. */
217
218 if (flags & AUTH_ITEM_LAST)
219 {
220 if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
221 (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
222 string_format(buffer, buffsize, "Too few items in client_send in %s "
223 "authenticator", ablock->name);
224 return ERROR;
225 }
226
227 /* Now that we know we'll continue, we put the received data into $auth<n>,
228 if possible. First, decode it: buffer+4 skips over the SMTP status code. */
229
230 clear_len = b64decode(buffer+4, &clear);
231
232 /* If decoding failed, the default is to terminate the authentication, and
233 return FAIL, with the SMTP response still in the buffer. However, if client_
234 ignore_invalid_base64 is set, we ignore the error, and put an empty string
235 into $auth<n>. */
236
237 if (clear_len < 0)
238 {
239 uschar *save_bad = string_copy(buffer);
240 if (!(flags & AUTH_ITEM_IGN64))
241 {
242 if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
243 (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
244 string_format(buffer, buffsize, "Invalid base64 string in server "
245 "response \"%s\"", save_bad);
246 return CANCELLED;
247 }
248 DEBUG(D_auth) debug_printf("bad b64 decode for '%s';"
249 " ignoring due to client_ignore_invalid_base64\n", save_bad);
250 clear = string_copy(US"");
251 clear_len = 0;
252 }
253
254 *inout = clear;
255 return DEFER;
256 }
257
258
259 /* End of get_data.c */
260