Commit | Line | Data |
---|---|---|
0756eb3c PH |
1 | /************************************************* |
2 | * Exim - an Internet mail transport agent * | |
3 | *************************************************/ | |
4 | ||
f9ba5e22 | 5 | /* Copyright (c) University of Cambridge 1995 - 2018 */ |
1e1ddfac | 6 | /* Copyright (c) The Exim Maintainers 2020 */ |
0756eb3c PH |
7 | /* See the file NOTICE for conditions of use and distribution. */ |
8 | ||
9 | #include "../exim.h" | |
10 | ||
11 | ||
37942ad8 JH |
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 | ||
0756eb3c PH |
53 | /************************************************* |
54 | * Issue a challenge and get a response * | |
55 | *************************************************/ | |
56 | ||
5c329a43 JH |
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. | |
0756eb3c PH |
59 | |
60 | Arguments: | |
61 | aptr set to point to the response (which is in big_buffer) | |
5c329a43 JH |
62 | challenge the challenge data (unencoded, may be binary) |
63 | challen the length of the challenge data, in bytes | |
0756eb3c PH |
64 | |
65 | Returns: OK on success | |
66 | BAD64 if response too large for buffer | |
67 | CANCELLED if response is "*" | |
68 | */ | |
69 | ||
70 | int | |
37942ad8 | 71 | auth_get_data(uschar ** aptr, const uschar * challenge, int challen) |
0756eb3c PH |
72 | { |
73 | int c; | |
74 | int p = 0; | |
925ac8e4 | 75 | smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen)); |
bd8fbe36 | 76 | while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF) |
0756eb3c PH |
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; | |
898d150f | 83 | DEBUG(D_receive) debug_printf("SMTP<< %s\n", big_buffer); |
0756eb3c PH |
84 | if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED; |
85 | *aptr = big_buffer; | |
86 | return OK; | |
87 | } | |
88 | ||
37942ad8 JH |
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 | |
f9fc9427 | 178 | if (--len > ++i) memmove(ss + i, ss + i + 1, len - i); |
37942ad8 JH |
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 | ||
14a806d6 | 197 | if (smtp_read_response(sx, buffer, buffsize, '2', timeout)) |
37942ad8 JH |
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 | ||
0756eb3c | 253 | /* End of get_data.c */ |