[Exim] Re: Second stab at supporting Cyrus SASL's saslauthd

Top Page
Delete this message
Reply to this message
Author: Alexander Sabourenkov
Date:  
To: exim-users
Subject: [Exim] Re: Second stab at supporting Cyrus SASL's saslauthd
This is a multi-part message in MIME format.
--
Alexander Sabourenkov wrote:
> Hello.
>
> This patch against 4.20 adds the saslauthd condition, which is like
> pwcheck, but not entirely.


Damn. Forgot to attach the file.


--

./lxnt

--
Index: src/auths/call_pwcheck.c
===================================================================
--- src/auths/call_pwcheck.c    (revision 319)
+++ src/auths/call_pwcheck.c    (working copy)
@@ -13,7 +13,7 @@
 *          External entry point                  *
 *************************************************/


-/* This function calls the Cyrus-SASL authentication daemon, passing over a
+/* This function calls a Cyrus-SASL authentication daemon, pwcheck, passing over a
colon-separated user name and password. As this is called from the string
expander, the string will always be in dynamic store and can be overwritten.

@@ -50,7 +50,7 @@
DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply);
return OK;

- case PWCHECK_BAD:
+ case PWCHECK_NO:
DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply);
return FAIL;

@@ -61,4 +61,56 @@
}
}

+/*************************************************
+*          External entry point                  *
+*************************************************/
+
+/* This function calls a Cyrus-SASL authentication daemon, saslauthd,
+As this is called from the string expander, all the strings will always
+be in dynamic store and can be overwritten.
+
+Arguments:
+  username        username
+  password        password
+  service         optional service
+  realm           optional realm
+  errptr   where to point an error message
+
+Returns:   OK if authentication succeeded
+           FAIL if authentication failed
+           ERROR some other error condition
+*/
+
+
+int
+auth_call_saslauthd(uschar *username, uschar *password, uschar *service, uschar *realm, uschar **errptr)
+{
+uschar *reply = NULL;
+
+if (service == NULL)
+    service = "";
+if (realm == NULL)
+    realm = "";
+
+DEBUG(D_auth)
+  debug_printf("Running saslauthd authentication for user \"%s\" \n", username);
+
+switch (saslauthd_verify_password(CS username, CS password, CS service, CS realm, (const char **)(&reply)))
+  {
+  case PWCHECK_OK:
+  DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply);
+  return OK;
+
+  case PWCHECK_NO:
+  DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply);
+  return FAIL;
+
+  default:
+  DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply);
+  *errptr = reply;
+  return ERROR;
+  }
+}
+
+
 /* End of call_pwcheck.c */
Index: src/auths/pwcheck.c
===================================================================
--- src/auths/pwcheck.c    (revision 319)
+++ src/auths/pwcheck.c    (working copy)
@@ -45,7 +45,7 @@


/*
* Taken from Cyrus-SASL library and adapted by Alexander S. Sabourenkov
- * Oct 2001 - Apr 2002. Slightly modified by Philip Hazel.
+ * Oct 2001 - Apr 2003. Slightly modified by Philip Hazel.
*
* screwdriver@???
*
@@ -55,6 +55,17 @@
#include "../exim.h"
#include "pwcheck.h"

+#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
+
+#include <sys/uio.h>
+
+static int retry_read(int, void *, unsigned );
+static int retry_writev(int, struct iovec *, int );
+static int read_string(int, uschar **);
+static int write_string(int, const uschar *, int);
+
+#endif
+
/* A dummy function that always fails if pwcheck support is not
wanted. */

@@ -73,84 +84,22 @@
/* This is the real function */

#else
-#include <sys/uio.h>

-
  /* taken from cyrus-sasl file checkpw.c */
