auth2-chall.c revision 181110
166458Sdfr/* $OpenBSD: auth2-chall.c,v 1.31 2006/08/05 08:28:24 dtucker Exp $ */
266458Sdfr/*
396912Smarcel * Copyright (c) 2001 Markus Friedl.  All rights reserved.
496912Smarcel * Copyright (c) 2001 Per Allansson.  All rights reserved.
5139790Simp *
666458Sdfr * Redistribution and use in source and binary forms, with or without
766458Sdfr * modification, are permitted provided that the following conditions
866458Sdfr * are met:
966458Sdfr * 1. Redistributions of source code must retain the above copyright
1066458Sdfr *    notice, this list of conditions and the following disclaimer.
1166458Sdfr * 2. Redistributions in binary form must reproduce the above copyright
1266458Sdfr *    notice, this list of conditions and the following disclaimer in the
1366458Sdfr *    documentation and/or other materials provided with the distribution.
1466458Sdfr *
1566458Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1666458Sdfr * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1766458Sdfr * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1866458Sdfr * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1966458Sdfr * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2066458Sdfr * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2166458Sdfr * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2266458Sdfr * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2366458Sdfr * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2466458Sdfr * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2566458Sdfr */
2666458Sdfr
2766458Sdfr#include "includes.h"
2866458Sdfr
2966458Sdfr#include <sys/types.h>
3066458Sdfr
3166458Sdfr#include <stdarg.h>
3266458Sdfr#include <stdio.h>
3366458Sdfr#include <string.h>
3466458Sdfr
3566458Sdfr#include "xmalloc.h"
3666458Sdfr#include "ssh2.h"
3766458Sdfr#include "key.h"
3866458Sdfr#include "hostfile.h"
3966458Sdfr#include "auth.h"
4066458Sdfr#include "buffer.h"
4166458Sdfr#include "packet.h"
4266458Sdfr#include "dispatch.h"
4366458Sdfr#include "log.h"
4496912Smarcel#include "servconf.h"
4566458Sdfr
4666458Sdfr/* import */
47170033Salcextern ServerOptions options;
48170033Salc
4966458Sdfrstatic int auth2_challenge_start(Authctxt *);
50115084Smarcelstatic int send_userauth_info_request(Authctxt *);
5166458Sdfrstatic void input_userauth_info_response(int, u_int32_t, void *);
5266458Sdfr
5366458Sdfr#ifdef BSD_AUTH
5466458Sdfrextern KbdintDevice bsdauth_device;
5566458Sdfr#else
5666458Sdfr#ifdef USE_PAM
5766458Sdfrextern KbdintDevice sshpam_device;
5866458Sdfr#endif
5966458Sdfr#ifdef SKEY
6066458Sdfrextern KbdintDevice skey_device;
6166458Sdfr#endif
6266458Sdfr#endif
6366458Sdfr
6466458SdfrKbdintDevice *devices[] = {
6566458Sdfr#ifdef BSD_AUTH
6666458Sdfr	&bsdauth_device,
6766458Sdfr#else
6892670Speter#ifdef USE_PAM
6966458Sdfr	&sshpam_device,
7066458Sdfr#endif
7166458Sdfr#ifdef SKEY
7266458Sdfr	&skey_device,
7366458Sdfr#endif
7466458Sdfr#endif
7566458Sdfr	NULL
7666458Sdfr};
7766458Sdfr
7866458Sdfrtypedef struct KbdintAuthctxt KbdintAuthctxt;
7966458Sdfrstruct KbdintAuthctxt
8066458Sdfr{
8166458Sdfr	char *devices;
8266458Sdfr	void *ctxt;
8366458Sdfr	KbdintDevice *device;
8466458Sdfr	u_int nreq;
8566458Sdfr};
8666458Sdfr
8766458Sdfr#ifdef USE_PAM
8866458Sdfrvoid
8966458Sdfrremove_kbdint_device(const char *devname)
9066458Sdfr{
9166458Sdfr	int i, j;
9266458Sdfr
9366458Sdfr	for (i = 0; devices[i] != NULL; i++)
9466458Sdfr		if (strcmp(devices[i]->name, devname) == 0) {
9566458Sdfr			for (j = i; devices[j] != NULL; j++)
9666458Sdfr				devices[j] = devices[j+1];
9766458Sdfr			i--;
9866458Sdfr		}
9966458Sdfr}
10066458Sdfr#endif
10166458Sdfr
102150008Salcstatic KbdintAuthctxt *
10366458Sdfrkbdint_alloc(const char *devs)
10466458Sdfr{
10566458Sdfr	KbdintAuthctxt *kbdintctxt;
10666458Sdfr	Buffer b;
10766458Sdfr	int i;
10866458Sdfr
109106486Smarcel#ifdef USE_PAM
110106486Smarcel	if (!options.use_pam)
111106486Smarcel		remove_kbdint_device("pam");
112106486Smarcel#endif
113106486Smarcel
114169291Salc	kbdintctxt = xmalloc(sizeof(KbdintAuthctxt));
115169291Salc	if (strcmp(devs, "") == 0) {
116169291Salc		buffer_init(&b);
117169291Salc		for (i = 0; devices[i]; i++) {
118169291Salc			if (buffer_len(&b) > 0)
119170519Salc				buffer_append(&b, ",", 1);
120170519Salc			buffer_append(&b, devices[i]->name,
121170519Salc			    strlen(devices[i]->name));
122170519Salc		}
123170519Salc		buffer_append(&b, "\0", 1);
124170519Salc		kbdintctxt->devices = xstrdup(buffer_ptr(&b));
125172317Salc		buffer_free(&b);
126170519Salc	} else {
127170519Salc		kbdintctxt->devices = xstrdup(devs);
128170519Salc	}
129170519Salc	debug("kbdint_alloc: devices '%s'", kbdintctxt->devices);
130172317Salc	kbdintctxt->ctxt = NULL;
131172317Salc	kbdintctxt->device = NULL;
132170519Salc	kbdintctxt->nreq = 0;
133170519Salc
134170519Salc	return kbdintctxt;
135170519Salc}
136170519Salcstatic void
137170519Salckbdint_reset_device(KbdintAuthctxt *kbdintctxt)
138170519Salc{
139170519Salc	if (kbdintctxt->ctxt) {
140170519Salc		kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
141170519Salc		kbdintctxt->ctxt = NULL;
142170519Salc	}
143170519Salc	kbdintctxt->device = NULL;
144170519Salc}
145170519Salcstatic void
146170519Salckbdint_free(KbdintAuthctxt *kbdintctxt)
147170519Salc{
14896912Smarcel	if (kbdintctxt->device)
14996912Smarcel		kbdint_reset_device(kbdintctxt);
15096912Smarcel	if (kbdintctxt->devices) {
15196912Smarcel		xfree(kbdintctxt->devices);
15296912Smarcel		kbdintctxt->devices = NULL;
15396912Smarcel	}
15496912Smarcel	xfree(kbdintctxt);
15596912Smarcel}
15696912Smarcel/* get next device */
157119906Smarcelstatic int
158119906Smarcelkbdint_next_device(KbdintAuthctxt *kbdintctxt)
159119906Smarcel{
160119906Smarcel	size_t len;
161119906Smarcel	char *t;
162119906Smarcel	int i;
163119906Smarcel
164119906Smarcel	if (kbdintctxt->device)
165119906Smarcel		kbdint_reset_device(kbdintctxt);
166119906Smarcel	do {
167121268Smarcel		len = kbdintctxt->devices ?
168121268Smarcel		    strcspn(kbdintctxt->devices, ",") : 0;
169119906Smarcel
17066458Sdfr		if (len == 0)
17166458Sdfr			break;
17266458Sdfr		for (i = 0; devices[i]; i++)
17366458Sdfr			if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
17466458Sdfr				kbdintctxt->device = devices[i];
175115084Smarcel		t = kbdintctxt->devices;
176115084Smarcel		kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
177115084Smarcel		xfree(t);
178115084Smarcel		debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
17966458Sdfr		    kbdintctxt->devices : "<empty>");
18066458Sdfr	} while (kbdintctxt->devices && !kbdintctxt->device);
181115084Smarcel
18296912Smarcel	return kbdintctxt->device ? 1 : 0;
18366458Sdfr}
18466458Sdfr
18566458Sdfr/*
18666458Sdfr * try challenge-response, set authctxt->postponed if we have to
18766458Sdfr * wait for the response.
18866458Sdfr */
18966458Sdfrint
190168920Ssepotvinauth2_challenge(Authctxt *authctxt, char *devs)
191168920Ssepotvin{
19266458Sdfr	debug("auth2_challenge: user=%s devs=%s",
19366458Sdfr	    authctxt->user ? authctxt->user : "<nouser>",
19466458Sdfr	    devs ? devs : "<no devs>");
19566458Sdfr
19666458Sdfr	if (authctxt->user == NULL || !devs)
19766458Sdfr		return 0;
19866458Sdfr	if (authctxt->kbdintctxt == NULL)
19966458Sdfr		authctxt->kbdintctxt = kbdint_alloc(devs);
20066458Sdfr	return auth2_challenge_start(authctxt);
20166458Sdfr}
20266458Sdfr
20396912Smarcel/* unregister kbd-int callbacks and context */
204void
205auth2_challenge_stop(Authctxt *authctxt)
206{
207	/* unregister callback */
208	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
209	if (authctxt->kbdintctxt != NULL)  {
210		kbdint_free(authctxt->kbdintctxt);
211		authctxt->kbdintctxt = NULL;
212	}
213}
214
215/* side effect: sets authctxt->postponed if a reply was sent*/
216static int
217auth2_challenge_start(Authctxt *authctxt)
218{
219	KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
220
221	debug2("auth2_challenge_start: devices %s",
222	    kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
223
224	if (kbdint_next_device(kbdintctxt) == 0) {
225		auth2_challenge_stop(authctxt);
226		return 0;
227	}
228	debug("auth2_challenge_start: trying authentication method '%s'",
229	    kbdintctxt->device->name);
230
231	if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
232		auth2_challenge_stop(authctxt);
233		return 0;
234	}
235	if (send_userauth_info_request(authctxt) == 0) {
236		auth2_challenge_stop(authctxt);
237		return 0;
238	}
239	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
240	    &input_userauth_info_response);
241
242	authctxt->postponed = 1;
243	return 0;
244}
245
246static int
247send_userauth_info_request(Authctxt *authctxt)
248{
249	KbdintAuthctxt *kbdintctxt;
250	char *name, *instr, **prompts;
251	u_int i, *echo_on;
252
253	kbdintctxt = authctxt->kbdintctxt;
254	if (kbdintctxt->device->query(kbdintctxt->ctxt,
255	    &name, &instr, &kbdintctxt->nreq, &prompts, &echo_on))
256		return 0;
257
258	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
259	packet_put_cstring(name);
260	packet_put_cstring(instr);
261	packet_put_cstring("");		/* language not used */
262	packet_put_int(kbdintctxt->nreq);
263	for (i = 0; i < kbdintctxt->nreq; i++) {
264		packet_put_cstring(prompts[i]);
265		packet_put_char(echo_on[i]);
266	}
267	packet_send();
268	packet_write_wait();
269
270	for (i = 0; i < kbdintctxt->nreq; i++)
271		xfree(prompts[i]);
272	xfree(prompts);
273	xfree(echo_on);
274	xfree(name);
275	xfree(instr);
276	return 1;
277}
278
279static void
280input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
281{
282	Authctxt *authctxt = ctxt;
283	KbdintAuthctxt *kbdintctxt;
284	int authenticated = 0, res, len;
285	u_int i, nresp;
286	char **response = NULL, *method;
287
288	if (authctxt == NULL)
289		fatal("input_userauth_info_response: no authctxt");
290	kbdintctxt = authctxt->kbdintctxt;
291	if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
292		fatal("input_userauth_info_response: no kbdintctxt");
293	if (kbdintctxt->device == NULL)
294		fatal("input_userauth_info_response: no device");
295
296	authctxt->postponed = 0;	/* reset */
297	nresp = packet_get_int();
298	if (nresp != kbdintctxt->nreq)
299		fatal("input_userauth_info_response: wrong number of replies");
300	if (nresp > 100)
301		fatal("input_userauth_info_response: too many replies");
302	if (nresp > 0) {
303		response = xcalloc(nresp, sizeof(char *));
304		for (i = 0; i < nresp; i++)
305			response[i] = packet_get_string(NULL);
306	}
307	packet_check_eom();
308
309	res = kbdintctxt->device->respond(kbdintctxt->ctxt, nresp, response);
310
311	for (i = 0; i < nresp; i++) {
312		memset(response[i], 'r', strlen(response[i]));
313		xfree(response[i]);
314	}
315	if (response)
316		xfree(response);
317
318	switch (res) {
319	case 0:
320		/* Success! */
321		authenticated = authctxt->valid ? 1 : 0;
322		break;
323	case 1:
324		/* Authentication needs further interaction */
325		if (send_userauth_info_request(authctxt) == 1)
326			authctxt->postponed = 1;
327		break;
328	default:
329		/* Failure! */
330		break;
331	}
332
333	len = strlen("keyboard-interactive") + 2 +
334		strlen(kbdintctxt->device->name);
335	method = xmalloc(len);
336	snprintf(method, len, "keyboard-interactive/%s",
337	    kbdintctxt->device->name);
338
339	if (!authctxt->postponed) {
340		if (authenticated) {
341			auth2_challenge_stop(authctxt);
342		} else {
343			/* start next device */
344			/* may set authctxt->postponed */
345			auth2_challenge_start(authctxt);
346		}
347	}
348	userauth_finish(authctxt, authenticated, method);
349	xfree(method);
350}
351
352void
353privsep_challenge_enable(void)
354{
355#if defined(BSD_AUTH) || defined(USE_PAM) || defined(SKEY)
356	int n = 0;
357#endif
358#ifdef BSD_AUTH
359	extern KbdintDevice mm_bsdauth_device;
360#endif
361#ifdef USE_PAM
362	extern KbdintDevice mm_sshpam_device;
363#endif
364#ifdef SKEY
365	extern KbdintDevice mm_skey_device;
366#endif
367
368#ifdef BSD_AUTH
369	devices[n++] = &mm_bsdauth_device;
370#else
371#ifdef USE_PAM
372	devices[n++] = &mm_sshpam_device;
373#endif
374#ifdef SKEY
375	devices[n++] = &mm_skey_device;
376#endif
377#endif
378}
379