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