Merge branch 'master' into 4.next
[users/jgh/exim.git] / src / src / auths / cyrus_sasl.c
index 7e97acb794bdda927dda9a5aa62bab6ca3249693..afbeb81e2383bd359a0b113901ea9694a9566a80 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/auths/cyrus_sasl.c,v 1.6 2009/11/16 19:50:38 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This code was originally contributed by Matthew Byng-Maddick */
@@ -27,7 +25,9 @@ in a dummy argument to stop even pickier compilers complaining about infinite
 loops. */
 
 #ifndef AUTH_CYRUS_SASL
-static void dummy(int x) { dummy(x-1); }
+static void dummy(int x);
+static void dummy2(int x) { dummy(x-1); }
+static void dummy(int x) { dummy2(x-1); }
 #else
 
 
@@ -63,6 +63,20 @@ auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_cyrus_sasl_init(auth_instance *ablock) {}
+int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_cyrus_sasl_client(auth_instance *ablock, smtp_inblock *inblock,
+  smtp_outblock *outblock, int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_cyrus_sasl_version_report(FILE *f) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -97,10 +111,11 @@ auth_cyrus_sasl_init(auth_instance *ablock)
 {
 auth_cyrus_sasl_options_block *ob =
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
-uschar *list, *listptr, *buffer;
+const uschar *list, *listptr, *buffer;
 int rc, i;
 unsigned int len;
-uschar *rs_point;
+uschar *rs_point, *expanded_hostname;
+char *realm_expanded;
 
 sasl_conn_t *conn;
 sasl_callback_t cbs[]={
@@ -111,11 +126,26 @@ sasl_callback_t cbs[]={
 if(ob->server_mech == NULL)
   ob->server_mech=string_copy(ablock->public_name);
 
+expanded_hostname = expand_string(ob->server_hostname);
+if (expanded_hostname == NULL)
+  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+      "couldn't expand server_hostname [%s]: %s",
+      ablock->name, ob->server_hostname, expand_string_message);
+
+realm_expanded=NULL;
+if (ob->server_realm != NULL) {
+  realm_expanded = CS expand_string(ob->server_realm);
+  if (realm_expanded == NULL)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+        "couldn't expand server_realm [%s]: %s",
+        ablock->name, ob->server_realm, expand_string_message);
+}
+
 /* we're going to initialise the library to check that there is an
  * authenticator of type whatever mechanism we're using
  */
 
-cbs[0].proc = &mysasl_config;
+cbs[0].proc = (int(*)(void)) &mysasl_config;
 cbs[0].context = ob->server_mech;
 
 rc=sasl_server_init(cbs, "exim");
@@ -124,13 +154,13 @@ if( rc != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL library.", ablock->name);
 
-rc=sasl_server_new(CS ob->server_service, CS primary_hostname,
-                   CS ob->server_realm, NULL, NULL, NULL, 0, &conn);
+rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
+                   realm_expanded, NULL, NULL, NULL, 0, &conn);
 if( rc != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL server connection.", ablock->name);
 
-rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
+rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
 if( rc != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't get Cyrus SASL mechanism list.", ablock->name);
@@ -138,7 +168,11 @@ if( rc != SASL_OK )
 i=':';
 listptr=list;
 
-HDEBUG(D_auth) debug_printf("Cyrus SASL knows about: %s\n", list);
+HDEBUG(D_auth) {
+  debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
+      ob->server_service, expanded_hostname, realm_expanded);
+  debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
+}
 
 /* the store_get / store_reset mechanism is hierarchical
  * the hierarchy is stored for us behind our back. This point
@@ -186,7 +220,8 @@ uschar *output, *out2, *input, *clear, *hname;
 uschar *debug = NULL;   /* Stops compiler complaining */
 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
 sasl_conn_t *conn;
-int rc, firsttime=1, clen;
+char *realm_expanded;
+int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
 unsigned int inlen, outlen;
 
 input=data;
@@ -195,7 +230,11 @@ inlen=Ustrlen(data);
 HDEBUG(D_auth) debug=string_copy(data);
 
 hname=expand_string(ob->server_hostname);
-if(hname == NULL)
+realm_expanded=NULL;
+if (hname && ob->server_realm)
+  realm_expanded= CS expand_string(ob->server_realm);
+if((hname == NULL) ||
+   ((realm_expanded == NULL) && (ob->server_realm != NULL)))
   {
   auth_defer_msg = expand_string_message;
   return DEFER;
@@ -203,7 +242,7 @@ if(hname == NULL)
 
 if(inlen)
   {
-  clen=auth_b64decode(input, &clear);
+  clen = b64decode(input, &clear);
   if(clen < 0)
     {
     return BAD64;
@@ -219,9 +258,13 @@ if (rc != SASL_OK)
   return DEFER;
   }
 
-rc=sasl_server_new(CS ob->server_service, CS hname, CS ob->server_realm, NULL,
+rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
   NULL, NULL, 0, &conn);
 
+HDEBUG(D_auth)
+  debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
+      ob->server_service, hname, realm_expanded);
+
 if( rc != SASL_OK )
   {
   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
@@ -229,6 +272,81 @@ if( rc != SASL_OK )
   return DEFER;
   }
 
+if (tls_in.cipher)
+  {
+  rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
+  if (rc != SASL_OK)
+    {
+    HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
+        tls_in.bits, sasl_errstring(rc, NULL, NULL));
+    auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
+    sasl_done();
+    return DEFER;
+    }
+  else
+    HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
+  }
+else
+  HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
+
+/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
+annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
+with their iptostring() function, which just wraps
+getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
+inet_ntop which we wrap in our host_ntoa() function.
+
+So the docs are too strict and we shouldn't worry about :: contractions. */
+
+/* Set properties for remote and local host-ip;port */
+for (i=0; i < 2; ++i)
+  {
+  struct sockaddr_storage ss;
+  int (*query)(int, struct sockaddr *, socklen_t *);
+  int propnum, port;
+  const uschar *label;
+  uschar *address, *address_port;
+  const char *s_err;
+  socklen_t sslen;
+
+  if (i)
+    {
+    query = &getpeername;
+    propnum = SASL_IPREMOTEPORT;
+    label = CUS"peer";
+    }
+  else
+    {
+    query = &getsockname;
+    propnum = SASL_IPLOCALPORT;
+    label = CUS"local";
+    }
+
+  sslen = sizeof(ss);
+  rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
+  if (rc < 0)
+    {
+    HDEBUG(D_auth)
+      debug_printf("Failed to get %s address information: %s\n",
+          label, strerror(errno));
+    break;
+    }
+
+  address = host_ntoa(-1, &ss, NULL, &port);
+  address_port = string_sprintf("%s;%d", address, port);
+
+  rc = sasl_setprop(conn, propnum, address_port);
+  if (rc != SASL_OK)
+    {
+    s_err = sasl_errdetail(conn);
+    HDEBUG(D_auth)
+      debug_printf("Failed to set %s SASL property: [%d] %s\n",
+          label, rc, s_err ? s_err : "<unknown reason>");
+    break;
+    }
+  HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
+      label, address_port);
+  }
+
 rc=SASL_CONTINUE;
 
 while(rc==SASL_CONTINUE)
@@ -259,7 +377,7 @@ while(rc==SASL_CONTINUE)
     HDEBUG(D_auth) debug=string_copy(input);
     if(inlen)
       {
-      clen=auth_b64decode(input, &clear);
+      clen = b64decode(input, &clear);
       if(clen < 0)
        {
         sasl_dispose(&conn);
@@ -327,12 +445,54 @@ while(rc==SASL_CONTINUE)
     /* Get the username and copy it into $auth1 and $1. The former is now the
     preferred variable; the latter is the original variable. */
     rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
+    if (rc != SASL_OK)
+      {
+      HDEBUG(D_auth)
+        debug_printf("Cyrus SASL library will not tell us the username: %s\n",
+            sasl_errstring(rc, NULL, NULL));
+      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+         "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
+         sasl_errstring(rc, NULL, NULL));
+      sasl_dispose(&conn);
+      sasl_done();
+      return FAIL;
+      }
+
     auth_vars[0] = expand_nstring[1] = string_copy(out2);
     expand_nlength[1] = Ustrlen(expand_nstring[1]);
     expand_nmax = 1;
 
     HDEBUG(D_auth)
-      debug_printf("Cyrus SASL %s authentication succeeded for %s\n", ob->server_mech, out2);
+      debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
+          ob->server_mech, auth_vars[0]);
+
+    rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
+    if (rc != SASL_OK)
+      {
+      HDEBUG(D_auth)
+        debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
+            sasl_errstring(rc, NULL, NULL));
+      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+          "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
+          sasl_errstring(rc, NULL, NULL));
+      sasl_dispose(&conn);
+      sasl_done();
+      return FAIL;
+      }
+    negotiated_ssf = *negotiated_ssf_ptr;
+    HDEBUG(D_auth)
+      debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
+    if (negotiated_ssf > 0)
+      {
+      HDEBUG(D_auth)
+        debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
+      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+          "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
+      sasl_dispose(&conn);
+      sasl_done();
+      return FAIL;
+      }
+
     /* close down the connection, freeing up library's memory */
     sasl_dispose(&conn);
     sasl_done();
@@ -379,6 +539,7 @@ auth_cyrus_sasl_client(
 return FAIL;
 }
 
+#endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_CYRUS_SASL */
 
 /* End of cyrus_sasl.c */