- /*
-  * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
-  * until all the data is written out or an error occurs.
-  */
- static int retry_writev(int fd, struct iovec *iov, int iovcnt)
- {
-     int n;
-     int i;
-     int written = 0;
-     static int iov_max =
- #ifdef MAXIOV
-       MAXIOV
- #else
- #ifdef IOV_MAX
-       IOV_MAX
- #else
-       8192
- #endif
- #endif
-       ;
-
-     for (;;) {
-       while (iovcnt && iov[0].iov_len == 0) {
-       iov++;
-       iovcnt--;
-       }
-
-       if (!iovcnt) return written;
-
-       n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
-       if (n == -1) {
-       if (errno == EINVAL && iov_max > 10) {
-           iov_max /= 2;
-           continue;
-       }
-       if (errno == EINTR) continue;
-       return -1;
-       }
-
-       written += n;
-
-       for (i = 0; i < iovcnt; i++) {
-       if (iov[i].iov_len > n) {
-           iov[i].iov_base = (char *)iov[i].iov_base + n;
-           iov[i].iov_len -= n;
-           break;
-       }
-       n -= iov[i].iov_len;
-       iov[i].iov_len = 0;
-       }
-
-       if (i == iovcnt) return written;
-     }
- }
-
-
- /* taken from cyrus-sasl file checkpw.c */
  /* pwcheck daemon-authenticated login */
  int pwcheck_verify_password(const char *userid,
                   const char *passwd,
                   const char **reply)
  {
-     int s;
+     int s, start, r, n;
      struct sockaddr_un srvaddr;
-     int r;
-     struct iovec iov[10];
+     struct iovec iov[2];
      static char response[1024];
-     int start, n;
-     /* char pwpath[1024];   is not used - PH */


      if (reply) { *reply = NULL; }


      s = socket(AF_UNIX, SOCK_STREAM, 0);
-     if (s == -1) return errno;
+     if (s == -1) { return PWCHECK_FAIL; }


      memset((char *)&srvaddr, 0, sizeof(srvaddr));
      srvaddr.sun_family = AF_UNIX;
@@ -185,9 +134,307 @@


      response[start] = '\0';
      if (reply) { *reply = response; }
-     return PWCHECK_BAD;
+     return PWCHECK_NO;
  }


#endif

