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