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