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 /*
  26  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  27  * Use is subject to license terms.
  28  */
  29 
  30 #include "includes.h"
  31 RCSID("$OpenBSD: auth2-chall.c,v 1.20 2002/06/30 21:59:45 deraadt Exp $");
  32 
  33 #include "ssh2.h"
  34 #include "auth.h"
  35 #include "buffer.h"
  36 #include "packet.h"
  37 #include "xmalloc.h"
  38 #include "dispatch.h"
  39 #include "auth.h"
  40 #include "log.h"
  41 
  42 #ifndef lint
  43 static void auth2_challenge_start(Authctxt *);
  44 static int send_userauth_info_request(Authctxt *);
  45 static void input_userauth_info_response(int, u_int32_t, void *);
  46 
  47 #ifdef BSD_AUTH
  48 extern KbdintDevice bsdauth_device;
  49 #else
  50 #ifdef SKEY
  51 extern KbdintDevice skey_device;
  52 #endif
  53 #endif
  54 
  55 KbdintDevice *devices[] = {
  56 #ifdef BSD_AUTH
  57         &bsdauth_device,
  58 #else
  59 #ifdef SKEY
  60         &skey_device,
  61 #endif
  62 #endif
  63         NULL
  64 };
  65 
  66 typedef struct KbdintAuthctxt KbdintAuthctxt;
  67 struct KbdintAuthctxt
  68 {
  69         char *devices;
  70         void *ctxt;
  71         KbdintDevice *device;
  72         u_int nreq;
  73 };
  74 
  75 static KbdintAuthctxt *
  76 kbdint_alloc(const char *devs)
  77 {
  78         KbdintAuthctxt *kbdintctxt;
  79         Buffer b;
  80         int i;
  81 
  82         kbdintctxt = xmalloc(sizeof(KbdintAuthctxt));
  83         if (strcmp(devs, "") == 0) {
  84                 buffer_init(&b);
  85                 for (i = 0; devices[i]; i++) {
  86                         if (buffer_len(&b) > 0)
  87                                 buffer_append(&b, ",", 1);
  88                         buffer_append(&b, devices[i]->name,
  89                             strlen(devices[i]->name));
  90                 }
  91                 buffer_append(&b, "\0", 1);
  92                 kbdintctxt->devices = xstrdup(buffer_ptr(&b));
  93                 buffer_free(&b);
  94         } else {
  95                 kbdintctxt->devices = xstrdup(devs);
  96         }
  97         debug("kbdint_alloc: devices '%s'", kbdintctxt->devices);
  98         kbdintctxt->ctxt = NULL;
  99         kbdintctxt->device = NULL;
 100         kbdintctxt->nreq = 0;
 101 
 102         return kbdintctxt;
 103 }
 104 static void
 105 kbdint_reset_device(KbdintAuthctxt *kbdintctxt)
 106 {
 107         if (kbdintctxt->ctxt) {
 108                 kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
 109                 kbdintctxt->ctxt = NULL;
 110         }
 111         kbdintctxt->device = NULL;
 112 }
 113 static void
 114 kbdint_free(KbdintAuthctxt *kbdintctxt)
 115 {
 116         if (kbdintctxt->device)
 117                 kbdint_reset_device(kbdintctxt);
 118         if (kbdintctxt->devices) {
 119                 xfree(kbdintctxt->devices);
 120                 kbdintctxt->devices = NULL;
 121         }
 122         xfree(kbdintctxt);
 123 }
 124 /* get next device */
 125 static int
 126 kbdint_next_device(KbdintAuthctxt *kbdintctxt)
 127 {
 128         size_t len;
 129         char *t;
 130         int i;
 131 
 132         if (kbdintctxt->device)
 133                 kbdint_reset_device(kbdintctxt);
 134         do {
 135                 len = kbdintctxt->devices ?
 136                     strcspn(kbdintctxt->devices, ",") : 0;
 137 
 138                 if (len == 0)
 139                         break;
 140                 for (i = 0; devices[i]; i++)
 141                         if (strncmp(kbdintctxt->devices, devices[i]->name, len) == 0)
 142                                 kbdintctxt->device = devices[i];
 143                 t = kbdintctxt->devices;
 144                 kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
 145                 xfree(t);
 146                 debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
 147                    kbdintctxt->devices : "<empty>");
 148         } while (kbdintctxt->devices && !kbdintctxt->device);
 149 
 150         return kbdintctxt->device ? 1 : 0;
 151 }
 152 
 153 /*
 154  * try challenge-response, set authctxt->method->postponed if we have to
 155  * wait for the response.
 156  */
 157 void
 158 auth2_challenge(Authctxt *authctxt, char *devs)
 159 {
 160         debug("auth2_challenge: user=%s devs=%s",
 161             authctxt->user ? authctxt->user : "<nouser>",
 162             devs ? devs : "<no devs>");
 163 
 164         if (authctxt->user == NULL || !devs)
 165                 return;
 166         if (authctxt->method->method_data != NULL) {
 167                 auth2_challenge_abandon(authctxt);
 168                 authctxt->method->abandoned = 0;
 169         }
 170         authctxt->method->method_data = (void *) kbdint_alloc(devs);
 171         auth2_challenge_start(authctxt);
 172 }
 173 
 174 /* unregister kbd-int callbacks and context */
 175 static void
 176 auth2_challenge_stop(Authctxt *authctxt)
 177 {
 178         /* unregister callback */
 179         dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
 180         if (authctxt->method->method_data != NULL)  {
 181                 kbdint_free((KbdintAuthctxt *) authctxt->method->method_data);
 182                 authctxt->method->method_data = NULL;
 183         }
 184 }
 185 
 186 void
 187 auth2_challenge_abandon(Authctxt *authctxt)
 188 {
 189         auth2_challenge_stop(authctxt);
 190         authctxt->method->abandoned = 1;
 191         authctxt->method->postponed = 0;
 192         authctxt->method->authenticated = 0;
 193         authctxt->method->abandons++;
 194         authctxt->method->attempts++;
 195 }
 196 
 197 /* side effect: sets authctxt->method->postponed if a reply was sent*/
 198 static void
 199 auth2_challenge_start(Authctxt *authctxt)
 200 {
 201         KbdintAuthctxt *kbdintctxt = (KbdintAuthctxt *)
 202                                 authctxt->method->method_data;
 203 
 204         debug2("auth2_challenge_start: devices %s",
 205             kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
 206 
 207         if (kbdint_next_device(kbdintctxt) == 0) {
 208                 auth2_challenge_stop(authctxt);
 209                 return;
 210         }
 211         debug("auth2_challenge_start: trying authentication method '%s'",
 212             kbdintctxt->device->name);
 213 
 214         if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
 215                 auth2_challenge_stop(authctxt);
 216                 return;
 217         }
 218         if (send_userauth_info_request(authctxt) == 0) {
 219                 auth2_challenge_stop(authctxt);
 220                 return;
 221         }
 222         dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
 223             &input_userauth_info_response);
 224 
 225         authctxt->method->postponed = 1;
 226 }
 227 
 228 static int
 229 send_userauth_info_request(Authctxt *authctxt)
 230 {
 231         KbdintAuthctxt *kbdintctxt;
 232         char *name, *instr, **prompts;
 233         int i;
 234         u_int *echo_on;
 235 
 236         kbdintctxt = (KbdintAuthctxt *) authctxt->method->method_data;
 237         if (kbdintctxt->device->query(kbdintctxt->ctxt,
 238             &name, &instr, &kbdintctxt->nreq, &prompts, &echo_on))
 239                 return 0;
 240 
 241         packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
 242         packet_put_cstring(name);
 243         packet_put_utf8_cstring(instr);
 244         packet_put_cstring("");         /* language not used */
 245         packet_put_int(kbdintctxt->nreq);
 246         for (i = 0; i < kbdintctxt->nreq; i++) {
 247                 packet_put_utf8_cstring(prompts[i]);
 248                 packet_put_char(echo_on[i]);
 249         }
 250         packet_send();
 251         packet_write_wait();
 252 
 253         for (i = 0; i < kbdintctxt->nreq; i++)
 254                 xfree(prompts[i]);
 255         xfree(prompts);
 256         xfree(echo_on);
 257         xfree(name);
 258         xfree(instr);
 259         return 1;
 260 }
 261 
 262 static void
 263 input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
 264 {
 265         Authctxt *authctxt = ctxt;
 266         KbdintAuthctxt *kbdintctxt;
 267         int i, res, len;
 268         u_int nresp;
 269         char **response = NULL, *method;
 270 
 271         if (authctxt == NULL)
 272                 fatal("input_userauth_info_response: no authctxt");
 273         kbdintctxt = (KbdintAuthctxt *) authctxt->method->method_data;
 274         if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
 275                 fatal("input_userauth_info_response: no kbdintctxt");
 276         if (kbdintctxt->device == NULL)
 277                 fatal("input_userauth_info_response: no device");
 278 
 279         nresp = packet_get_int();
 280         if (nresp != kbdintctxt->nreq)
 281                 fatal("input_userauth_info_response: wrong number of replies");
 282         if (nresp > 100)
 283                 fatal("input_userauth_info_response: too many replies");
 284         if (nresp > 0) {
 285                 response = xmalloc(nresp * sizeof(char *));
 286                 for (i = 0; i < nresp; i++)
 287                         response[i] = packet_get_string(NULL);
 288         }
 289         packet_check_eom();
 290 
 291         if (authctxt->valid) {
 292                 res = kbdintctxt->device->respond(kbdintctxt->ctxt,
 293                     nresp, response);
 294         } else {
 295                 res = -1;
 296         }
 297 
 298         for (i = 0; i < nresp; i++) {
 299                 memset(response[i], 'r', strlen(response[i]));
 300                 xfree(response[i]);
 301         }
 302         if (response)
 303                 xfree(response);
 304 
 305         authctxt->method->postponed = 0;  /* reset */
 306         switch (res) {
 307         case 0:
 308                 /* Success! */
 309                 authctxt->method->authenticated = 1;
 310                 break;
 311         case 1:
 312                 /* Authentication needs further interaction */
 313                 if (send_userauth_info_request(authctxt) == 1) {
 314                         authctxt->method->postponed = 1;
 315                 }
 316                 break;
 317         default:
 318                 /* Failure! */
 319                 break;
 320         }
 321 
 322 
 323         len = strlen("keyboard-interactive") + 2 +
 324                 strlen(kbdintctxt->device->name);
 325         method = xmalloc(len);
 326         snprintf(method, len, "keyboard-interactive/%s",
 327             kbdintctxt->device->name);
 328 
 329         if (authctxt->method->authenticated || authctxt->method->abandoned) {
 330                 auth2_challenge_stop(authctxt);
 331         } else {
 332                 /* start next device */
 333                 /* may set authctxt->method->postponed */
 334                 auth2_challenge_start(authctxt);
 335         }
 336         userauth_finish(authctxt, method);
 337         xfree(method);
 338 }
 339 
 340 void
 341 privsep_challenge_enable(void)
 342 {
 343 #ifdef BSD_AUTH
 344         extern KbdintDevice mm_bsdauth_device;
 345 #endif
 346 #ifdef SKEY
 347         extern KbdintDevice mm_skey_device;
 348 #endif
 349         /* As long as SSHv1 has devices[0] hard coded this is fine */
 350 #ifdef BSD_AUTH
 351         devices[0] = &mm_bsdauth_device;
 352 #else
 353 #ifdef SKEY
 354         devices[0] = &mm_skey_device;
 355 #endif
 356 #endif
 357 }
 358 #endif /* lint */