Authenticators: refactor SASL support code
[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 /* See the file NOTICE for conditions of use and distribution. */
7
8 #include "../exim.h"
9
10
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
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
70 auth_get_data(uschar ** aptr, const uschar * challenge, int challen)
71 {
72 int c;
73 int p = 0;
74 smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen));
75 while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
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;
82 DEBUG(D_receive) debug_printf("SMTP<< %s\n", big_buffer);
83 if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
84 *aptr = big_buffer;
85 return OK;
86 }
87
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
177       {
178       i++;
179       len--;
180       memmove(ss + i, ss + i + 1, len - i);
181       }
182
183 /* The first string is attached to the AUTH command; others are sent
184 unembellished. */
185
186 if (flags & AUTH_ITEM_FIRST)
187   {
188   if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
189        ablock->public_name, len == 0 ? "" : " ", b64encode(CUS ss, len)) < 0)
190     return FAIL_SEND;
191   }
192 else
193   if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS ss, len)) < 0)
194     return FAIL_SEND;
195
196 /* If we receive a success response from the server, authentication
197 has succeeded. There may be more data to send, but is there any point
198 in provoking an error here? */
199
200 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
201   {
202   *inout = NULL;
203   return OK;
204   }
205
206 /* Not a success response. If errno != 0 there is some kind of transmission
207 error. Otherwise, check the response code in the buffer. If it starts with
208 '3', more data is expected. */
209
210 if (errno != 0 || buffer[0] != '3') return FAIL;
211
212 /* If there is no more data to send, we have to cancel the authentication
213 exchange and return ERROR. */
214
215 if (flags & AUTH_ITEM_LAST)
216   {
217   if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
218     (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
219   string_format(buffer, buffsize, "Too few items in client_send in %s "
220     "authenticator", ablock->name);
221   return ERROR;
222   }
223
224 /* Now that we know we'll continue, we put the received data into $auth<n>,
225 if possible. First, decode it: buffer+4 skips over the SMTP status code. */
226
227 clear_len = b64decode(buffer+4, &clear);
228
229 /* If decoding failed, the default is to terminate the authentication, and
230 return FAIL, with the SMTP response still in the buffer. However, if client_
231 ignore_invalid_base64 is set, we ignore the error, and put an empty string
232 into $auth<n>. */
233
234 if (clear_len < 0)
235   {
236   uschar *save_bad = string_copy(buffer);
237   if (!(flags & AUTH_ITEM_IGN64))
238     {
239     if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
240       (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
241     string_format(buffer, buffsize, "Invalid base64 string in server "
242       "response \"%s\"", save_bad);
243     return CANCELLED;
244     }
245   DEBUG(D_auth) debug_printf("bad b64 decode for '%s';"
246        " ignoring due to client_ignore_invalid_base64\n", save_bad);
247   clear = string_copy(US"");
248   clear_len = 0;
249   }
250
251 *inout = clear;
252 return DEFER;
253 }
254   
255   
256 /* End of get_data.c */