1 /*
2 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5
6 #include "includes.h"
7
8 RCSID("$Id: auth2-pam.c,v 1.14 2002/06/28 16:48:12 mouring Exp $");
9
10 #ifdef USE_PAM
11 #include <security/pam_appl.h>
12
13 #include "ssh.h"
14 #include "ssh2.h"
15 #include "auth.h"
16 #include "auth-pam.h"
17 #include "auth-options.h"
18 #include "packet.h"
19 #include "xmalloc.h"
20 #include "dispatch.h"
21 #include "canohost.h"
22 #include "log.h"
23 #include "servconf.h"
24 #include "misc.h"
25
26 #ifdef HAVE_BSM
27 #include "bsmaudit.h"
28 #endif /* HAVE_BSM */
29
30 extern u_int utmp_len;
31 extern ServerOptions options;
32
33 extern Authmethod method_kbdint;
34 extern Authmethod method_passwd;
35
36 #define SSHD_PAM_KBDINT_SVC "sshd-kbdint"
37 /* Maximum attempts for changing expired password */
38 #define DEF_ATTEMPTS 3
39
40 static int do_pam_conv_kbd_int(int num_msg,
41 struct pam_message **msg, struct pam_response **resp,
42 void *appdata_ptr);
43 static void input_userauth_info_response_pam(int type,
44 u_int32_t seqnr,
45 void *ctxt);
46
47 static struct pam_conv conv2 = {
48 do_pam_conv_kbd_int,
49 NULL,
50 };
51 extern char *__pam_msg;
52
53 static void do_pam_kbdint_cleanup(pam_handle_t *pamh);
54 static void do_pam_kbdint(Authctxt *authctxt);
55
56 void
57 auth2_pam(Authctxt *authctxt)
58 {
59 if (authctxt->user == NULL)
60 fatal("auth2_pam: internal error: no user");
61 if (authctxt->method == NULL)
62 fatal("auth2_pam: internal error: no method");
63
64 conv2.appdata_ptr = authctxt;
65 new_start_pam(authctxt, &conv2);
66
67 authctxt->method->method_data = NULL; /* freed in the conv func */
68 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
69 &input_userauth_info_response_pam);
70
71 /*
72 * Since password userauth and keyboard-interactive userauth
73 * both use PAM, and since keyboard-interactive is so much
74 * better than password userauth, we should not allow the user
75 * to try password userauth after trying keyboard-interactive.
76 */
77 if (method_passwd.enabled)
78 *method_passwd.enabled = 0;
79
80 do_pam_kbdint(authctxt);
81
82 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
83 }
84
85 static void
86 do_pam_kbdint(Authctxt *authctxt)
87 {
88 int retval, retval2;
89 pam_handle_t *pamh = authctxt->pam->h;
90 const char *where = "authenticating";
91 char *text = NULL;
92
93 debug2("Calling pam_authenticate()");
94 retval = pam_authenticate(pamh,
95 options.permit_empty_passwd ? 0 :
96 PAM_DISALLOW_NULL_AUTHTOK);
97
98 if (retval != PAM_SUCCESS)
99 goto cleanup;
100
101 debug2("kbd-int: pam_authenticate() succeeded");
102 where = "authorizing";
103 retval = pam_acct_mgmt(pamh, 0);
104
105 if (retval == PAM_NEW_AUTHTOK_REQD) {
106 if (authctxt->valid && authctxt->pw != NULL) {
107 /* send password expiration warning */
108 message_cat(&text,
109 gettext("Warning: Your password has expired,"
110 " please change it now."));
111 packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
112 packet_put_cstring(""); /* name */
113 packet_put_utf8_cstring(text); /* instructions */
114 packet_put_cstring(""); /* language, unused */
115 packet_put_int(0);
116 packet_send();
117 packet_write_wait();
118 debug("expiration message sent");
119 if (text)
120 xfree(text);
121 /*
122 * wait for the response so it does not mix
123 * with the upcoming PAM conversation
124 */
125 packet_read_expect(SSH2_MSG_USERAUTH_INFO_RESPONSE);
126 /*
127 * Can't use temporarily_use_uid() and restore_uid()
128 * here because we need (euid == 0 && ruid == pw_uid)
129 * whereas temporarily_use_uid() arranges for
130 * (suid = 0 && euid == pw_uid && ruid == pw_uid).
131 */
132 (void) setreuid(authctxt->pw->pw_uid, -1);
133 debug2("kbd-int: changing expired password");
134 where = "changing authentication tokens (password)";
135 /*
136 * Depending on error returned from pam_chauthtok, we
137 * need to try to change password a few times before
138 * we error out and return.
139 */
140 int tries = 0;
141 while ((retval = pam_chauthtok(pamh,
142 PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) {
143 if (tries++ < DEF_ATTEMPTS) {
144 if ((retval == PAM_AUTHTOK_ERR) ||
145 (retval == PAM_TRY_AGAIN)) {
146 continue;
147 }
148 }
149 break;
150 }
151 audit_sshd_chauthtok(retval, authctxt->pw->pw_uid,
152 authctxt->pw->pw_gid);
153 (void) setreuid(0, -1);
154 } else {
155 retval = PAM_PERM_DENIED;
156 }
157 }
158
159 if (retval != PAM_SUCCESS)
160 goto cleanup;
161
162 authctxt->pam->state |= PAM_S_DONE_ACCT_MGMT;
163
164 retval = finish_userauth_do_pam(authctxt);
165
166 if (retval != PAM_SUCCESS)
167 goto cleanup;
168
169 /*
170 * PAM handle stays around so we can call pam_close_session()
171 * on it later.
172 */
173 authctxt->method->authenticated = 1;
174 debug2("kbd-int: success (pam->state == %x)", authctxt->pam->state);
175 return;
176
177 cleanup:
178 /*
179 * Check for abandonment and cleanup. When kbdint is abandoned
180 * authctxt->pam->h is NULLed and by this point a new handle may
181 * be allocated.
182 */
183 if (authctxt->pam->h != pamh) {
184 log("Keyboard-interactive (PAM) userauth abandoned "
185 "while %s", where);
186 if ((retval2 = pam_end(pamh, retval)) != PAM_SUCCESS) {
187 log("Cannot close PAM handle after "
188 "kbd-int userauth abandonment[%d]: %.200s",
189 retval2, PAM_STRERROR(pamh, retval2));
190 }
191 authctxt->method->abandoned = 1;
192
193 /*
194 * Avoid double counting; these are incremented in
195 * kbdint_pam_abandon() so that they reflect the correct
196 * count when userauth_finish() is called before
197 * unwinding the dispatch_run() loop, but they are
198 * incremented again in input_userauth_request() when
199 * the loop is unwound, right here.
200 */
201 if (authctxt->method->abandons)
202 authctxt->method->abandons--;
203 if (authctxt->method->attempts)
204 authctxt->method->attempts--;
205 }
206 else {
207 /* Save error value for pam_end() */
208 authctxt->pam->last_pam_retval = retval;
209 log("Keyboard-interactive (PAM) userauth failed[%d] "
210 "while %s: %.200s", retval, where,
211 PAM_STRERROR(pamh, retval));
212 /* pam handle can be reused elsewhere, so no pam_end() here */
213 }
214
215 return;
216 }
217
218 static int
219 do_pam_conv_kbd_int(int num_msg, struct pam_message **msg,
220 struct pam_response **resp, void *appdata_ptr)
221 {
222 int i, j;
223 char *text;
224 Convctxt *conv_ctxt;
225 Authctxt *authctxt = (Authctxt *)appdata_ptr;
226
227 if (!authctxt || !authctxt->method) {
228 debug("Missing state during PAM conversation");
229 return PAM_CONV_ERR;
230 }
231
232 conv_ctxt = xmalloc(sizeof(Convctxt));
233 (void) memset(conv_ctxt, 0, sizeof(Convctxt));
234 conv_ctxt->finished = 0;
235 conv_ctxt->num_received = 0;
236 conv_ctxt->num_expected = 0;
237 conv_ctxt->prompts = xmalloc(sizeof(int) * num_msg);
238 conv_ctxt->responses = xmalloc(sizeof(struct pam_response) * num_msg);
239 (void) memset(conv_ctxt->responses, 0, sizeof(struct pam_response) * num_msg);
240
241 text = NULL;
242 for (i = 0, conv_ctxt->num_expected = 0; i < num_msg; i++) {
243 int style = PAM_MSG_MEMBER(msg, i, msg_style);
244 switch (style) {
245 case PAM_PROMPT_ECHO_ON:
246 debug2("PAM echo on prompt: %s",
247 PAM_MSG_MEMBER(msg, i, msg));
248 conv_ctxt->num_expected++;
249 break;
250 case PAM_PROMPT_ECHO_OFF:
251 debug2("PAM echo off prompt: %s",
252 PAM_MSG_MEMBER(msg, i, msg));
253 conv_ctxt->num_expected++;
254 break;
255 case PAM_TEXT_INFO:
256 debug2("PAM text info prompt: %s",
257 PAM_MSG_MEMBER(msg, i, msg));
258 message_cat(&__pam_msg, PAM_MSG_MEMBER(msg, i, msg));
259 break;
260 case PAM_ERROR_MSG:
261 debug2("PAM error prompt: %s",
262 PAM_MSG_MEMBER(msg, i, msg));
263 message_cat(&__pam_msg, PAM_MSG_MEMBER(msg, i, msg));
264 break;
265 default:
266 /* Capture all these messages to be sent at once */
267 message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
268 break;
269 }
270 }
271
272 if (conv_ctxt->num_expected == 0 && text == NULL) {
273 xfree(conv_ctxt->prompts);
274 xfree(conv_ctxt->responses);
275 xfree(conv_ctxt);
276 return PAM_SUCCESS;
277 }
278
279 authctxt->method->method_data = (void *) conv_ctxt;
280
281 packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
282 packet_put_cstring(""); /* Name */
283 packet_put_utf8_cstring(text ? text : ""); /* Instructions */
284 packet_put_cstring(""); /* Language */
285 packet_put_int(conv_ctxt->num_expected);
286
287 if (text)
288 xfree(text);
289
290 for (i = 0, j = 0; i < num_msg; i++) {
291 int style = PAM_MSG_MEMBER(msg, i, msg_style);
292
293 /* Skip messages which don't need a reply */
294 if (style != PAM_PROMPT_ECHO_ON && style != PAM_PROMPT_ECHO_OFF)
295 continue;
296
297 conv_ctxt->prompts[j++] = i;
298 packet_put_utf8_cstring(PAM_MSG_MEMBER(msg, i, msg));
299 packet_put_char(style == PAM_PROMPT_ECHO_ON);
300 }
301 packet_send();
302 packet_write_wait();
303
304 /*
305 * Here the dispatch_run() loop is nested. It should be unwound
306 * if keyboard-interactive userauth is abandoned (or restarted;
307 * same thing).
308 *
309 * The condition for breaking out of the nested dispatch_run() loop is
310 * ((got kbd-int info reponse) || (kbd-int abandoned))
311 *
312 * conv_ctxt->finished is set in either of those cases.
313 *
314 * When abandonment is detected the conv_ctxt->finished is set as
315 * is conv_ctxt->abandoned, causing this function to signal
316 * userauth nested dispatch_run() loop unwinding and to return
317 * PAM_CONV_ERR;
318 */
319 debug2("Nesting dispatch_run loop");
320 dispatch_run(DISPATCH_BLOCK, &conv_ctxt->finished, appdata_ptr);
321 debug2("Nested dispatch_run loop exited");
322
323 if (conv_ctxt->abandoned) {
324 authctxt->unwind_dispatch_loop = 1;
325 xfree(conv_ctxt->prompts);
326 xfree(conv_ctxt->responses);
327 xfree(conv_ctxt);
328 debug("PAM conv function returns PAM_CONV_ERR");
329 return PAM_CONV_ERR;
330 }
331
332 if (conv_ctxt->num_received == conv_ctxt->num_expected) {
333 *resp = conv_ctxt->responses;
334 xfree(conv_ctxt->prompts);
335 xfree(conv_ctxt);
336 debug("PAM conv function returns PAM_SUCCESS");
337 return PAM_SUCCESS;
338 }
339
340 debug("PAM conv function returns PAM_CONV_ERR");
341 xfree(conv_ctxt->prompts);
342 xfree(conv_ctxt->responses);
343 xfree(conv_ctxt);
344 return PAM_CONV_ERR;
345 }
346
347 static void
348 input_userauth_info_response_pam(int type, u_int32_t seqnr, void *ctxt)
349 {
350 Authctxt *authctxt = ctxt;
351 Convctxt *conv_ctxt;
352 unsigned int nresp = 0, rlen = 0, i = 0;
353 char *resp;
354
355 if (authctxt == NULL)
356 fatal("input_userauth_info_response_pam: no authentication context");
357
358 /* Check for spurious/unexpected info response */
359 if (method_kbdint.method_data == NULL) {
360 debug("input_userauth_info_response_pam: no method context");
361 return;
362 }
363
364 conv_ctxt = (Convctxt *) method_kbdint.method_data;
365
366 nresp = packet_get_int(); /* Number of responses. */
367 debug("got %d responses", nresp);
368
369
370 #if 0
371 if (nresp != conv_ctxt->num_expected)
372 fatal("%s: Received incorrect number of responses "
373 "(expected %d, received %u)", __func__,
374 conv_ctxt->num_expected, nresp);
375 #endif
376
377 if (nresp > 100)
378 fatal("%s: too many replies", __func__);
379
380 for (i = 0; i < nresp && i < conv_ctxt->num_expected ; i++) {
381 int j = conv_ctxt->prompts[i];
382
383 /*
384 * We assume that ASCII charset is used for password
385 * although the protocol requires UTF-8 encoding for the
386 * password string. Therefore, we don't perform code
387 * conversion for the string.
388 */
389 resp = packet_get_string(&rlen);
390 if (i < conv_ctxt->num_expected) {
391 conv_ctxt->responses[j].resp_retcode = PAM_SUCCESS;
392 conv_ctxt->responses[j].resp = xstrdup(resp);
393 conv_ctxt->num_received++;
394 }
395 xfree(resp);
396 }
397
398 if (nresp < conv_ctxt->num_expected)
399 fatal("%s: too few replies (%d < %d)", __func__,
400 nresp, conv_ctxt->num_expected);
401
402 /* XXX - This could make a covert channel... */
403 if (nresp > conv_ctxt->num_expected)
404 debug("Ignoring additional PAM replies");
405
406 conv_ctxt->finished = 1;
407
408 packet_check_eom();
409 }
410
411 #if 0
412 int
413 kbdint_pam_abandon_chk(Authctxt *authctxt, Authmethod *method)
414 {
415 if (!method)
416 return 0; /* fatal(), really; it'll happen somewhere else */
417
418 if (!method->method_data)
419 return 0;
420
421 return 1;
422 }
423 #endif
424
425 void
426 kbdint_pam_abandon(Authctxt *authctxt, Authmethod *method)
427 {
428 Convctxt *conv_ctxt;
429
430 /*
431 * But, if it ever becomes desirable and possible to support
432 * kbd-int userauth abandonment, here's what must be done.
433 */
434 if (!method)
435 return;
436
437 if (!method->method_data)
438 return;
439
440 conv_ctxt = (Convctxt *) method->method_data;
441
442 /* dispatch_run() loop will exit */
443 conv_ctxt->abandoned = 1;
444 conv_ctxt->finished = 1;
445
446 /*
447 * The method_data will be free in the corresponding, active
448 * conversation function
449 */
450 method->method_data = NULL;
451
452 /* update counts that can't be updated elsewhere */
453 method->abandons++;
454 method->attempts++;
455
456 /* Finally, we cannot re-use the current current PAM handle */
457 authctxt->pam->h = NULL; /* Let the conv function cleanup */
458 }
459 #endif