auth2-chall.c revision 98684
1/*
2 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
3 * Copyright (c) 2001 Per Allansson.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25#include "includes.h"
26RCSID("$OpenBSD: auth2-chall.c,v 1.18 2002/06/19 00:27:55 deraadt Exp $");
27RCSID("$FreeBSD: head/crypto/openssh/auth2-chall.c 98684 2002-06-23 16:09:08Z des $");
28
29#include "ssh2.h"
30#include "auth.h"
31#include "buffer.h"
32#include "packet.h"
33#include "xmalloc.h"
34#include "dispatch.h"
35#include "auth.h"
36#include "log.h"
37
38static int auth2_challenge_start(Authctxt *);
39static int send_userauth_info_request(Authctxt *);
40static void input_userauth_info_response(int, u_int32_t, void *);
41
42#ifdef BSD_AUTH
43extern KbdintDevice bsdauth_device;
44#elif defined(USE_PAM)
45extern KbdintDevice pam_device;
46#elif defined(SKEY)
47extern KbdintDevice skey_device;
48#endif
49
50KbdintDevice *devices[] = {
51#ifdef BSD_AUTH
52	&bsdauth_device,
53#elif defined(USE_PAM)
54	&pam_device,
55#elif defined(SKEY)
56	&skey_device,
57#endif
58	NULL
59};
60
61typedef struct KbdintAuthctxt KbdintAuthctxt;
62struct KbdintAuthctxt
63{
64	char *devices;
65	void *ctxt;
66	KbdintDevice *device;
67};
68
69static KbdintAuthctxt *
70kbdint_alloc(const char *devs)
71{
72	KbdintAuthctxt *kbdintctxt;
73	Buffer b;
74	int i;
75
76	kbdintctxt = xmalloc(sizeof(KbdintAuthctxt));
77	if (strcmp(devs, "") == 0) {
78		buffer_init(&b);
79		for (i = 0; devices[i]; i++) {
80			if (buffer_len(&b) > 0)
81				buffer_append(&b, ",", 1);
82			buffer_append(&b, devices[i]->name,
83			    strlen(devices[i]->name));
84		}
85		buffer_append(&b, "\0", 1);
86		kbdintctxt->devices = xstrdup(buffer_ptr(&b));
87		buffer_free(&b);
88	} else {
89		kbdintctxt->devices = xstrdup(devs);
90	}
91	debug("kbdint_alloc: devices '%s'", kbdintctxt->devices);
92	kbdintctxt->ctxt = NULL;
93	kbdintctxt->device = NULL;
94
95	return kbdintctxt;
96}
97static void
98kbdint_reset_device(KbdintAuthctxt *kbdintctxt)
99{
100	if (kbdintctxt->ctxt) {
101		kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
102		kbdintctxt->ctxt = NULL;
103	}
104	kbdintctxt->device = NULL;
105}
106static void
107kbdint_free(KbdintAuthctxt *kbdintctxt)
108{
109	if (kbdintctxt->device)
110		kbdint_reset_device(kbdintctxt);
111	if (kbdintctxt->devices) {
112		xfree(kbdintctxt->devices);
113		kbdintctxt->devices = NULL;
114	}
115	xfree(kbdintctxt);
116}
117/* get next device */
118static int
119kbdint_next_device(KbdintAuthctxt *kbdintctxt)
120{
121	size_t len;
122	char *t;
123	int i;
124
125	if (kbdintctxt->device)
126		kbdint_reset_device(kbdintctxt);
127	do {
128		len = kbdintctxt->devices ?
129		    strcspn(kbdintctxt->devices, ",") : 0;
130
131		if (len == 0)
132			break;
133		for (i = 0; devices[i]; i++)
134			if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
135				kbdintctxt->device = devices[i];
136		t = kbdintctxt->devices;
137		kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
138		xfree(t);
139		debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
140		   kbdintctxt->devices : "<empty>");
141	} while (kbdintctxt->devices && !kbdintctxt->device);
142
143	return kbdintctxt->device ? 1 : 0;
144}
145
146/*
147 * try challenge-response, set authctxt->postponed if we have to
148 * wait for the response.
149 */
150int
151auth2_challenge(Authctxt *authctxt, char *devs)
152{
153	debug("auth2_challenge: user=%s devs=%s",
154	    authctxt->user ? authctxt->user : "<nouser>",
155	    devs ? devs : "<no devs>");
156
157	if (authctxt->user == NULL || !devs)
158		return 0;
159	if (authctxt->kbdintctxt == NULL)
160		authctxt->kbdintctxt = kbdint_alloc(devs);
161	return auth2_challenge_start(authctxt);
162}
163
164/* unregister kbd-int callbacks and context */
165void
166auth2_challenge_stop(Authctxt *authctxt)
167{
168	/* unregister callback */
169	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
170	if (authctxt->kbdintctxt != NULL)  {
171		kbdint_free(authctxt->kbdintctxt);
172		authctxt->kbdintctxt = NULL;
173	}
174}
175
176/* side effect: sets authctxt->postponed if a reply was sent*/
177static int
178auth2_challenge_start(Authctxt *authctxt)
179{
180	KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
181
182	debug2("auth2_challenge_start: devices %s",
183	    kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
184
185	if (kbdint_next_device(kbdintctxt) == 0) {
186		auth2_challenge_stop(authctxt);
187		return 0;
188	}
189	debug("auth2_challenge_start: trying authentication method '%s'",
190	    kbdintctxt->device->name);
191
192	if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
193		auth2_challenge_stop(authctxt);
194		return 0;
195	}
196	if (send_userauth_info_request(authctxt) == 0) {
197		auth2_challenge_stop(authctxt);
198		return 0;
199	}
200	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
201	    &input_userauth_info_response);
202
203	authctxt->postponed = 1;
204	return 0;
205}
206
207static int
208send_userauth_info_request(Authctxt *authctxt)
209{
210	KbdintAuthctxt *kbdintctxt;
211	char *name, *instr, **prompts;
212	int i;
213	u_int numprompts, *echo_on;
214
215	kbdintctxt = authctxt->kbdintctxt;
216	if (kbdintctxt->device->query(kbdintctxt->ctxt,
217	    &name, &instr, &numprompts, &prompts, &echo_on))
218		return 0;
219
220	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
221	packet_put_cstring(name);
222	packet_put_cstring(instr);
223	packet_put_cstring("");		/* language not used */
224	packet_put_int(numprompts);
225	for (i = 0; i < numprompts; i++) {
226		packet_put_cstring(prompts[i]);
227		packet_put_char(echo_on[i]);
228	}
229	packet_send();
230	packet_write_wait();
231
232	for (i = 0; i < numprompts; i++)
233		xfree(prompts[i]);
234	xfree(prompts);
235	xfree(echo_on);
236	xfree(name);
237	xfree(instr);
238	return 1;
239}
240
241static void
242input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
243{
244	Authctxt *authctxt = ctxt;
245	KbdintAuthctxt *kbdintctxt;
246	int i, authenticated = 0, res, len;
247	u_int nresp;
248	char **response = NULL, *method;
249
250	if (authctxt == NULL)
251		fatal("input_userauth_info_response: no authctxt");
252	kbdintctxt = authctxt->kbdintctxt;
253	if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
254		fatal("input_userauth_info_response: no kbdintctxt");
255	if (kbdintctxt->device == NULL)
256		fatal("input_userauth_info_response: no device");
257
258	authctxt->postponed = 0;	/* reset */
259	nresp = packet_get_int();
260	if (nresp > 0) {
261		response = xmalloc(nresp * sizeof(char*));
262		for (i = 0; i < nresp; i++)
263			response[i] = packet_get_string(NULL);
264	}
265	packet_check_eom();
266
267	if (authctxt->valid) {
268		res = kbdintctxt->device->respond(kbdintctxt->ctxt,
269		    nresp, response);
270	} else {
271		res = -1;
272	}
273
274	for (i = 0; i < nresp; i++) {
275		memset(response[i], 'r', strlen(response[i]));
276		xfree(response[i]);
277	}
278	if (response)
279		xfree(response);
280
281	switch (res) {
282	case 0:
283		/* Success! */
284		authenticated = 1;
285		break;
286	case 1:
287		/* Authentication needs further interaction */
288		if (send_userauth_info_request(authctxt) == 1)
289			authctxt->postponed = 1;
290		break;
291	default:
292		/* Failure! */
293		break;
294	}
295
296	len = strlen("keyboard-interactive") + 2 +
297		strlen(kbdintctxt->device->name);
298	method = xmalloc(len);
299	snprintf(method, len, "keyboard-interactive/%s",
300	    kbdintctxt->device->name);
301
302	if (!authctxt->postponed) {
303		if (authenticated) {
304			auth2_challenge_stop(authctxt);
305		} else {
306			/* start next device */
307			/* may set authctxt->postponed */
308			auth2_challenge_start(authctxt);
309		}
310	}
311	userauth_finish(authctxt, authenticated, method);
312	xfree(method);
313}
314
315void
316privsep_challenge_enable(void)
317{
318#ifdef BSD_AUTH
319	extern KbdintDevice mm_bsdauth_device;
320#endif
321#ifdef SKEY
322	extern KbdintDevice mm_skey_device;
323#endif
324	/* As long as SSHv1 has devices[0] hard coded this is fine */
325#ifdef BSD_AUTH
326	devices[0] = &mm_bsdauth_device;
327#else
328#ifdef SKEY
329	devices[0] = &mm_skey_device;
330#endif
331#endif
332}
333