Commit | Line | Data |
---|---|---|
0756eb3c PH |
1 | /************************************************* |
2 | * Exim - an Internet mail transport agent * | |
3 | *************************************************/ | |
4 | ||
3386088d | 5 | /* Copyright (c) University of Cambridge 1995 - 2015 */ |
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 | { | |
69 | int i; | |
70 | int sep = 0; | |
71 | struct pam_response *reply; | |
72 | ||
73 | if (pam_arg_ended) return PAM_CONV_ERR; | |
74 | ||
75 | reply = malloc(sizeof(struct pam_response) * num_msg); | |
76 | ||
77 | if (reply == NULL) return PAM_CONV_ERR; | |
78 | ||
79 | for (i = 0; i < num_msg; i++) | |
80 | { | |
81 | uschar *arg; | |
82 | switch (msg[i]->msg_style) | |
83 | { | |
84 | case PAM_PROMPT_ECHO_ON: | |
85 | case PAM_PROMPT_ECHO_OFF: | |
86 | arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size); | |
87 | if (arg == NULL) | |
88 | { | |
89 | arg = US""; | |
90 | pam_arg_ended = TRUE; | |
91 | } | |
92 | reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */ | |
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 | int | |
93a6fce2 | 132 | auth_call_pam(const uschar *s, uschar **errptr) |
0756eb3c PH |
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, big_buffer, big_buffer_size); | |
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 = (uschar *)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 | #endif /* SUPPORT_PAM */ | |
204 | ||
205 | /* End of call_pam.c */ |