EXTERNAL authenticator
[exim.git] / src / src / auths / get_data.c
index 4d6c6d4a647bf69a00d5a4a45452a52f05cdfbf6..37dcd37cc54a43ed6fe23780eb58df3ad46d822b 100644 (file)
@@ -2,12 +2,53 @@
 *     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      *
 *************************************************/
@@ -26,12 +67,12 @@ Returns:      OK on success
 */
 
 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;
@@ -44,4 +85,172 @@ if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
 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 */