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 __RCSID("$MirOS: src/usr.bin/ssh/auth2-chall.c,v 1.4 2008/12/16 22:13:26 tg Exp $");
43
44 static int auth2_challenge_start(Authctxt *);
45 static int send_userauth_info_request(Authctxt *);
46 static void input_userauth_info_response(int, u_int32_t, void *);
47
48 #ifdef BSD_AUTH
49 extern KbdintDevice bsdauth_device;
50 #endif
51
52 KbdintDevice *devices[] = {
53 #ifdef BSD_AUTH
54 &bsdauth_device,
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 u_int nreq;
66 };
67
68 static KbdintAuthctxt *
kbdint_alloc(const char * devs)69 kbdint_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 kbdintctxt->nreq = 0;
94
95 return kbdintctxt;
96 }
97 static void
kbdint_reset_device(KbdintAuthctxt * kbdintctxt)98 kbdint_reset_device(KbdintAuthctxt *kbdintctxt)
99 {
100 if (kbdintctxt->ctxt) {
101 kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
102 kbdintctxt->ctxt = NULL;
103 }
104 kbdintctxt->device = NULL;
105 }
106 static void
kbdint_free(KbdintAuthctxt * kbdintctxt)107 kbdint_free(KbdintAuthctxt *kbdintctxt)
108 {
109 if (kbdintctxt->device)
110 kbdint_reset_device(kbdintctxt);
111 if (kbdintctxt->devices) {
112 xfree(kbdintctxt->devices);
113 kbdintctxt->devices = NULL;
114 }
115 xfree(kbdintctxt);
116 }
117 /* get next device */
118 static int
kbdint_next_device(KbdintAuthctxt * kbdintctxt)119 kbdint_next_device(KbdintAuthctxt *kbdintctxt)
120 {
121 size_t len;
122 char *t;
123 int i;
124
125 if (kbdintctxt->device)
126 kbdint_reset_device(kbdintctxt);
127 do {
128 len = kbdintctxt->devices ?
129 strcspn(kbdintctxt->devices, ",") : 0;
130
131 if (len == 0)
132 break;
133 for (i = 0; devices[i]; i++)
134 if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
135 kbdintctxt->device = devices[i];
136 t = kbdintctxt->devices;
137 kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
138 xfree(t);
139 debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
140 kbdintctxt->devices : "<empty>");
141 } while (kbdintctxt->devices && !kbdintctxt->device);
142
143 return kbdintctxt->device ? 1 : 0;
144 }
145
146 /*
147 * try challenge-response, set authctxt->postponed if we have to
148 * wait for the response.
149 */
150 int
auth2_challenge(Authctxt * authctxt,char * devs)151 auth2_challenge(Authctxt *authctxt, char *devs)
152 {
153 debug("auth2_challenge: user=%s devs=%s",
154 authctxt->user ? authctxt->user : "<nouser>",
155 devs ? devs : "<no devs>");
156
157 if (authctxt->user == NULL || !devs)
158 return 0;
159 if (authctxt->kbdintctxt == NULL)
160 authctxt->kbdintctxt = kbdint_alloc(devs);
161 return auth2_challenge_start(authctxt);
162 }
163
164 /* unregister kbd-int callbacks and context */
165 void
auth2_challenge_stop(Authctxt * authctxt)166 auth2_challenge_stop(Authctxt *authctxt)
167 {
168 /* unregister callback */
169 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
170 if (authctxt->kbdintctxt != NULL) {
171 kbdint_free(authctxt->kbdintctxt);
172 authctxt->kbdintctxt = NULL;
173 }
174 }
175
176 /* side effect: sets authctxt->postponed if a reply was sent*/
177 static int
auth2_challenge_start(Authctxt * authctxt)178 auth2_challenge_start(Authctxt *authctxt)
179 {
180 KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
181
182 debug2("auth2_challenge_start: devices %s",
183 kbdintctxt->devices ? kbdintctxt->devices : "<empty>");
184
185 if (kbdint_next_device(kbdintctxt) == 0) {
186 auth2_challenge_stop(authctxt);
187 return 0;
188 }
189 debug("auth2_challenge_start: trying authentication method '%s'",
190 kbdintctxt->device->name);
191
192 if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
193 auth2_challenge_stop(authctxt);
194 return 0;
195 }
196 if (send_userauth_info_request(authctxt) == 0) {
197 auth2_challenge_stop(authctxt);
198 return 0;
199 }
200 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
201 &input_userauth_info_response);
202
203 authctxt->postponed = 1;
204 return 0;
205 }
206
207 static int
send_userauth_info_request(Authctxt * authctxt)208 send_userauth_info_request(Authctxt *authctxt)
209 {
210 KbdintAuthctxt *kbdintctxt;
211 char *name, *instr, **prompts;
212 u_int i, *echo_on;
213
214 kbdintctxt = authctxt->kbdintctxt;
215 if (kbdintctxt->device->query(kbdintctxt->ctxt,
216 &name, &instr, &kbdintctxt->nreq, &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(kbdintctxt->nreq);
224 for (i = 0; i < kbdintctxt->nreq; 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 < kbdintctxt->nreq; i++)
232 xfree(prompts[i]);
233 xfree(prompts);
234 xfree(echo_on);
235 xfree(name);
236 xfree(instr);
237 return 1;
238 }
239
240 static void
input_userauth_info_response(int type,u_int32_t seq,void * ctxt)241 input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
242 {
243 Authctxt *authctxt = ctxt;
244 KbdintAuthctxt *kbdintctxt;
245 int authenticated = 0, res;
246 u_int i, 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 != kbdintctxt->nreq)
260 fatal("input_userauth_info_response: wrong number of replies");
261 if (nresp > 100)
262 fatal("input_userauth_info_response: too many replies");
263 if (nresp > 0) {
264 response = xcalloc(nresp, sizeof(char *));
265 for (i = 0; i < nresp; i++)
266 response[i] = packet_get_string(NULL);
267 }
268 packet_check_eom();
269
270 res = kbdintctxt->device->respond(kbdintctxt->ctxt, nresp, response);
271
272 for (i = 0; i < nresp; i++) {
273 memset(response[i], 'r', strlen(response[i]));
274 xfree(response[i]);
275 }
276 if (response)
277 xfree(response);
278
279 switch (res) {
280 case 0:
281 /* Success! */
282 authenticated = authctxt->valid ? 1 : 0;
283 break;
284 case 1:
285 /* Authentication needs further interaction */
286 if (send_userauth_info_request(authctxt) == 1)
287 authctxt->postponed = 1;
288 break;
289 default:
290 /* Failure! */
291 break;
292 }
293
294 xasprintf(&method, "keyboard-interactive/%s", kbdintctxt->device->name);
295
296 if (!authctxt->postponed) {
297 if (authenticated) {
298 auth2_challenge_stop(authctxt);
299 } else {
300 /* start next device */
301 /* may set authctxt->postponed */
302 auth2_challenge_start(authctxt);
303 }
304 }
305 userauth_finish(authctxt, authenticated, method);
306 xfree(method);
307 }
308
309 void
privsep_challenge_enable(void)310 privsep_challenge_enable(void)
311 {
312 #ifdef BSD_AUTH
313 extern KbdintDevice mm_bsdauth_device;
314 /* As long as SSHv1 has devices[0] hard coded this is fine */
315 devices[0] = &mm_bsdauth_device;
316 #endif
317 }
318