* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
+/****************************************************************
+* Decode and split the argument of an AUTH command *
+****************************************************************/
+
+/* If data was supplied on the AUTH command, decode it, and split it up into
+multiple items at binary zeros. The strings are put into $auth1, $auth2, etc,
+up to a maximum. To retain backwards compatibility, they are also put int $1,
+$2, etc. If the data consists of the string "=" it indicates a single, empty
+string. */
+
+int
+auth_read_input(const uschar * data)
+{
+if (Ustrcmp(data, "=") == 0)
+ {
+ auth_vars[0] = expand_nstring[++expand_nmax] = US"";
+ expand_nlength[expand_nmax] = 0;
+ }
+else
+ {
+ uschar * clear, * end;
+ int len;
+
+ if ((len = b64decode(data, &clear)) < 0) return BAD64;
+ DEBUG(D_auth) debug_printf("auth input decode:");
+ for (end = clear + len; clear < end && expand_nmax < EXPAND_MAXN; )
+ {
+ DEBUG(D_auth) debug_printf(" '%s'", clear);
+ if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
+ expand_nstring[++expand_nmax] = clear;
+ while (*clear != 0) clear++;
+ expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
+ }
+ DEBUG(D_auth) debug_printf("\n");
+ }
+return OK;
+}
+
+
+
+
/*************************************************
* Issue a challenge and get a response *
*************************************************/
*/
int
-auth_get_data(uschar **aptr, uschar *challenge, int challen)
+auth_get_data(uschar ** aptr, const uschar * challenge, int challen)
{
int c;
int p = 0;
-smtp_printf("334 %s\r\n", b64encode(challenge, challen));
-while ((c = receive_getc()) != '\n' && c != EOF)
+smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen));
+while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
big_buffer[p++] = c;
return OK;
}
+
+
+int
+auth_prompt(const uschar * challenge)
+{
+int rc, len;
+uschar * resp, * clear, * end;
+
+if ((rc = auth_get_data(&resp, challenge, Ustrlen(challenge))) != OK)
+ return rc;
+if ((len = b64decode(resp, &clear)) < 0)
+ return BAD64;
+end = clear + len;
+
+/* This loop must run at least once, in case the length is zero */
+do
+ {
+ if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
+ expand_nstring[++expand_nmax] = clear;
+ while (*clear != 0) clear++;
+ expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
+ }
+while (clear < end && expand_nmax < EXPAND_MAXN);
+return OK;
+}
+
+
+/***********************************************
+* Send an AUTH-negotiation item *
+************************************************/
+
+/* Expand and send one client auth item and read the response.
+Include the AUTH command and method if tagged as "first". Use the given buffer
+for receiving the b6-encoded reply; decode it it return it in the string arg.
+
+Return:
+ OK success
+ FAIL_SEND error after writing a command; errno is set
+ FAIL failed after reading a response;
+ either errno is set (for timeouts, I/O failures) or
+ the buffer contains the SMTP response line
+ CANCELLED the client cancelled authentication (often "fail" in expansion)
+ the buffer may contain a message; if not, *buffer = 0
+ ERROR local problem (typically expansion error); message in buffer
+ DEFER more items expected
+*/
+
+int
+auth_client_item(void * sx, auth_instance * ablock, const uschar ** inout,
+ unsigned flags, int timeout, uschar * buffer, int buffsize)
+{
+int len, clear_len;
+uschar * ss, * clear;
+
+ss = US expand_cstring(*inout);
+if (ss == *inout) ss = string_copy(ss);
+
+/* Forced expansion failure is not an error; authentication is abandoned. On
+all but the first string, we have to abandon the authentication attempt by
+sending a line containing "*". Save the failed expansion string, because it
+is in big_buffer, and that gets used by the sending function. */
+
+if (!ss)
+ {
+ if (!(flags & AUTH_ITEM_FIRST))
+ {
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void) smtp_read_response(sx, US buffer, buffsize, '2', timeout);
+ }
+ if (f.expand_string_forcedfail)
+ {
+ *buffer = 0; /* No message */
+ return CANCELLED;
+ }
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+ "authenticator: %s", *inout, ablock->name, expand_string_message);
+ return ERROR;
+ }
+
+len = Ustrlen(ss);
+
+/* The character ^ is used as an escape for a binary zero character, which is
+needed for the PLAIN mechanism. It must be doubled if really needed. */
+
+for (int i = 0; i < len; i++)
+ if (ss[i] == '^')
+ if (ss[i+1] != '^')
+ ss[i] = 0;
+ else
+ {
+ i++;
+ len--;
+ memmove(ss + i, ss + i + 1, len - i);
+ }
+
+/* The first string is attached to the AUTH command; others are sent
+unembellished. */
+
+if (flags & AUTH_ITEM_FIRST)
+ {
+ if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
+ ablock->public_name, len == 0 ? "" : " ", b64encode(CUS ss, len)) < 0)
+ return FAIL_SEND;
+ }
+else
+ if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS ss, len)) < 0)
+ return FAIL_SEND;
+
+/* If we receive a success response from the server, authentication
+has succeeded. There may be more data to send, but is there any point
+in provoking an error here? */
+
+if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
+ {
+ *inout = NULL;
+ return OK;
+ }
+
+/* Not a success response. If errno != 0 there is some kind of transmission
+error. Otherwise, check the response code in the buffer. If it starts with
+'3', more data is expected. */
+
+if (errno != 0 || buffer[0] != '3') return FAIL;
+
+/* If there is no more data to send, we have to cancel the authentication
+exchange and return ERROR. */
+
+if (flags & AUTH_ITEM_LAST)
+ {
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
+ string_format(buffer, buffsize, "Too few items in client_send in %s "
+ "authenticator", ablock->name);
+ return ERROR;
+ }
+
+/* Now that we know we'll continue, we put the received data into $auth<n>,
+if possible. First, decode it: buffer+4 skips over the SMTP status code. */
+
+clear_len = b64decode(buffer+4, &clear);
+
+/* If decoding failed, the default is to terminate the authentication, and
+return FAIL, with the SMTP response still in the buffer. However, if client_
+ignore_invalid_base64 is set, we ignore the error, and put an empty string
+into $auth<n>. */
+
+if (clear_len < 0)
+ {
+ uschar *save_bad = string_copy(buffer);
+ if (!(flags & AUTH_ITEM_IGN64))
+ {
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
+ string_format(buffer, buffsize, "Invalid base64 string in server "
+ "response \"%s\"", save_bad);
+ return CANCELLED;
+ }
+ DEBUG(D_auth) debug_printf("bad b64 decode for '%s';"
+ " ignoring due to client_ignore_invalid_base64\n", save_bad);
+ clear = string_copy(US"");
+ clear_len = 0;
+ }
+
+*inout = clear;
+return DEFER;
+}
+
+
/* End of get_data.c */