+
+ /* A dummy function that always fails if saslauthd support is not
+wanted. */
+
+#ifndef CYRUS_SASLAUTHD_SOCKET
+int saslauthd_verify_password(const char *userid,
+                const char *passwd,
+                const char *service,
+                const char *realm,
+                const char **reply)
+{
+userid = userid;  /* Keep picky compilers happy */
+passwd = passwd;
+service = service;
+realm = realm;
+*reply = "saslauthd support is not included in this Exim binary";
+return PWCHECK_FAIL;
+}
+
+
+/* This is the real function */
+
+#else
+ /* written from scratch  */
+ /* saslauthd daemon-authenticated login */
+
+int saslauthd_verify_password(const char *userid,
+                const char *password,
+                const char *service,
+                const char *realm,
+                const char **reply)
+{
+     uschar *daemon_reply;
+     int s, r;
+     struct sockaddr_un srvaddr;
+
+    DEBUG(D_auth)
+        debug_printf("saslauthd userid='%s' password='%s' servicename='%s'"
+                     " realm='%s'\n", userid, password, service, realm );
+
+    if (reply)
+        *reply = NULL;
+
+    s = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (s == -1) {
+        if (reply)
+            *reply = strerror(errno);
+       return PWCHECK_FAIL;
+    }
+
+    memset((char *)&srvaddr, 0, sizeof(srvaddr));
+    srvaddr.sun_family = AF_UNIX;
+    strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
+            sizeof(srvaddr.sun_path));
+    r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
+    if (r == -1) {
+        DEBUG(D_auth)
+            debug_printf("Cannot connect to saslauthd daemon (at '%s'): %s\n",
+                         CYRUS_SASLAUTHD_SOCKET, strerror(errno));
+       if (reply)
+           *reply = string_sprintf("cannot connect to saslauthd daemon at "
+                                   "%s: %s", strerror(errno));
+       return PWCHECK_FAIL;
+    }
+
+    if ( write_string(s, userid, strlen(userid)) < 0) {
+        DEBUG(D_auth)
+            debug_printf("Failed to send userid to saslauthd daemon \n");
+        close(s);
+        return PWCHECK_FAIL;
+    }
+
+    if ( write_string(s, password, strlen(password)) < 0) {
+        DEBUG(D_auth)
+            debug_printf("Failed to send password to saslauthd daemon \n");
+        close(s);
+        return PWCHECK_FAIL;
+    }
+
+    memset(password, 0, strlen(password));
+
+    if ( write_string(s, service, strlen(service)) < 0) {
+        DEBUG(D_auth)
+            debug_printf("Failed to send service name to saslauthd daemon \n");
+        close(s);
+        return PWCHECK_FAIL;
+    }
+
+    if ( write_string(s, realm, strlen(realm)) < 0) {
+        DEBUG(D_auth)
+            debug_printf("Failed to send realm to saslauthd daemon \n");
+        close(s);
+        return PWCHECK_FAIL;
+    }
+
+    if ( read_string(s, &daemon_reply ) < 2) {
+        DEBUG(D_auth)
+            debug_printf("Corrupted answer '%s' received. \n", daemon_reply);
+        close(s);
+        return PWCHECK_FAIL;
+    }
+
+    close(s);
+
+    DEBUG(D_auth)
+        debug_printf("Answer '%s' received. \n", daemon_reply);
+
+    *reply = daemon_reply;
+
+    if ( (daemon_reply[0] == 'O') && (daemon_reply[1] == 'K') )
+        return PWCHECK_OK;
+
+    if ( (daemon_reply[0] == 'N') && (daemon_reply[1] == 'O') )
+        return PWCHECK_NO;
+
+    return PWCHECK_FAIL;
+}
+
+#endif
+
+
+/* helper functions */
+#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
+
+#define MAX_REQ_LEN 1024
+
+/* written from scratch */
+
+/* FUNCTION: read_string */
+
+/* SYNOPSIS
+ * read a sasld-style counted string into
+ * store-allocated buffer, set pointer to the buffer,
+ * return number of bytes read or -1 on failure.
+ * END SYNOPSIS */
+
+static int read_string(int fd, uschar **retval) {
+    unsigned short count;
+    int rc;
+
+    rc = (retry_read(fd, &count, sizeof(count)) < (int) sizeof(count));
+    if (!rc) {
+        count = ntohs(count);
+        if (count > MAX_REQ_LEN) {
+            return -1;
+        } else {
+            *retval = store_get(count + 1);
+            rc = (retry_read(fd, *retval, count) < (int) count);
+            (*retval)[count] = '\0';
+            return count;
+        }
+    }
+}
+
+
+/* FUNCTION: write_string */
+
+/* SYNOPSIS
+ * write a sasld-style counted string into given fd
+ * written bytes on success, -1 on failure.
+ * END SYNOPSIS */
+
+static int write_string(int fd, const uschar *string, int len) {
+    unsigned short count;
+    int rc;
+    struct iovec iov[2];
+
+    count = htons(len);
+
+    iov[0].iov_base = (void *) &count;
+    iov[0].iov_len = sizeof(count);
+    iov[1].iov_base = (void *) string;
+    iov[1].iov_len = len;
+
+    rc = retry_writev(fd, iov, 2);
+
+    return rc;
+}
+
+
+/* taken from cyrus-sasl file saslauthd/saslauthd-unix.c  */
+
+/* FUNCTION: retry_read */
+
+/* SYNOPSIS
+ * Keep calling the read() system call with 'fd', 'buf', and 'nbyte'
+ * until all the data is read in or an error occurs.
+ * END SYNOPSIS */
+static int retry_read(int fd, void *inbuf, unsigned nbyte)
+{
+    int n;
+    int nread = 0;
+    char *buf = (char *)inbuf;
+
+    if (nbyte == 0) return 0;
+
+    for (;;) {
+    n = read(fd, buf, nbyte);
+    if (n == 0) {
+        /* end of file */
+        return -1;
+    }
+    if (n == -1) {
+        if (errno == EINTR) continue;
+        return -1;
+    }
+
+    nread += n;
+
+    if (n >= (int) nbyte) return nread;
+
+    buf += n;
+    nbyte -= n;
+    }
+}
+
+/* END FUNCTION: retry_read */
+
+/* FUNCTION: retry_writev */
+
+/* SYNOPSIS
+ * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
+ * until all the data is written out or an error occurs.
+ * END SYNOPSIS */
+
+static int    /* R: bytes written, or -1 on error */
+retry_writev (
+  /* PARAMETERS */
+  int fd,                /* I: fd to write on */
+  struct iovec *iov,            /* U: iovec array base
+                     *    modified as data written */
+  int iovcnt                /* I: number of iovec entries */
+  /* END PARAMETERS */
+  )
+{
+    /* VARIABLES */
+    int n;                /* return value from writev() */
+    int i;                /* loop counter */
+    int written;            /* bytes written so far */
+    static int iov_max;            /* max number of iovec entries */
+    /* END VARIABLES */
+
+    /* initialization */
+#ifdef MAXIOV
+    iov_max = MAXIOV;
+#else /* ! MAXIOV */
+# ifdef IOV_MAX
+    iov_max = IOV_MAX;
+# else /* ! IOV_MAX */
+    iov_max = 8192;
+# endif /* ! IOV_MAX */
+#endif /* ! MAXIOV */
+    written = 0;
+
+    for (;;) {
+
+    while (iovcnt && iov[0].iov_len == 0) {
+        iov++;
+        iovcnt--;
+    }
+
+    if (!iovcnt) {
+        return written;
+    }
+
+    n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
+    if (n == -1) {
+        if (errno == EINVAL && iov_max > 10) {
+        iov_max /= 2;
+        continue;
+        }
+        if (errno == EINTR) {
+        continue;
+        }
+        return -1;
+    } else {
+        written += n;
+    }
+
+    for (i = 0; i < iovcnt; i++) {
+        if (iov[i].iov_len > (unsigned) n) {
+        iov[i].iov_base = (char *)iov[i].iov_base + n;
+        iov[i].iov_len -= n;
+        break;
+        }
+        n -= iov[i].iov_len;
+        iov[i].iov_len = 0;
+    }
+
+    if (i == iovcnt) {
+        return written;
+    }
+    }
+    /* NOTREACHED */
+}
+
+/* END FUNCTION: retry_writev */
+#endif
 /* End of auths/pwcheck.c */
