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