1 /*
2 Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
3
4 You're allowed to do whatever you like with this software (including
5 re-distribution in source and/or binary form, with or without
6 modification), provided that credit is given where it is due and any
7 modified versions are marked as such. There's absolutely no warranty.
8 */
9
10 #include "auth-common.h"
11 #include "passdb.h"
12
13 #ifdef PASSDB_PAM
14
15 #include "lib-signals.h"
16 #include "str.h"
17 #include "net.h"
18 #include "safe-memset.h"
19 #include "auth-cache.h"
20
21 #include <sys/stat.h>
22
23 #ifdef HAVE_SECURITY_PAM_APPL_H
24 # include <security/pam_appl.h>
25 #elif defined(HAVE_PAM_PAM_APPL_H)
26 # include <pam/pam_appl.h>
27 #endif
28
29 #if defined(sun) || defined(__sun__) || defined(_HPUX_SOURCE)
30 # define pam_const
31 #else
32 # define pam_const const
33 #endif
34
35 typedef pam_const void *pam_item_t;
36
37 #define PASSDB_PAM_DEFAULT_MAX_REQUESTS 100
38
39 struct pam_passdb_module {
40 struct passdb_module module;
41
42 const char *service_name, *pam_cache_key;
43 unsigned int requests_left;
44
45 bool pam_setcred:1;
46 bool pam_session:1;
47 bool failure_show_msg:1;
48 };
49
50 struct pam_conv_context {
51 struct auth_request *request;
52 const char *pass;
53 const char *failure_msg;
54 };
55
56 static int
pam_userpass_conv(int num_msg,pam_const struct pam_message ** msg,struct pam_response ** resp_r,void * appdata_ptr)57 pam_userpass_conv(int num_msg, pam_const struct pam_message **msg,
58 struct pam_response **resp_r, void *appdata_ptr)
59 {
60 /* @UNSAFE */
61 struct pam_conv_context *ctx = appdata_ptr;
62 struct passdb_module *_passdb = ctx->request->passdb->passdb;
63 struct pam_passdb_module *passdb = (struct pam_passdb_module *)_passdb;
64 struct pam_response *resp;
65 char *string;
66 int i;
67
68 *resp_r = NULL;
69
70 resp = calloc(num_msg, sizeof(struct pam_response));
71 if (resp == NULL)
72 i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
73
74 for (i = 0; i < num_msg; i++) {
75 e_debug(authdb_event(ctx->request),
76 "#%d/%d style=%d msg=%s", i+1, num_msg,
77 msg[i]->msg_style,
78 msg[i]->msg != NULL ? msg[i]->msg : "");
79 switch (msg[i]->msg_style) {
80 case PAM_PROMPT_ECHO_ON:
81 /* Assume we're asking for user. We might not ever
82 get here because PAM already knows the user. */
83 string = strdup(ctx->request->fields.user);
84 if (string == NULL)
85 i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
86 break;
87 case PAM_PROMPT_ECHO_OFF:
88 /* Assume we're asking for password */
89 if (passdb->failure_show_msg)
90 ctx->failure_msg = t_strdup(msg[i]->msg);
91 string = strdup(ctx->pass);
92 if (string == NULL)
93 i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
94 break;
95 case PAM_ERROR_MSG:
96 case PAM_TEXT_INFO:
97 string = NULL;
98 break;
99 default:
100 while (--i >= 0) {
101 if (resp[i].resp != NULL) {
102 safe_memset(resp[i].resp, 0,
103 strlen(resp[i].resp));
104 free(resp[i].resp);
105 }
106 }
107
108 free(resp);
109 return PAM_CONV_ERR;
110 }
111
112 resp[i].resp_retcode = PAM_SUCCESS;
113 resp[i].resp = string;
114 }
115
116 *resp_r = resp;
117 return PAM_SUCCESS;
118 }
119
120 static const char *
pam_get_missing_service_file_path(const char * service ATTR_UNUSED)121 pam_get_missing_service_file_path(const char *service ATTR_UNUSED)
122 {
123 #ifdef SUNPAM
124 /* Uses /etc/pam.conf - we're not going to parse that */
125 return NULL;
126 #else
127 static bool service_checked = FALSE;
128 const char *path;
129 struct stat st;
130
131 if (service_checked) {
132 /* check and complain only once */
133 return NULL;
134 }
135 service_checked = TRUE;
136
137 path = t_strdup_printf("/etc/pam.d/%s", service);
138 if (stat(path, &st) < 0 && errno == ENOENT) {
139 /* looks like it's missing. but before assuming that the system
140 even uses /etc/pam.d, make sure that it exists. */
141 if (stat("/etc/pam.d", &st) == 0)
142 return path;
143 }
144 /* exists or is unknown */
145 return NULL;
146 #endif
147 }
148
try_pam_auth(struct auth_request * request,pam_handle_t * pamh,const char * service)149 static int try_pam_auth(struct auth_request *request, pam_handle_t *pamh,
150 const char *service)
151 {
152 struct passdb_module *_module = request->passdb->passdb;
153 struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
154 const char *path, *str;
155 pam_item_t item;
156 int status;
157
158 if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
159 path = pam_get_missing_service_file_path(service);
160 switch (status) {
161 case PAM_USER_UNKNOWN:
162 str = "unknown user";
163 break;
164 default:
165 str = t_strconcat("pam_authenticate() failed: ",
166 pam_strerror(pamh, status), NULL);
167 break;
168 }
169 if (path != NULL) {
170 /* log this as error, since it probably is */
171 str = t_strdup_printf("%s (%s missing?)", str, path);
172 e_error(authdb_event(request), "%s", str);
173 } else if (status == PAM_AUTH_ERR) {
174 str = t_strconcat(str, " ("AUTH_LOG_MSG_PASSWORD_MISMATCH"?)", NULL);
175 if (request->set->debug_passwords) {
176 str = t_strconcat(str, " (given password: ",
177 request->mech_password,
178 ")", NULL);
179 }
180 e_info(authdb_event(request), "%s", str);
181 } else {
182 if (status == PAM_USER_UNKNOWN)
183 auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
184 else {
185 e_info(authdb_event(request),
186 "%s", str);
187 }
188 }
189 return status;
190 }
191
192 #ifdef HAVE_PAM_SETCRED
193 if (module->pam_setcred) {
194 if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) !=
195 PAM_SUCCESS) {
196 e_error(authdb_event(request),
197 "pam_setcred() failed: %s",
198 pam_strerror(pamh, status));
199 return status;
200 }
201 }
202 #endif
203
204 if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
205 e_error(authdb_event(request),
206 "pam_acct_mgmt() failed: %s",
207 pam_strerror(pamh, status));
208 return status;
209 }
210
211 if (module->pam_session) {
212 if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
213 e_error(authdb_event(request),
214 "pam_open_session() failed: %s",
215 pam_strerror(pamh, status));
216 return status;
217 }
218
219 if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
220 e_error(authdb_event(request),
221 "pam_close_session() failed: %s",
222 pam_strerror(pamh, status));
223 return status;
224 }
225 }
226
227 status = pam_get_item(pamh, PAM_USER, &item);
228 if (status != PAM_SUCCESS) {
229 e_error(authdb_event(request),
230 "pam_get_item(PAM_USER) failed: %s",
231 pam_strerror(pamh, status));
232 return status;
233 }
234 auth_request_set_field(request, "user", item, NULL);
235 return PAM_SUCCESS;
236 }
237
set_pam_items(struct auth_request * request,pam_handle_t * pamh)238 static void set_pam_items(struct auth_request *request, pam_handle_t *pamh)
239 {
240 const char *host;
241
242 /* These shouldn't fail, and we don't really care if they do. */
243 host = net_ip2addr(&request->fields.remote_ip);
244 if (host[0] != '\0')
245 (void)pam_set_item(pamh, PAM_RHOST, host);
246 (void)pam_set_item(pamh, PAM_RUSER, request->fields.user);
247 /* TTY is needed by eg. pam_access module */
248 (void)pam_set_item(pamh, PAM_TTY, "dovecot");
249 }
250
251 static enum passdb_result
pam_verify_plain_call(struct auth_request * request,const char * service,const char * password)252 pam_verify_plain_call(struct auth_request *request, const char *service,
253 const char *password)
254 {
255 pam_handle_t *pamh;
256 struct pam_conv_context ctx;
257 struct pam_conv conv;
258 enum passdb_result result;
259 int status, status2;
260
261 conv.conv = pam_userpass_conv;
262 conv.appdata_ptr = &ctx;
263
264 i_zero(&ctx);
265 ctx.request = request;
266 ctx.pass = password;
267
268 status = pam_start(service, request->fields.user, &conv, &pamh);
269 if (status != PAM_SUCCESS) {
270 e_error(authdb_event(request),
271 "pam_start() failed: %s",
272 pam_strerror(pamh, status));
273 return PASSDB_RESULT_INTERNAL_FAILURE;
274 }
275
276 set_pam_items(request, pamh);
277 status = try_pam_auth(request, pamh, service);
278 if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
279 e_error(authdb_event(request),
280 "pam_end() failed: %s",
281 pam_strerror(pamh, status2));
282 return PASSDB_RESULT_INTERNAL_FAILURE;
283 }
284
285 switch (status) {
286 case PAM_SUCCESS:
287 result = PASSDB_RESULT_OK;
288 break;
289 case PAM_USER_UNKNOWN:
290 result = PASSDB_RESULT_USER_UNKNOWN;
291 break;
292 case PAM_NEW_AUTHTOK_REQD:
293 case PAM_ACCT_EXPIRED:
294 result = PASSDB_RESULT_PASS_EXPIRED;
295 break;
296 default:
297 result = PASSDB_RESULT_PASSWORD_MISMATCH;
298 break;
299 }
300
301 if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) {
302 auth_request_set_field(request, "reason",
303 ctx.failure_msg, NULL);
304 }
305 return result;
306 }
307
308 static void
pam_verify_plain(struct auth_request * request,const char * password,verify_plain_callback_t * callback)309 pam_verify_plain(struct auth_request *request, const char *password,
310 verify_plain_callback_t *callback)
311 {
312 struct passdb_module *_module = request->passdb->passdb;
313 struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
314 enum passdb_result result;
315 const char *service, *error;
316
317 if (module->requests_left > 0) {
318 if (--module->requests_left == 0)
319 worker_restart_request = TRUE;
320 }
321
322 if (t_auth_request_var_expand(module->service_name, request, NULL,
323 &service, &error) <= 0) {
324 e_debug(authdb_event(request),
325 "Failed to expand service %s: %s",
326 module->service_name, error);
327 callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
328 return;
329 }
330
331 e_debug(authdb_event(request),
332 "lookup service=%s", service);
333
334 result = pam_verify_plain_call(request, service, password);
335 callback(result, request);
336 }
337
338 static struct passdb_module *
pam_preinit(pool_t pool,const char * args)339 pam_preinit(pool_t pool, const char *args)
340 {
341 struct pam_passdb_module *module;
342 const char *const *t_args;
343 int i;
344
345 module = p_new(pool, struct pam_passdb_module, 1);
346 module->service_name = "dovecot";
347 /* we're caching the password by using directly the plaintext password
348 given by the auth mechanism */
349 module->module.default_pass_scheme = "PLAIN";
350 module->module.blocking = TRUE;
351 module->requests_left = PASSDB_PAM_DEFAULT_MAX_REQUESTS;
352
353 t_args = t_strsplit_spaces(args, " ");
354 for(i = 0; t_args[i] != NULL; i++) {
355 /* -session for backwards compatibility */
356 if (strcmp(t_args[i], "-session") == 0 ||
357 strcmp(t_args[i], "session=yes") == 0)
358 module->pam_session = TRUE;
359 else if (strcmp(t_args[i], "setcred=yes") == 0)
360 module->pam_setcred = TRUE;
361 else if (str_begins(t_args[i], "cache_key=")) {
362 module->module.default_cache_key =
363 auth_cache_parse_key(pool, t_args[i] + 10);
364 } else if (strcmp(t_args[i], "blocking=yes") == 0) {
365 /* ignore, for backwards compatibility */
366 } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) {
367 module->failure_show_msg = TRUE;
368 } else if (strcmp(t_args[i], "*") == 0) {
369 /* for backwards compatibility */
370 module->service_name = "%Ls";
371 } else if (str_begins(t_args[i], "max_requests=")) {
372 if (str_to_uint(t_args[i] + 13,
373 &module->requests_left) < 0) {
374 i_error("pam: Invalid requests_left value: %s",
375 t_args[i] + 13);
376 }
377 } else if (t_args[i+1] == NULL) {
378 module->service_name = p_strdup(pool, t_args[i]);
379 } else {
380 i_fatal("pam: Unknown setting: %s", t_args[i]);
381 }
382 }
383 return &module->module;
384 }
385
386 struct passdb_module_interface passdb_pam = {
387 "pam",
388
389 pam_preinit,
390 NULL,
391 NULL,
392
393 pam_verify_plain,
394 NULL,
395 NULL
396 };
397 #else
398 struct passdb_module_interface passdb_pam = {
399 .name = "pam"
400 };
401 #endif
402