Index: src/auths/pwcheck.h
===================================================================
--- src/auths/pwcheck.h    (revision 319)
+++ src/auths/pwcheck.h    (working copy)
@@ -9,13 +9,18 @@
 daemon. */


/* Error codes */
+/* OK - auth successful
+ NO - access denied
+ FAIL - [temporary] failure */

+
#define PWCHECK_OK 0
-#define PWCHECK_BAD 1
+#define PWCHECK_NO 1
#define PWCHECK_FAIL 2

-/* Cyrus function for doing the business */
+/* Cyrus functions for doing the business */

extern int pwcheck_verify_password(const char *, const char *, const char **);
+extern int saslauthd_verify_password(const char *, const char *, const char *, const char *, const char **);

 /* End of pwcheck.h */
Index: src/functions.h
===================================================================
--- src/functions.h    (revision 319)
+++ src/functions.h    (working copy)
@@ -42,6 +42,7 @@
 extern int     auth_b64decode(uschar *, uschar **);
 extern int     auth_call_pam(uschar *, uschar **);
 extern int     auth_call_pwcheck(uschar *, uschar **);
+extern int     auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *, uschar **);
 extern int     auth_call_radius(uschar *, uschar **);
 extern int     auth_get_data(uschar **, uschar *);
 extern int     auth_get_no64_data(uschar **, uschar *);
Index: src/EDITME
===================================================================
--- src/EDITME    (revision 319)
+++ src/EDITME    (working copy)
@@ -523,6 +523,20 @@



 #------------------------------------------------------------------------------
