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