pam dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 7 Sep 2024 17:52:15 +0000 (18:52 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 7 Sep 2024 18:03:06 +0000 (19:03 +0100)
13 files changed:
doc/doc-txt/NewStuff
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/src/EDITME
src/src/auths/call_pam.c [deleted file]
src/src/exim.h
src/src/expand.c
src/src/functions.h
src/src/miscmods/Makefile
src/src/miscmods/pam.c [new file with mode: 0644]
src/src/miscmods/pam_api.h [new file with mode: 0644]
src/src/miscmods/radius_api.h [new file with mode: 0644]

index fe94e46c711aa64f4a82333d8c693c45db71e575..935efb59a1d2b4876c98b2fdaf9b20a8f687f574 100644 (file)
@@ -14,7 +14,7 @@ Version 4.98
 
  3. Events smtp:fail:protocol and smtp:fail:syntax
 
- 4. JSON and LDAP lookup support, RADIUS, SPF, DKIM, DMARC and ARC support,
+ 4. JSON and LDAP lookup support, PAM, RADIUS, SPF, DKIM, DMARC and ARC support,
     all the router and authenticator drivers, and all the transport drivers
     except smtp, can now be built as loadable modules
 
index ddd7cf5ac56b349cf05c4c661954fc146f5f951c..22b56aae5a1363bb359190c60605db81054afbac 100644 (file)
@@ -509,7 +509,7 @@ OBJ_LOOKUPS = lf_quote.o lf_check_file.o lf_sqlperform.o
 OBJ_ROUTERS = rf_change_domain.o rf_expand_data.o rf_get_errors_address.o \
        rf_get_munge_headers.o rf_get_transport.o rf_get_ugid.o \
        rf_lookup_hostlist.o rf_queue_add.o rf_self_action.o rf_set_ugid.o
-OBJ_AUTHS = call_pam.o call_pwcheck.o check_serv_cond.o \
+OBJ_AUTHS = call_pwcheck.o check_serv_cond.o \
        get_data.o get_no64_data.o pwcheck.o
 
 OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
@@ -968,9 +968,6 @@ rf_set_ugid.o:           routers/rf_set_ugid.c
 auth-spa.o:         auths/auth-spa.c
        @echo "$(CC) $<"
        $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $<
-call_pam.o:         auths/call_pam.c
-       @echo "$(CC) $<"
-       $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $<
 call_pwcheck.o:     auths/call_pwcheck.c auths/pwcheck.h
        @echo "$(CC) $<"
        $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $<
index 96a6cce22cdffbe9f7595a8cda6d9afec5194130..4fc917dc9c741963e785abba8e30b94e3d3636b5 100755 (executable)
@@ -309,7 +309,7 @@ done <<-END
  routers    ROUTER     ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
  transports TRANSPORT  APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
  auths     AUTH        CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
- miscmods   SUPPORT    ARC _DKIM DMARC RADIUS SPF
+ miscmods   SUPPORT    ARC _DKIM DMARC PAM RADIUS SPF
 END
 
 # See if there is a definition of EXIM_PERL in what we have built so far.
index 1b272a5c9afd16d2b1a97b41f4152f2cc1bdf733..a3a3131a217013636cc01da457750817ae29543d 100755 (executable)
@@ -102,6 +102,7 @@ for f in dummy.c \
        pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \
        pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \
        dmarc.c dmarc.h dmarc_api.h \
+       pam.c pam_api.h \
        radius.c radius_api.h \
        spf.c spf.h spf_api.h
 do
index 9d458842affaa9143acc95dbcbab8ce55ed638d8..c12d74c35ad4e237203175215e0999d4e376e3f2 100644 (file)
@@ -1089,6 +1089,8 @@ ZCAT_COMMAND=/usr/bin/zcat
 # distributions (see http://ftp.kernel.org/pub/linux/libs/pam/). The Exim
 # support, which is intended for use in conjunction with the SMTP AUTH
 # facilities, is included only when requested by the following setting:
+#
+# For a dynamic module build add SUPPORT_PAM=2 and SUPPORT_PAM_LIBS=-lpam
 
 # SUPPORT_PAM=yes
 
@@ -1146,6 +1148,9 @@ ZCAT_COMMAND=/usr/bin/zcat
 # which is intended for use in conjunction with the SMTP AUTH facilities,
 # is included only when requested by setting the following parameter to the
 # location of your Radius configuration file:
+#
+# For a dynamic module build add SUPPORT_RADIUS=2 and (if needed)
+# SUPPORT_RADIUS_LIBS=-l<foo>
 
 # RADIUS_CONFIG_FILE=/etc/radiusclient/radiusclient.conf
 # RADIUS_CONFIG_FILE=/etc/radius.conf
diff --git a/src/src/auths/call_pam.c b/src/src/auths/call_pam.c
deleted file mode 100644 (file)
index 3ff1571..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#include "../exim.h"
-
-/* This module contains functions that call the PAM authentication mechanism
-defined by Sun for Solaris and also available for Linux and other OS.
-
-We can't just compile this code and allow the library mechanism to omit the
-functions if they are not wanted, because we need to have the PAM headers
-available for compiling. Therefore, compile these functions only if SUPPORT_PAM
-is defined. However, some compilers don't like compiling empty modules, so keep
-them happy with a dummy when skipping the rest. Make it reference itself to
-stop picky compilers complaining that it is unused, and put in a dummy argument
-to stop even pickier compilers complaining about infinite loops.
-Then use a mutually-recursive pair as gcc is just getting stupid. */
-
-#ifndef SUPPORT_PAM
-static void dummy(int x);
-static void dummy2(int x) { dummy(x-1); }
-static void dummy(int x) { dummy2(x-1); }
-#else  /* SUPPORT_PAM */
-
-#ifdef PAM_H_IN_PAM
-#include <pam/pam_appl.h>
-#else
-#include <security/pam_appl.h>
-#endif
-
-/* According to the specification, it should be possible to have an application
-data pointer passed to the conversation function. However, I was unable to get
-this to work on Solaris 2.6, so static variables are used instead. */
-
-static int pam_conv_had_error;
-static const uschar *pam_args;
-static BOOL pam_arg_ended;
-
-
-
-/*************************************************
-*           PAM conversation function            *
-*************************************************/
-
-/* This function is passed to the PAM authentication function, and it calls it
-back when it wants data from the client. The string list is in pam_args. When
-we reach the end, we pass back an empty string once. If this function is called
-again, it will give an error response. This is protection against something
-crazy happening.
-
-Arguments:
-  num_msg        number of messages associated with the call
-  msg            points to an array of length num_msg of pam_message structures
-  resp           set to point to the response block, which has to be got by
-                   this function
-  appdata_ptr    the application data pointer - not used because in Solaris
-                   2.6 it always arrived in pam_converse() as NULL
-
-Returns:         a PAM return code
-*/
-
-static int
-pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
-  struct pam_response **resp, void *appdata_ptr)
-{
-int sep = 0;
-struct pam_response *reply;
-
-/* It seems that PAM frees reply[] */
-
-if (  pam_arg_ended
-   || !(reply = malloc(sizeof(struct pam_response) * num_msg)))
-  return PAM_CONV_ERR;
-
-for (int i = 0; i < num_msg; i++)
-  {
-  uschar *arg;
-  switch (msg[i]->msg_style)
-    {
-    case PAM_PROMPT_ECHO_ON:
-    case PAM_PROMPT_ECHO_OFF:
-      if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0)))
-       {
-       arg = US"";
-       pam_arg_ended = TRUE;
-       }
-      reply[i].resp = strdup(CCS arg); /* Use libc malloc, PAM frees resp directly*/
-      reply[i].resp_retcode = PAM_SUCCESS;
-      break;
-
-    case PAM_TEXT_INFO:    /* Just acknowledge messages */
-    case PAM_ERROR_MSG:
-      reply[i].resp_retcode = PAM_SUCCESS;
-      reply[i].resp = NULL;
-      break;
-
-    default:  /* Must be an error of some sort... */
-      free(reply);
-      pam_conv_had_error = TRUE;
-      return PAM_CONV_ERR;
-    }
-  }
-
-*resp = reply;
-return PAM_SUCCESS;
-}
-
-
-
-/*************************************************
-*              Perform PAM authentication        *
-*************************************************/
-
-/* This function calls the PAM authentication mechanism, passing over one or
-more data strings.
-
-Arguments:
-  s        a colon-separated list of strings
-  errptr   where to point an error message
-
-Returns:   OK if authentication succeeded
-           FAIL if authentication failed
-           ERROR some other error condition
-*/
-
-int
-auth_call_pam(const uschar *s, uschar **errptr)
-{
-pam_handle_t *pamh = NULL;
-struct pam_conv pamc;
-int pam_error;
-int sep = 0;
-uschar *user;
-
-/* Set up the input data structure: the address of the conversation function,
-and a pointer to application data, which we don't use because I couldn't get it
-to work under Solaris 2.6 - it always arrived in pam_converse() as NULL. */
-
-pamc.conv = pam_converse;
-pamc.appdata_ptr = NULL;
-
-/* Initialize the static data - the current input data, the error flag, and the
-flag for data end. */
-
-pam_args = s;
-pam_conv_had_error = FALSE;
-pam_arg_ended = FALSE;
-
-/* The first string in the list is the user. If this is an empty string, we
-fail. PAM doesn't support authentication with an empty user (it prompts for it,
-causing a potential mis-interpretation). */
-
-user = string_nextinlist(&pam_args, &sep, NULL, 0);
-if (user == NULL || user[0] == 0) return FAIL;
-
-/* Start off PAM interaction */
-
-DEBUG(D_auth)
-  debug_printf("Running PAM authentication for user \"%s\"\n", user);
-
-pam_error = pam_start ("exim", CS user, &pamc, &pamh);
-
-/* Do the authentication - the pam_authenticate() will call pam_converse() to
-get the data it wants. After successful authentication we call pam_acct_mgmt()
-to apply any other restrictions (e.g. only some times of day). */
-
-if (pam_error == PAM_SUCCESS)
-  {
-  pam_error = pam_authenticate (pamh, PAM_SILENT);
-  if (pam_error == PAM_SUCCESS && !pam_conv_had_error)
-    pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
-  }
-
-/* Finish the PAM interaction - this causes it to clean up store etc. Unclear
-what should be passed as the second argument. */
-
-pam_end(pamh, PAM_SUCCESS);
-
-/* Sort out the return code. If not success, set the error message. */
-
-if (pam_error == PAM_SUCCESS)
-  {
-  DEBUG(D_auth) debug_printf("PAM success\n");
-  return OK;
-  }
-
-*errptr = US pam_strerror(pamh, pam_error);
-DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
-
-if (pam_error == PAM_USER_UNKNOWN ||
-    pam_error == PAM_AUTH_ERR ||
-    pam_error == PAM_ACCT_EXPIRED)
-  return FAIL;
-
-return ERROR;
-}
-
-#endif  /* SUPPORT_PAM */
-
-/* End of call_pam.c */
index 550f1a7bdec909da03d8ad68b514756fe05b24d6..a3b7112a6f988a7361fc948b384527c4e2c26e96 100644 (file)
@@ -561,6 +561,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #ifdef RADIUS_CONFIG_FILE
 # include "miscmods/radius_api.h"
 #endif
