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