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