auth2-chall.c revision 92555
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.16 2002/01/13 17:57:37 markus Exp $");
27
28#include "ssh2.h"
29#include "auth.h"
30#include "buffer.h"
31#include "packet.h"
32#include "xmalloc.h"
33#include "dispatch.h"
34#include "auth.h"
35#include "log.h"
36
37static int auth2_challenge_start(Authctxt *);
38static int send_userauth_info_request(Authctxt *);
39static void input_userauth_info_response(int, u_int32_t, void *);
40
41#ifdef BSD_AUTH
42extern KbdintDevice bsdauth_device;
43#else
44#ifdef SKEY
45extern KbdintDevice skey_device;
46#endif
47#endif
48
49KbdintDevice *devices[] = {
50#ifdef BSD_AUTH
51	&bsdauth_device,
52#else
53#ifdef SKEY
54	&skey_device,
55#endif
56#endif
57	NULL
58};
59
60typedef struct KbdintAuthctxt KbdintAuthctxt;
61struct KbdintAuthctxt
62{
63	char *devices;
64	void *ctxt;
65	KbdintDevice *device;
66};
67
68static KbdintAuthctxt *
69kbdint_alloc(const char *devs)
70{
71	KbdintAuthctxt *kbdintctxt;
72	Buffer b;
73	int i;
74
75	kbdintctxt = xmalloc(sizeof(KbdintAuthctxt));
76	if (strcmp(devs, "") == 0) {
77		buffer_init(&b);
78		for (i = 0; devices[i]; i++) {
79			if (buffer_len(&b) > 0)
80				buffer_append(&b, ",", 1);
81			buffer_append(&b, devices[i]->name,
82			    strlen(devices[i]->name));
83		}
84		buffer_append(&b, "\0", 1);
85		kbdintctxt->devices = xstrdup(buffer_ptr(&b));
86		buffer_free(&b);
87	} else {
88		kbdintctxt->devices = xstrdup(devs);
89	}
90	debug("kbdint_alloc: devices '%s'", kbdintctxt->devices);
91	kbdintctxt->ctxt = NULL;
92	kbdintctxt->device = NULL;
93
94	return kbdintctxt;
95}
96static void
97kbdint_reset_device(KbdintAuthctxt *kbdintctxt)
98{
99	if (kbdintctxt->ctxt) {
100		kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
101		kbdintctxt->ctxt = NULL;
102	}
103	kbdintctxt->device = NULL;
104}
105static void
106kbdint_free(KbdintAuthctxt *kbdintctxt)
107{
108	if (kbdintctxt->device)
109		kbdint_reset_device(kbdintctxt);
110	if (kbdintctxt->devices) {
111		xfree(kbdintctxt->devices);
112		kbdintctxt->devices = NULL;
113	}
114	xfree(kbdintctxt);
115}
116/* get next device */
117static int
118kbdint_next_device(KbdintAuthctxt *kbdintctxt)
119{
120	size_t len;
121	char *t;
122	int i;
123
124	if (kbdintctxt->device)
125		kbdint_reset_device(kbdintctxt);
126	do {
127		len = kbdintctxt->devices ?
128		    strcspn(kbdintctxt->devices, ",") : 0;
129
130		if (len == 0)
131			break;
132		for (i = 0; devices[i]; i++)
133			if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
134				kbdintctxt->device = devices[i];
135		t = kbdintctxt->devices;
136		kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
137		xfree(t);
138		debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
139		   kbdintctxt->devices : "<empty>");
140	} while (kbdintctxt->devices && !kbdintctxt->device);
141
142	return kbdintctxt->device ? 1 : 0;
143}
144
145/*
146 * try challenge-response, set authctxt->postponed if we have to
147 * wait for the response.
148 */
149int
150auth2_challenge(Authctxt *authctxt, char *devs)
151{
152	debug("auth2_challenge: user=%s devs=%s",
153	    authctxt->user ? authctxt->user : "<nouser>",
154	    devs ? devs : "<no devs>");
155
156	if (authctxt->user == NULL || !devs)
157		return 0;
158	if (authctxt->kbdintctxt == NULL)
159		authctxt->kbdintctxt = kbdint_alloc(devs);
160	return auth2_challenge_start(authctxt);
161}
162
163/* unregister kbd-int callbacks and context */
164void
165auth2_challenge_stop(Authctxt *authctxt)
166{
167	/* unregister callback */
168	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
169	if (authctxt->kbdintctxt != NULL)  {
170		kbdint_free(authctxt->kbdintctxt);
171		authctxt->kbdintctxt = NULL;
172	}
173}
174
175/* side effect: sets authctxt->postponed if a reply was sent*/
176static int
177auth2_challenge_start(Authctxt *authctxt)
178{
179	KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
180
181	debug2("auth2_challenge_start: devices %s",
182	    kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
183
184	if (kbdint_next_device(kbdintctxt) == 0) {
185		auth2_challenge_stop(authctxt);
186		return 0;
187	}
188	debug("auth2_challenge_start: trying authentication method '%s'",
189	    kbdintctxt->device->name);
190
191	if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
192		auth2_challenge_stop(authctxt);
193		return 0;
194	}
195	if (send_userauth_info_request(authctxt) == 0) {
196		auth2_challenge_stop(authctxt);
197		return 0;
198	}
199	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
200	    &input_userauth_info_response);
201
202	authctxt->postponed = 1;
203	return 0;
204}
205
206static int
207send_userauth_info_request(Authctxt *authctxt)
208{
209	KbdintAuthctxt *kbdintctxt;
210	char *name, *instr, **prompts;
211	int i;
212	u_int numprompts, *echo_on;
213
214	kbdintctxt = authctxt->kbdintctxt;
215	if (kbdintctxt->device->query(kbdintctxt->ctxt,
216	    &name, &instr, &numprompts, &prompts, &echo_on))
217		return 0;
218
219	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
220	packet_put_cstring(name);
221	packet_put_cstring(instr);
222	packet_put_cstring(""); 	/* language not used */
223	packet_put_int(numprompts);
224	for (i = 0; i < numprompts; i++) {
225		packet_put_cstring(prompts[i]);
226		packet_put_char(echo_on[i]);
227	}
228	packet_send();
229	packet_write_wait();
230
231	for (i = 0; i < numprompts; i++)
232		xfree(prompts[i]);
233	xfree(prompts);
234	xfree(echo_on);
235	xfree(name);
236	xfree(instr);
237	return 1;
238}
239
240static void
241input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
242{
243	Authctxt *authctxt = ctxt;
244	KbdintAuthctxt *kbdintctxt;
245	int i, authenticated = 0, res, len;
246	u_int nresp;
247	char **response = NULL, *method;
248
249	if (authctxt == NULL)
250		fatal("input_userauth_info_response: no authctxt");
251	kbdintctxt = authctxt->kbdintctxt;
252	if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
253		fatal("input_userauth_info_response: no kbdintctxt");
254	if (kbdintctxt->device == NULL)
255		fatal("input_userauth_info_response: no device");
256
257	authctxt->postponed = 0;	/* reset */
258	nresp = packet_get_int();
259	if (nresp > 0) {
260		response = xmalloc(nresp * sizeof(char*));
261		for (i = 0; i < nresp; i++)
262			response[i] = packet_get_string(NULL);
263	}
264	packet_check_eom();
265
266	if (authctxt->valid) {
267		res = kbdintctxt->device->respond(kbdintctxt->ctxt,
268		    nresp, response);
269	} else {
270		res = -1;
271	}
272
273	for (i = 0; i < nresp; i++) {
274		memset(response[i], 'r', strlen(response[i]));
275		xfree(response[i]);
276	}
277	if (response)
278		xfree(response);
279
280	switch (res) {
281	case 0:
282		/* Success! */
283		authenticated = 1;
284		break;
285	case 1:
286		/* Authentication needs further interaction */
287		if (send_userauth_info_request(authctxt) == 1)
288			authctxt->postponed = 1;
289		break;
290	default:
291		/* Failure! */
292		break;
293	}
294
295	len = strlen("keyboard-interactive") + 2 +
296		strlen(kbdintctxt->device->name);
297	method = xmalloc(len);
298	snprintf(method, len, "keyboard-interactive/%s",
299	    kbdintctxt->device->name);
300
301	if (!authctxt->postponed) {
302		if (authenticated) {
303			auth2_challenge_stop(authctxt);
304		} else {
305			/* start next device */
306			/* may set authctxt->postponed */
307			auth2_challenge_start(authctxt);
308		}
309	}
310	userauth_finish(authctxt, authenticated, method);
311	xfree(method);
312}
313