1f1b2e2eb614508e3150bdbca7d5f1d67e301145
[exim.git] / src / src / miscmods / pam.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 #include "../exim.h"
11
12 /* This module contains functions that call the PAM authentication mechanism
13 defined by Sun for Solaris and also available for Linux and other OS.
14
15 We can't just compile this code and allow the library mechanism to omit the
16 functions if they are not wanted, because we need to have the PAM headers
17 available for compiling. Therefore, compile these functions only if SUPPORT_PAM
18 is defined. However, some compilers don't like compiling empty modules, so keep
19 them happy with a dummy when skipping the rest. Make it reference itself to
20 stop picky compilers complaining that it is unused, and put in a dummy argument
21 to stop even pickier compilers complaining about infinite loops.
22 Then use a mutually-recursive pair as gcc is just getting stupid. */
23
24 #ifndef SUPPORT_PAM
25 static void dummy(int x);
26 static void dummy2(int x) { dummy(x-1); }
27 static void dummy(int x) { dummy2(x-1); }
28 #else  /* SUPPORT_PAM */
29
30 #ifdef PAM_H_IN_PAM
31 # include <pam/pam_appl.h>
32 #else
33 # include <security/pam_appl.h>
34 #endif
35
36 /* According to the specification, it should be possible to have an application
37 data pointer passed to the conversation function. However, I was unable to get
38 this to work on Solaris 2.6, so static variables are used instead. */
39
40 static int pam_conv_had_error;
41 static const uschar *pam_args;
42 static BOOL pam_arg_ended;
43
44
45
46 /*************************************************
47 *           PAM conversation function            *
48 *************************************************/
49
50 /* This function is passed to the PAM authentication function, and it calls it
51 back when it wants data from the client. The string list is in pam_args. When
52 we reach the end, we pass back an empty string once. If this function is called
53 again, it will give an error response. This is protection against something
54 crazy happening.
55
56 Arguments:
57   num_msg        number of messages associated with the call
58   msg            points to an array of length num_msg of pam_message structures
59   resp           set to point to the response block, which has to be got by
60                    this function
61   appdata_ptr    the application data pointer - not used because in Solaris
62                    2.6 it always arrived in pam_converse() as NULL
63
64 Returns:         a PAM return code
65 */
66
67 static int
68 pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
69   struct pam_response **resp, void *appdata_ptr)
70 {
71 int sep = 0;
72 struct pam_response *reply;
73
74 /* It seems that PAM frees reply[] */
75
76 if (  pam_arg_ended
77    || !(reply = malloc(sizeof(struct pam_response) * num_msg)))
78   return PAM_CONV_ERR;
79
80 for (int i = 0; i < num_msg; i++)
81   {
82   uschar *arg;
83   switch (msg[i]->msg_style)
84     {
85     case PAM_PROMPT_ECHO_ON:
86     case PAM_PROMPT_ECHO_OFF:
87       if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0)))
88         {
89         arg = US"";
90         pam_arg_ended = TRUE;
91         }
92       reply[i].resp = strdup(CCS arg); /* Use libc malloc, PAM frees resp directly*/
93       reply[i].resp_retcode = PAM_SUCCESS;
94       break;
95
96     case PAM_TEXT_INFO:    /* Just acknowledge messages */
97     case PAM_ERROR_MSG:
98       reply[i].resp_retcode = PAM_SUCCESS;
99       reply[i].resp = NULL;
100       break;
101
102     default:  /* Must be an error of some sort... */
103       free(reply);
104       pam_conv_had_error = TRUE;
105       return PAM_CONV_ERR;
106     }
107   }
108
109 *resp = reply;
110 return PAM_SUCCESS;
111 }
112
113
114
115 /*************************************************
116 *              Perform PAM authentication        *
117 *************************************************/
118
119 /* This function calls the PAM authentication mechanism, passing over one or
120 more data strings.
121
122 Arguments:
123   s        a colon-separated list of strings
124   errptr   where to point an error message
125
126 Returns:   OK if authentication succeeded
127            FAIL if authentication failed
128            ERROR some other error condition
129 */
130
131 static int
132 auth_call_pam(const uschar *s, uschar **errptr)
133 {
134 pam_handle_t *pamh = NULL;
135 struct pam_conv pamc;
136 int pam_error;
137 int sep = 0;
138 uschar *user;
139
140 /* Set up the input data structure: the address of the conversation function,
141 and a pointer to application data, which we don't use because I couldn't get it
142 to work under Solaris 2.6 - it always arrived in pam_converse() as NULL. */
143
144 pamc.conv = pam_converse;
145 pamc.appdata_ptr = NULL;
146
147 /* Initialize the static data - the current input data, the error flag, and the
148 flag for data end. */
149
150 pam_args = s;
151 pam_conv_had_error = FALSE;
152 pam_arg_ended = FALSE;
153
154 /* The first string in the list is the user. If this is an empty string, we
155 fail. PAM doesn't support authentication with an empty user (it prompts for it,
156 causing a potential mis-interpretation). */
157
158 user = string_nextinlist(&pam_args, &sep, NULL, 0);
159 if (user == NULL || user[0] == 0) return FAIL;
160
161 /* Start off PAM interaction */
162
163 DEBUG(D_auth)
164   debug_printf("Running PAM authentication for user \"%s\"\n", user);
165
166 pam_error = pam_start ("exim", CS user, &pamc, &pamh);
167
168 /* Do the authentication - the pam_authenticate() will call pam_converse() to
169 get the data it wants. After successful authentication we call pam_acct_mgmt()
170 to apply any other restrictions (e.g. only some times of day). */
171
172 if (pam_error == PAM_SUCCESS)
173   {
174   pam_error = pam_authenticate (pamh, PAM_SILENT);
175   if (pam_error == PAM_SUCCESS && !pam_conv_had_error)
176     pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
177   }
178
179 /* Finish the PAM interaction - this causes it to clean up store etc. Unclear
180 what should be passed as the second argument. */
181
182 pam_end(pamh, PAM_SUCCESS);
183
184 /* Sort out the return code. If not success, set the error message. */
185
186 if (pam_error == PAM_SUCCESS)
187   {
188   DEBUG(D_auth) debug_printf("PAM success\n");
189   return OK;
190   }
191
192 *errptr = US pam_strerror(pamh, pam_error);
193 DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
194
195 if (pam_error == PAM_USER_UNKNOWN ||
196     pam_error == PAM_AUTH_ERR ||
197     pam_error == PAM_ACCT_EXPIRED)
198   return FAIL;
199
200 return ERROR;
201 }
202
203
204
205 /******************************************************************************/
206 /* Module API */
207
208 static void * pam_functions[] = {
209   [RADIUS_AUTH_CALL] =  auth_call_pam,
210 };
211
212 misc_module_info rad_module_info =
213 {
214   .name =               US"pam",
215 # ifdef DYNLOOKUP
216   .dyn_magic =          MISC_MODULE_MAGIC,
217 # endif
218
219   .functions =          pam_functions,
220   .functions_count =    nelem(pam_functions),
221 };
222
223 #endif  /* SUPPORT_PAM */
224
225 /* End of call_pam.c */