Commit | Line | Data |
---|---|---|
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 | |
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 | |
e1d15f5e JH |
19 | to stop even pickier compilers complaining about infinite loops. |
20 | Then use a mutually-recursive pair as gcc is just getting stupid. */ | |
0756eb3c PH |
21 | |
22 | #ifndef SUPPORT_PAM | |
e1d15f5e JH |
23 | static void dummy(int x); |
24 | static void dummy2(int x) { dummy(x-1); } | |
25 | static 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 | |
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; | |
93a6fce2 | 39 | static const uschar *pam_args; |
0756eb3c PH |
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 | { | |
0756eb3c PH |
69 | int sep = 0; |
70 | struct pam_response *reply; | |
71 | ||
72 | if (pam_arg_ended) return PAM_CONV_ERR; | |
73 | ||
f3ebb786 | 74 | reply = store_get(sizeof(struct pam_response) * num_msg, FALSE); |
0756eb3c | 75 | |
d7978c0f | 76 | for (int i = 0; i < num_msg; i++) |
0756eb3c PH |
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 | } | |
f3ebb786 | 89 | reply[i].resp = CS string_copy_perm(arg, FALSE); /* PAM frees resp */ |
0756eb3c PH |
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... */ | |
0756eb3c PH |
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 | |
93a6fce2 | 128 | auth_call_pam(const uschar *s, uschar **errptr) |
0756eb3c PH |
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 | ||
5903c6ff | 188 | *errptr = US pam_strerror(pamh, pam_error); |
0756eb3c PH |
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 */ |