+/*************************************************
+* 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 */