+# Support for authentication via the Cyrus SASL saslauthd daemon is available.
+# The Exim support, which is intented for use in conjunction with the SMTP AUTH
+# facilities, is included only when requested by setting the following
+# parameter to the location of the saslauthd daemon's socket directory.
+#
+# There is no need to install all of SASL on your system. You just need to run
+# ./configure --with-saslauthd, cd to the saslauthd directory with sources, make
+# and make install. You must create the socket directory (default /var/saslauthd)
+# and chown it to exim's user and group. Once you have installed saslauthd, you
+# should arrange for it to be started by root at boot time.
+
+# CYRUS_SASLAUTHD_SOCKET=/var/saslauthd/saslauthd
+
+#------------------------------------------------------------------------------
 # TCP wrappers: If you want to use tcpwrappers from within Exim, uncomment
 # this setting. See the manual section entitled "Use of tcpwrappers" in the
 # chapter on building and installing Exim.
Index: src/expand.c
===================================================================
--- src/expand.c    (revision 319)
+++ src/expand.c    (working copy)
@@ -1271,14 +1271,14 @@
         || Ustrcmp(name, "pam") == 0
         #endif  /* SUPPORT_PAM */
         #ifdef RADIUS_CONFIG_FILE
-    || Ustrcmp(name, "radius") == 0
-    #endif    /* RADIUS_CONFIG_FILE */
+        || Ustrcmp(name, "radius") == 0
+        #endif    /* RADIUS_CONFIG_FILE */
         #ifdef LOOKUP_LDAP
         || Ustrcmp(name, "ldapauth") == 0
         #endif
         #ifdef CYRUS_PWCHECK_SOCKET
-    || Ustrcmp(name, "pwcheck") == 0
-    #endif
+        || Ustrcmp(name, "pwcheck") == 0
+        #endif
         )
   {
   uschar *sub;
@@ -1330,7 +1330,7 @@


       #ifdef CYRUS_PWCHECK_SOCKET
       if (name[0] == 'p')
-     rc = auth_call_pwcheck(sub, &expand_string_message);
+        rc = auth_call_pwcheck(sub, &expand_string_message);
       #endif  /* CYRUS_PWCHECK_SOCKET */


       if (rc == ERROR || rc == DEFER) return NULL;
@@ -1340,6 +1340,57 @@
   return s;
   }


+#ifdef CYRUS_SASLAUTHD_SOCKET
+/* saslauthd {{username}{password}[{service}[{realm}]]}  */
+else if ( Ustrcmp(name, "saslauthd") == 0)
+  {
+  int i = 0;
+  int sub_number = 0;
+  int sub_start_idx = 0;
+  const uschar *rerror;
+  uschar *sub[4];
+  int inside_curly = 0;
+  int esl;
+
+  sub[2] = NULL; sub[3] = NULL;
+
+  while (isspace(*s)) s++;
+  if (*s != '{')
+    goto COND_FAILED_CURLY_START;
+  s++;
+  for (i = 0; i < 4; i++)
+    {
+    while (isspace(*s)) s++;
+    if (*s != '{')
+      {
+      if (i == 0) goto COND_FAILED_CURLY_START;
+      if (i > 1) break;
+      expand_string_message = string_sprintf("missing 2nd string in {} "
+        "after \"%s\"", name);
+      return NULL;
+      }
+    sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+    if (sub[i] == NULL) return NULL;
+    if (*s++ != '}') goto COND_FAILED_CURLY_END;
+
+    }
+  while (isspace(*s)) s++;
+  if (*s != '}')
+    goto COND_FAILED_CURLY_END;
+  s++;
+
+  if (yield != NULL)
+  {
+    int rc;
+    rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3], &expand_string_message);
+    if (rc == ERROR || rc == DEFER) return NULL;
+    *yield = (rc == OK) == testfor;
+  }
+  return s;
+}
+
+#endif /* CYRUS_SASLAUTHD_SOCKET */
+
 /* eq:   tests for string equality
 match:   does a regular expression match and sets up the numerical
            variables if it succeeds
Index: src/config.h.defaults
===================================================================
--- src/config.h.defaults    (revision 319)
+++ src/config.h.defaults    (working copy)
@@ -23,6 +23,7 @@
 #define CONFIGURE_FILE_USE_EUID
 #define CONFIGURE_FILE_USE_NODE
 #define CYRUS_PWCHECK_SOCKET
+#define CYRUS_SASLAUTHD_SOCKET


 #define DEFAULT_CRYPT              crypt
 #define DELIVER_IN_BUFFER_SIZE     8192
--