+#ifdef SUPPORT_PAM
+# include "miscmods/pam_api.h"
+#endif
 
 /* The following stuff must follow the inclusion of config.h because it
 requires various things that are set therein. */
index cdfe93cdc6b3fce2d2d493450a697244e82bfa7e..d9e71897e95d0fefef2e4ab91a9118336bb96356 100644 (file)
@@ -2767,12 +2767,19 @@ switch(cond_type = identify_operator(&s, &opname))
     /* Various authentication tests - all optionally compiled */
 
     case ECOND_PAM:
-    #ifdef SUPPORT_PAM
-    rc = auth_call_pam(sub[0], &expand_string_message);
-    goto END_AUTH;
-    #else
-    goto COND_FAILED_NOT_COMPILED;
-    #endif  /* SUPPORT_PAM */
+#ifdef SUPPORT_PAM
+      {
+      const misc_module_info * mi = misc_mod_find(US"pam", NULL);
+      typedef int (*fn_t)(const uschar *, uschar **);
+      if (!mi)
+       goto COND_FAILED_NOT_COMPILED;
+      rc = (((fn_t *) mi->functions)[PAM_AUTH_CALL])
+                                         (sub[0], &expand_string_message);
+      goto END_AUTH;
+      }
+#else
+      goto COND_FAILED_NOT_COMPILED;
+#endif  /* SUPPORT_PAM */
 
     case ECOND_RADIUS:
 #ifdef RADIUS_CONFIG_FILE
