1/* $OpenBSD: auth2-chall.c,v 1.54 2020/10/18 11:32:01 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 "includes.h" 28 29#include <sys/types.h> 30 31#include <stdlib.h> 32#include <stdio.h> 33#include <string.h> 34#include <stdarg.h> 35 36#include "xmalloc.h" 37#include "ssh2.h" 38#include "sshkey.h" 39#include "hostfile.h" 40#include "auth.h" 41#include "sshbuf.h" 42#include "packet.h" 43#include "dispatch.h" 44#include "ssherr.h" 45#include "log.h" 46#include "misc.h" 47#include "servconf.h" 48 49/* import */ 50extern ServerOptions options; 51 52static int auth2_challenge_start(struct ssh *); 53static int send_userauth_info_request(struct ssh *); 54static int input_userauth_info_response(int, u_int32_t, struct ssh *); 55 56#ifdef BSD_AUTH 57extern KbdintDevice bsdauth_device; 58#else 59#ifdef USE_PAM 60extern KbdintDevice sshpam_device; 61#endif 62#endif 63 64KbdintDevice *devices[] = { 65#ifdef BSD_AUTH 66 &bsdauth_device, 67#else 68#ifdef USE_PAM 69 &sshpam_device, 70#endif 71#endif 72 NULL 73}; 74 75typedef struct KbdintAuthctxt KbdintAuthctxt; 76struct KbdintAuthctxt 77{ 78 char *devices; 79 void *ctxt; 80 KbdintDevice *device; 81 u_int nreq; 82 u_int devices_done; 83}; 84 85#ifdef USE_PAM 86void 87remove_kbdint_device(const char *devname) 88{ 89 int i, j; 90 91 for (i = 0; devices[i] != NULL; i++) 92 if (strcmp(devices[i]->name, devname) == 0) { 93 for (j = i; devices[j] != NULL; j++) 94 devices[j] = devices[j+1]; 95 i--; 96 } 97} 98#endif 99 100static KbdintAuthctxt * 101kbdint_alloc(const char *devs) 102{ 103 KbdintAuthctxt *kbdintctxt; 104 struct sshbuf *b; 105 int i, r; 106 107#ifdef USE_PAM 108 if (!options.use_pam) 109 remove_kbdint_device("pam"); 110#endif 111 112 kbdintctxt = xcalloc(1, sizeof(KbdintAuthctxt)); 113 if (strcmp(devs, "") == 0) { 114 if ((b = sshbuf_new()) == NULL) 115 fatal_f("sshbuf_new failed"); 116 for (i = 0; devices[i]; i++) { 117 if ((r = sshbuf_putf(b, "%s%s", 118 sshbuf_len(b) ? "," : "", devices[i]->name)) != 0) 119 fatal_fr(r, "buffer error"); 120 } 121 if ((kbdintctxt->devices = sshbuf_dup_string(b)) == NULL) 122 fatal_f("sshbuf_dup_string failed"); 123 sshbuf_free(b); 124 } else { 125 kbdintctxt->devices = xstrdup(devs); 126 } 127 debug("kbdint_alloc: devices '%s'", kbdintctxt->devices); 128 kbdintctxt->ctxt = NULL; 129 kbdintctxt->device = NULL; 130 kbdintctxt->nreq = 0; 131 132 return kbdintctxt; 133} 134static void 135kbdint_reset_device(KbdintAuthctxt *kbdintctxt) 136{ 137 if (kbdintctxt->ctxt) { 138 kbdintctxt->device->free_ctx(kbdintctxt->ctxt); 139 kbdintctxt->ctxt = NULL; 140 } 141 kbdintctxt->device = NULL; 142} 143static void 144kbdint_free(KbdintAuthctxt *kbdintctxt) 145{ 146 if (kbdintctxt->device) 147 kbdint_reset_device(kbdintctxt); 148 free(kbdintctxt->devices); 149 freezero(kbdintctxt, sizeof(*kbdintctxt)); 150} 151/* get next device */ 152static int 153kbdint_next_device(Authctxt *authctxt, KbdintAuthctxt *kbdintctxt) 154{ 155 size_t len; 156 char *t; 157 int i; 158 159 if (kbdintctxt->device) 160 kbdint_reset_device(kbdintctxt); 161 do { 162 len = kbdintctxt->devices ? 163 strcspn(kbdintctxt->devices, ",") : 0; 164 165 if (len == 0) 166 break; 167 for (i = 0; devices[i]; i++) { 168 if ((kbdintctxt->devices_done & (1 << i)) != 0 || 169 !auth2_method_allowed(authctxt, 170 "keyboard-interactive", devices[i]->name)) 171 continue; 172 if (strncmp(kbdintctxt->devices, devices[i]->name, 173 len) == 0) { 174 kbdintctxt->device = devices[i]; 175 kbdintctxt->devices_done |= 1 << i; 176 } 177 } 178 t = kbdintctxt->devices; 179 kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL; 180 free(t); 181 debug2("kbdint_next_device: devices %s", kbdintctxt->devices ? 182 kbdintctxt->devices : "<empty>"); 183 } while (kbdintctxt->devices && !kbdintctxt->device); 184 185 return kbdintctxt->device ? 1 : 0; 186} 187 188/* 189 * try challenge-response, set authctxt->postponed if we have to 190 * wait for the response. 191 */ 192int 193auth2_challenge(struct ssh *ssh, char *devs) 194{ 195 Authctxt *authctxt = ssh->authctxt; 196 debug("auth2_challenge: user=%s devs=%s", 197 authctxt->user ? authctxt->user : "<nouser>", 198 devs ? devs : "<no devs>"); 199 200 if (authctxt->user == NULL || !devs) 201 return 0; 202 if (authctxt->kbdintctxt == NULL) 203 authctxt->kbdintctxt = kbdint_alloc(devs); 204 return auth2_challenge_start(ssh); 205} 206 207/* unregister kbd-int callbacks and context */ 208void 209auth2_challenge_stop(struct ssh *ssh) 210{ 211 Authctxt *authctxt = ssh->authctxt; 212 /* unregister callback */ 213 ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL); 214 if (authctxt->kbdintctxt != NULL) { 215 kbdint_free(authctxt->kbdintctxt); 216 authctxt->kbdintctxt = NULL; 217 } 218} 219 220/* side effect: sets authctxt->postponed if a reply was sent*/ 221static int 222auth2_challenge_start(struct ssh *ssh) 223{ 224 Authctxt *authctxt = ssh->authctxt; 225 KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt; 226 227 debug2("auth2_challenge_start: devices %s", 228 kbdintctxt->devices ? kbdintctxt->devices : "<empty>"); 229 230 if (kbdint_next_device(authctxt, kbdintctxt) == 0) { 231 auth2_challenge_stop(ssh); 232 return 0; 233 } 234 debug("auth2_challenge_start: trying authentication method '%s'", 235 kbdintctxt->device->name); 236 237 if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) { 238 auth2_challenge_stop(ssh); 239 return 0; 240 } 241 if (send_userauth_info_request(ssh) == 0) { 242 auth2_challenge_stop(ssh); 243 return 0; 244 } 245 ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE, 246 &input_userauth_info_response); 247 248 authctxt->postponed = 1; 249 return 0; 250} 251 252static int 253send_userauth_info_request(struct ssh *ssh) 254{ 255 Authctxt *authctxt = ssh->authctxt; 256 KbdintAuthctxt *kbdintctxt; 257 char *name, *instr, **prompts; 258 u_int r, i, *echo_on; 259 260 kbdintctxt = authctxt->kbdintctxt; 261 if (kbdintctxt->device->query(kbdintctxt->ctxt, 262 &name, &instr, &kbdintctxt->nreq, &prompts, &echo_on)) 263 return 0; 264 265 if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_INFO_REQUEST)) != 0 || 266 (r = sshpkt_put_cstring(ssh, name)) != 0 || 267 (r = sshpkt_put_cstring(ssh, instr)) != 0 || 268 (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language not used */ 269 (r = sshpkt_put_u32(ssh, kbdintctxt->nreq)) != 0) 270 fatal_fr(r, "start packet"); 271 for (i = 0; i < kbdintctxt->nreq; i++) { 272 if ((r = sshpkt_put_cstring(ssh, prompts[i])) != 0 || 273 (r = sshpkt_put_u8(ssh, echo_on[i])) != 0) 274 fatal_fr(r, "assemble packet"); 275 } 276 if ((r = sshpkt_send(ssh)) != 0 || 277 (r = ssh_packet_write_wait(ssh)) != 0) 278 fatal_fr(r, "send packet"); 279 280 for (i = 0; i < kbdintctxt->nreq; i++) 281 free(prompts[i]); 282 free(prompts); 283 free(echo_on); 284 free(name); 285 free(instr); 286 return 1; 287} 288 289static int 290input_userauth_info_response(int type, u_int32_t seq, struct ssh *ssh) 291{ 292 Authctxt *authctxt = ssh->authctxt; 293 KbdintAuthctxt *kbdintctxt; 294 int authenticated = 0, res; 295 int r; 296 u_int i, nresp; 297 const char *devicename = NULL; 298 char **response = NULL; 299 300 if (authctxt == NULL) 301 fatal_f("no authctxt"); 302 kbdintctxt = authctxt->kbdintctxt; 303 if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL) 304 fatal_f("no kbdintctxt"); 305 if (kbdintctxt->device == NULL) 306 fatal_f("no device"); 307 308 authctxt->postponed = 0; /* reset */ 309 if ((r = sshpkt_get_u32(ssh, &nresp)) != 0) 310 fatal_fr(r, "parse packet"); 311 if (nresp != kbdintctxt->nreq) 312 fatal_f("wrong number of replies"); 313 if (nresp > 100) 314 fatal_f("too many replies"); 315 if (nresp > 0) { 316 response = xcalloc(nresp, sizeof(char *)); 317 for (i = 0; i < nresp; i++) { 318 if ((r = sshpkt_get_cstring(ssh, &response[i], NULL)) != 0) 319 fatal_fr(r, "parse response"); 320 } 321 } 322 if ((r = sshpkt_get_end(ssh)) != 0) 323 fatal_fr(r, "parse packet"); 324 325 res = kbdintctxt->device->respond(kbdintctxt->ctxt, nresp, response); 326 327 for (i = 0; i < nresp; i++) { 328 explicit_bzero(response[i], strlen(response[i])); 329 free(response[i]); 330 } 331 free(response); 332 333 switch (res) { 334 case 0: 335 /* Success! */ 336 authenticated = authctxt->valid ? 1 : 0; 337 break; 338 case 1: 339 /* Authentication needs further interaction */ 340 if (send_userauth_info_request(ssh) == 1) 341 authctxt->postponed = 1; 342 break; 343 default: 344 /* Failure! */ 345 break; 346 } 347 devicename = kbdintctxt->device->name; 348 if (!authctxt->postponed) { 349 if (authenticated) { 350 auth2_challenge_stop(ssh); 351 } else { 352 /* start next device */ 353 /* may set authctxt->postponed */ 354 auth2_challenge_start(ssh); 355 } 356 } 357 userauth_finish(ssh, authenticated, "keyboard-interactive", 358 devicename); 359 return 0; 360} 361 362void 363privsep_challenge_enable(void) 364{ 365#if defined(BSD_AUTH) || defined(USE_PAM) 366 int n = 0; 367#endif 368#ifdef BSD_AUTH 369 extern KbdintDevice mm_bsdauth_device; 370#endif 371#ifdef USE_PAM 372 extern KbdintDevice mm_sshpam_device; 373#endif 374 375#ifdef BSD_AUTH 376 devices[n++] = &mm_bsdauth_device; 377#else 378#ifdef USE_PAM 379 devices[n++] = &mm_sshpam_device; 380#endif 381#endif 382} 383