xref: /openbsd-src/usr.bin/ssh/auth2-chall.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /* $OpenBSD: auth2-chall.c,v 1.41 2014/02/02 03:44:31 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 = xcalloc(1, 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 	free(kbdintctxt->devices);
106 	explicit_bzero(kbdintctxt, sizeof(*kbdintctxt));
107 	free(kbdintctxt);
108 }
109 /* get next device */
110 static int
111 kbdint_next_device(Authctxt *authctxt, KbdintAuthctxt *kbdintctxt)
112 {
113 	size_t len;
114 	char *t;
115 	int i;
116 
117 	if (kbdintctxt->device)
118 		kbdint_reset_device(kbdintctxt);
119 	do {
120 		len = kbdintctxt->devices ?
121 		    strcspn(kbdintctxt->devices, ",") : 0;
122 
123 		if (len == 0)
124 			break;
125 		for (i = 0; devices[i]; i++) {
126 			if (!auth2_method_allowed(authctxt,
127 			    "keyboard-interactive", devices[i]->name))
128 				continue;
129 			if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
130 				kbdintctxt->device = devices[i];
131 		}
132 		t = kbdintctxt->devices;
133 		kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
134 		free(t);
135 		debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
136 		    kbdintctxt->devices : "<empty>");
137 	} while (kbdintctxt->devices && !kbdintctxt->device);
138 
139 	return kbdintctxt->device ? 1 : 0;
140 }
141 
142 /*
143  * try challenge-response, set authctxt->postponed if we have to
144  * wait for the response.
145  */
146 int
147 auth2_challenge(Authctxt *authctxt, char *devs)
148 {
149 	debug("auth2_challenge: user=%s devs=%s",
150 	    authctxt->user ? authctxt->user : "<nouser>",
151 	    devs ? devs : "<no devs>");
152 
153 	if (authctxt->user == NULL || !devs)
154 		return 0;
155 	if (authctxt->kbdintctxt == NULL)
156 		authctxt->kbdintctxt = kbdint_alloc(devs);
157 	return auth2_challenge_start(authctxt);
158 }
159 
160 /* unregister kbd-int callbacks and context */
161 void
162 auth2_challenge_stop(Authctxt *authctxt)
163 {
164 	/* unregister callback */
165 	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
166 	if (authctxt->kbdintctxt != NULL) {
167 		kbdint_free(authctxt->kbdintctxt);
168 		authctxt->kbdintctxt = NULL;
169 	}
170 }
171 
172 /* side effect: sets authctxt->postponed if a reply was sent*/
173 static int
174 auth2_challenge_start(Authctxt *authctxt)
175 {
176 	KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
177 
178 	debug2("auth2_challenge_start: devices %s",
179 	    kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
180 
181 	if (kbdint_next_device(authctxt, kbdintctxt) == 0) {
182 		auth2_challenge_stop(authctxt);
183 		return 0;
184 	}
185 	debug("auth2_challenge_start: trying authentication method '%s'",
186 	    kbdintctxt->device->name);
187 
188 	if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
189 		auth2_challenge_stop(authctxt);
190 		return 0;
191 	}
192 	if (send_userauth_info_request(authctxt) == 0) {
193 		auth2_challenge_stop(authctxt);
194 		return 0;
195 	}
196 	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
197 	    &input_userauth_info_response);
198 
199 	authctxt->postponed = 1;
200 	return 0;
201 }
202 
203 static int
204 send_userauth_info_request(Authctxt *authctxt)
205 {
206 	KbdintAuthctxt *kbdintctxt;
207 	char *name, *instr, **prompts;
208 	u_int i, *echo_on;
209 
210 	kbdintctxt = authctxt->kbdintctxt;
211 	if (kbdintctxt->device->query(kbdintctxt->ctxt,
212 	    &name, &instr, &kbdintctxt->nreq, &prompts, &echo_on))
213 		return 0;
214 
215 	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
216 	packet_put_cstring(name);
217 	packet_put_cstring(instr);
218 	packet_put_cstring("");		/* language not used */
219 	packet_put_int(kbdintctxt->nreq);
220 	for (i = 0; i < kbdintctxt->nreq; i++) {
221 		packet_put_cstring(prompts[i]);
222 		packet_put_char(echo_on[i]);
223 	}
224 	packet_send();
225 	packet_write_wait();
226 
227 	for (i = 0; i < kbdintctxt->nreq; i++)
228 		free(prompts[i]);
229 	free(prompts);
230 	free(echo_on);
231 	free(name);
232 	free(instr);
233 	return 1;
234 }
235 
236 static void
237 input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
238 {
239 	Authctxt *authctxt = ctxt;
240 	KbdintAuthctxt *kbdintctxt;
241 	int authenticated = 0, res;
242 	u_int i, nresp;
243 	const char *devicename = NULL;
244 	char **response = NULL;
245 
246 	if (authctxt == NULL)
247 		fatal("input_userauth_info_response: no authctxt");
248 	kbdintctxt = authctxt->kbdintctxt;
249 	if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
250 		fatal("input_userauth_info_response: no kbdintctxt");
251 	if (kbdintctxt->device == NULL)
252 		fatal("input_userauth_info_response: no device");
253 
254 	authctxt->postponed = 0;	/* reset */
255 	nresp = packet_get_int();
256 	if (nresp != kbdintctxt->nreq)
257 		fatal("input_userauth_info_response: wrong number of replies");
258 	if (nresp > 100)
259 		fatal("input_userauth_info_response: too many replies");
260 	if (nresp > 0) {
261 		response = xcalloc(nresp, sizeof(char *));
262 		for (i = 0; i < nresp; i++)
263 			response[i] = packet_get_string(NULL);
264 	}
265 	packet_check_eom();
266 
267 	res = kbdintctxt->device->respond(kbdintctxt->ctxt, nresp, response);
268 
269 	for (i = 0; i < nresp; i++) {
270 		explicit_bzero(response[i], strlen(response[i]));
271 		free(response[i]);
272 	}
273 	free(response);
274 
275 	switch (res) {
276 	case 0:
277 		/* Success! */
278 		authenticated = authctxt->valid ? 1 : 0;
279 		break;
280 	case 1:
281 		/* Authentication needs further interaction */
282 		if (send_userauth_info_request(authctxt) == 1)
283 			authctxt->postponed = 1;
284 		break;
285 	default:
286 		/* Failure! */
287 		break;
288 	}
289 	devicename = kbdintctxt->device->name;
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, "keyboard-interactive",
300 	    devicename);
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