index 493b2287e4ea8813527224aff7f3b95bd7a821bf..cb470bcb367c06f8c2994f495a753c47c37b314a 100644 (file)
@@ -111,7 +111,6 @@ extern void    add_driver_info(driver_info **, const driver_info *, size_t);
 
 
 extern void    assert_no_variables(void *, int, const char *, int);
-extern int     auth_call_pam(const uschar *, uschar **);
 extern int     auth_call_pwcheck(uschar *, uschar **);
 extern int     auth_call_saslauthd(const uschar *, const uschar *,
                 const uschar *, const uschar *, uschar **);
index a15fd9b380a058dffc31d2e6f043134013115b80..8f53088ff0f639fe2c75a9eea7334bc11a2327fe 100644 (file)
@@ -37,6 +37,8 @@ dkim.o  dkim.so:      $(HDRS) dkim.h dkim.c dkim_transport.c \
                        signing.h signing.c
 dmarc.o dmarc.so:      $(HDRS) pdkim.h dmarc.h dmarc.c
 dummy.o:               dummy.c
+pam.o   pam.so:                $(HDRS) pam.c
+radius.o radius.so:    $(HDRS) radius.c
 spf.o   spf.so:                $(HDRS) spf.h spf.c
 
 dkim.o:
diff --git a/src/src/miscmods/pam.c b/src/src/miscmods/pam.c
new file mode 100644 (file)
index 0000000..1f1b2e2
--- /dev/null
@@ -0,0 +1,225 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "../exim.h"
+
+/* This module contains functions that call the PAM authentication mechanism
+defined by Sun for Solaris and also available for Linux and other OS.
+
+We can't just compile this code and allow the library mechanism to omit the
+functions if they are not wanted, because we need to have the PAM headers
+available for compiling. Therefore, compile these functions only if SUPPORT_PAM
+is defined. However, some compilers don't like compiling empty modules, so keep
+them happy with a dummy when skipping the rest. Make it reference itself to
+stop picky compilers complaining that it is unused, and put in a dummy argument
+to stop even pickier compilers complaining about infinite loops.
+Then use a mutually-recursive pair as gcc is just getting stupid. */
+
+#ifndef SUPPORT_PAM
+static void dummy(int x);
+static void dummy2(int x) { dummy(x-1); }
+static void dummy(int x) { dummy2(x-1); }
+#else  /* SUPPORT_PAM */
+
+#ifdef PAM_H_IN_PAM
+# include <pam/pam_appl.h>
+#else
+# include <security/pam_appl.h>
+#endif
+
+/* According to the specification, it should be possible to have an application
+data pointer passed to the conversation function. However, I was unable to get
+this to work on Solaris 2.6, so static variables are used instead. */
+
+static int pam_conv_had_error;
+static const uschar *pam_args;
+static BOOL pam_arg_ended;
+
+
+
+/*************************************************
+*           PAM conversation function            *
+*************************************************/
+
+/* This function is passed to the PAM authentication function, and it calls it
+back when it wants data from the client. The string list is in pam_args. When
+we reach the end, we pass back an empty string once. If this function is called
+again, it will give an error response. This is protection against something
+crazy happening.
+
+Arguments:
+  num_msg        number of messages associated with the call
+  msg            points to an array of length num_msg of pam_message structures
+  resp           set to point to the response block, which has to be got by
+                   this function
+  appdata_ptr    the application data pointer - not used because in Solaris
+                   2.6 it always arrived in pam_converse() as NULL
+
+Returns:         a PAM return code
+*/
+
+static int
+pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
+  struct pam_response **resp, void *appdata_ptr)
+{
+int sep = 0;
+struct pam_response *reply;
+
+/* It seems that PAM frees reply[] */
+
+if (  pam_arg_ended
+   || !(reply = malloc(sizeof(struct pam_response) * num_msg)))
+  return PAM_CONV_ERR;
+
+for (int i = 0; i < num_msg; i++)
+  {
+  uschar *arg;
+  switch (msg[i]->msg_style)
+    {
+    case PAM_PROMPT_ECHO_ON:
+    case PAM_PROMPT_ECHO_OFF:
+      if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0)))
+       {
+       arg = US"";
+       pam_arg_ended = TRUE;
+       }
+      reply[i].resp = strdup(CCS arg); /* Use libc malloc, PAM frees resp directly*/
+      reply[i].resp_retcode = PAM_SUCCESS;
+      break;
+
+    case PAM_TEXT_INFO:    /* Just acknowledge messages */
+    case PAM_ERROR_MSG:
+      reply[i].resp_retcode = PAM_SUCCESS;
+      reply[i].resp = NULL;
+      break;
+
+    default:  /* Must be an error of some sort... */
+      free(reply);
+      pam_conv_had_error = TRUE;
+      return PAM_CONV_ERR;
+    }
+  }
+
+*resp = reply;
+return PAM_SUCCESS;
+}
+
+
+
+/*************************************************
+*              Perform PAM authentication        *
+*************************************************/
+
+/* This function calls the PAM authentication mechanism, passing over one or
+more data strings.
+
+Arguments:
+  s        a colon-separated list of strings
+  errptr   where to point an error message
+
+Returns:   OK if authentication succeeded
+           FAIL if authentication failed
+           ERROR some other error condition
+*/
+
+static int
+auth_call_pam(const uschar *s, uschar **errptr)
+{
+pam_handle_t *pamh = NULL;
+struct pam_conv pamc;
+int pam_error;
+int sep = 0;
+uschar *user;
+
+/* Set up the input data structure: the address of the conversation function,
+and a pointer to application data, which we don't use because I couldn't get it
+to work under Solaris 2.6 - it always arrived in pam_converse() as NULL. */
+
+pamc.conv = pam_converse;
+pamc.appdata_ptr = NULL;
+
+/* Initialize the static data - the current input data, the error flag, and the
+flag for data end. */
+
+pam_args = s;
+pam_conv_had_error = FALSE;
+pam_arg_ended = FALSE;
+
+/* The first string in the list is the user. If this is an empty string, we
+fail. PAM doesn't support authentication with an empty user (it prompts for it,
+causing a potential mis-interpretation). */
+
+user = string_nextinlist(&pam_args, &sep, NULL, 0);
+if (user == NULL || user[0] == 0) return FAIL;
+
+/* Start off PAM interaction */
+
+DEBUG(D_auth)
+  debug_printf("Running PAM authentication for user \"%s\"\n", user);
+
+pam_error = pam_start ("exim", CS user, &pamc, &pamh);
+
+/* Do the authentication - the pam_authenticate() will call pam_converse() to
+get the data it wants. After successful authentication we call pam_acct_mgmt()
+to apply any other restrictions (e.g. only some times of day). */
+
+if (pam_error == PAM_SUCCESS)
+  {
+  pam_error = pam_authenticate (pamh, PAM_SILENT);
+  if (pam_error == PAM_SUCCESS && !pam_conv_had_error)
+    pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
+  }
+
+/* Finish the PAM interaction - this causes it to clean up store etc. Unclear
+what should be passed as the second argument. */
+
+pam_end(pamh, PAM_SUCCESS);
+
+/* Sort out the return code. If not success, set the error message. */
+
+if (pam_error == PAM_SUCCESS)
+  {
+  DEBUG(D_auth) debug_printf("PAM success\n");
+  return OK;
+  }
+
+*errptr = US pam_strerror(pamh, pam_error);
+DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
+
+if (pam_error == PAM_USER_UNKNOWN ||
+    pam_error == PAM_AUTH_ERR ||
+    pam_error == PAM_ACCT_EXPIRED)
+  return FAIL;
+
+return ERROR;
+}
+
+
+
+/******************************************************************************/
+/* Module API */
+
+static void * pam_functions[] = {
+  [RADIUS_AUTH_CALL] = auth_call_pam,
+};
+
+misc_module_info rad_module_info =
+{
+  .name =              US"pam",
+# ifdef DYNLOOKUP
+  .dyn_magic =         MISC_MODULE_MAGIC,
+# endif
+
+  .functions =         pam_functions,
+  .functions_count =   nelem(pam_functions),
+};
+
+#endif  /* SUPPORT_PAM */
+
+/* End of call_pam.c */
diff --git a/src/src/miscmods/pam_api.h b/src/src/miscmods/pam_api.h
new file mode 100644 (file)
index 0000000..d83f27a
--- /dev/null
@@ -0,0 +1,14 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2024 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the pam module */
+
+
+/* Function table entry numbers */
+
+#define        PAM_AUTH_CALL           0
diff --git a/src/src/miscmods/radius_api.h b/src/src/miscmods/radius_api.h
new file mode 100644 (file)
index 0000000..361a01c
--- /dev/null
@@ -0,0 +1,14 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2024 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the radius module */
+
+
+/* Function table entry numbers */
+
+#define        RADIUS_AUTH_CALL                0