1 /*
   2  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
   3  */
   4 
   5 /* LOGIN is a PLAIN-like authenticator, but for older deployments. */
   6 
   7 /* Login SASL plugin
   8  * Rob Siemborski (SASLv2 Conversion)
   9  * contributed by Rainer Schoepf <schoepf@uni-mainz.de>
  10  * based on PLAIN, by Tim Martin <tmartin@andrew.cmu.edu>
  11  * $Id: login.c,v 1.25 2003/02/13 19:56:04 rjs3 Exp $
  12  */
  13 /* 
  14  * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
  15  *
  16  * Redistribution and use in source and binary forms, with or without
  17  * modification, are permitted provided that the following conditions
  18  * are met:
  19  *
  20  * 1. Redistributions of source code must retain the above copyright
  21  *    notice, this list of conditions and the following disclaimer. 
  22  *
  23  * 2. Redistributions in binary form must reproduce the above copyright
  24  *    notice, this list of conditions and the following disclaimer in
  25  *    the documentation and/or other materials provided with the
  26  *    distribution.
  27  *
  28  * 3. The name "Carnegie Mellon University" must not be used to
  29  *    endorse or promote products derived from this software without
  30  *    prior written permission. For permission or any other legal
  31  *    details, please contact  
  32  *      Office of Technology Transfer
  33  *      Carnegie Mellon University
  34  *      5000 Forbes Avenue
  35  *      Pittsburgh, PA  15213-3890
  36  *      (412) 268-4387, fax: (412) 268-7395
  37  *      tech-transfer@andrew.cmu.edu
  38  *
  39  * 4. Redistributions of any form whatsoever must retain the following
  40  *    acknowledgment:
  41  *    "This product includes software developed by Computing Services
  42  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
  43  *
  44  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
  45  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  46  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
  47  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  48  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  49  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
  50  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  51  */
  52 
  53 #include <config.h>
  54 #include <stdio.h>
  55 #include <ctype.h>
  56 #include <sasl.h>
  57 #include <saslplug.h>
  58 
  59 #include "plugin_common.h"
  60 
  61 #ifndef _SUN_SDK_
  62 #ifdef WIN32
  63 /* This must be after sasl.h */
  64 # include "saslLOGIN.h"
  65 #endif /* WIN32 */
  66 #endif /* !_SUN_SDK_ */
  67 
  68 /*****************************  Common Section  *****************************/
  69 
  70 #ifndef _SUN_SDK_
  71 static const char plugin_id[] = "$Id: login.c,v 1.25 2003/02/13 19:56:04 rjs3 Exp $";
  72 #endif /* !_SUN_SDK_ */
  73 
  74 /*****************************  Server Section  *****************************/
  75 
  76 typedef struct context {
  77     int state;
  78 
  79     char *username;
  80     size_t username_len;
  81 } server_context_t;
  82 
  83 static int login_server_mech_new(void *glob_context __attribute__((unused)), 
  84                                  sasl_server_params_t *sparams,
  85                                  const char *challenge __attribute__((unused)),
  86                                  unsigned challen __attribute__((unused)),
  87                                  void **conn_context)
  88 {
  89     server_context_t *text;
  90     
  91     /* holds state are in */
  92     text = sparams->utils->malloc(sizeof(server_context_t));
  93     if (text == NULL) {
  94         MEMERROR( sparams->utils );
  95         return SASL_NOMEM;
  96     }
  97     
  98     memset(text, 0, sizeof(server_context_t));
  99     
 100     text->state = 1;
 101     
 102     *conn_context = text;
 103     
 104     return SASL_OK;
 105 }
 106 
 107 #define USERNAME_CHALLENGE "Username:"
 108 #define PASSWORD_CHALLENGE "Password:"
 109 
 110 static int login_server_mech_step(void *conn_context,
 111                                   sasl_server_params_t *params,
 112                                   const char *clientin,
 113                                   unsigned clientinlen,
 114                                   const char **serverout,
 115                                   unsigned *serveroutlen,
 116                                   sasl_out_params_t *oparams)
 117 {
 118     server_context_t *text = (server_context_t *) conn_context;
 119     
 120     *serverout = NULL;
 121     *serveroutlen = 0;
 122     
 123     switch (text->state) {
 124 
 125     case 1:
 126         text->state = 2;
 127 
 128         /* Check inlen, (possibly we have already the user name) */
 129         /* In this case fall through to state 2 */
 130         if (clientinlen == 0) {
 131             /* demand username */
 132             
 133             *serveroutlen = strlen(USERNAME_CHALLENGE);
 134             *serverout = USERNAME_CHALLENGE;
 135 
 136             return SASL_CONTINUE;
 137         }
 138         
 139         
 140     case 2:
 141         /* Catch really long usernames */
 142         if (clientinlen > 1024) {
 143 #ifdef _SUN_SDK_
 144             params->utils->log(params->utils->conn, SASL_LOG_ERR,
 145                 "username too long (>1024 characters)");
 146 #else
 147             SETERROR(params->utils, "username too long (>1024 characters)");
 148 #endif  /* _SUN_SDK_ */
 149             return SASL_BADPROT;
 150         }
 151         
 152         /* get username */
 153         text->username =
 154             params->utils->malloc(sizeof(sasl_secret_t) + clientinlen + 1);
 155         if (!text->username) {
 156             MEMERROR( params->utils );
 157             return SASL_NOMEM;
 158         }
 159         
 160         strncpy(text->username, clientin, clientinlen);
 161         text->username_len = clientinlen;
 162         text->username[clientinlen] = '\0';
 163         
 164         /* demand password */
 165         *serveroutlen = strlen(PASSWORD_CHALLENGE);
 166         *serverout = PASSWORD_CHALLENGE;
 167         
 168         text->state = 3;
 169         
 170         return SASL_CONTINUE;
 171         
 172         
 173     case 3: {
 174         sasl_secret_t *password;
 175         int result;
 176         
 177         /* Catch really long passwords */
 178         if (clientinlen > 1024) {
 179 #ifdef _SUN_SDK_
 180             params->utils->log(params->utils->conn, SASL_LOG_ERR,
 181                      "clientinlen is > 1024 characters in LOGIN plugin");
 182 #else
 183             SETERROR(params->utils,
 184                      "clientinlen is > 1024 characters in LOGIN plugin");
 185 #endif  /* _SUN_SDK_ */
 186             return SASL_BADPROT;
 187         }
 188         
 189         /* get password */
 190         password =
 191             params->utils->malloc(sizeof(sasl_secret_t) + clientinlen + 1);
 192         if (!password) {
 193             MEMERROR(params->utils);
 194             return SASL_NOMEM;
 195         }
 196         
 197         strncpy((char *)password->data, clientin, clientinlen);
 198         password->data[clientinlen] = '\0';
 199         password->len = clientinlen;
 200 
 201         /* canonicalize username first, so that password verification is
 202          * done against the canonical id */
 203         result = params->canon_user(params->utils->conn, text->username,
 204                                     text->username_len,
 205                                     SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
 206         if (result != SASL_OK) {
 207                 _plug_free_secret(params->utils, &password);
 208                 return result;
 209         }
 210         
 211         /* verify_password - return sasl_ok on success */
 212         result = params->utils->checkpass(params->utils->conn,
 213                                         oparams->authid, oparams->alen,
 214                                         (char *)password->data, password->len);
 215         
 216         if (result != SASL_OK) {
 217             _plug_free_secret(params->utils, &password);
 218             return result;
 219         }
 220         
 221         if (params->transition) {
 222             params->transition(params->utils->conn,
 223                                (char *)password->data, password->len);
 224         }
 225         
 226         _plug_free_secret(params->utils, &password);
 227         
 228         *serverout = NULL;
 229         *serveroutlen = 0;
 230         
 231         oparams->doneflag = 1;
 232         oparams->mech_ssf = 0;
 233         oparams->maxoutbuf = 0;
 234         oparams->encode_context = NULL;
 235         oparams->encode = NULL;
 236         oparams->decode_context = NULL;
 237         oparams->decode = NULL;
 238         oparams->param_version = 0;
 239         
 240         return SASL_OK;
 241     }
 242 
 243 
 244     default:
 245         params->utils->log(NULL, SASL_LOG_ERR,
 246                            "Invalid LOGIN server step %d\n", text->state);
 247         return SASL_FAIL;
 248     }
 249     
 250     return SASL_FAIL; /* should never get here */
 251 }
 252 
 253 static void login_server_mech_dispose(void *conn_context,
 254                                       const sasl_utils_t *utils)
 255 {
 256     server_context_t *text = (server_context_t *) conn_context;
 257     
 258     if (!text) return;
 259     
 260     if (text->username) utils->free(text->username);
 261     
 262     utils->free(text);
 263 }
 264 
 265 static sasl_server_plug_t login_server_plugins[] = 
 266 {
 267     {
 268         "LOGIN",                        /* mech_name */
 269         0,                              /* max_ssf */
 270         SASL_SEC_NOANONYMOUS,           /* security_flags */
 271         0,                              /* features */
 272         NULL,                           /* glob_context */
 273         &login_server_mech_new,             /* mech_new */
 274         &login_server_mech_step,    /* mech_step */
 275         &login_server_mech_dispose, /* mech_dispose */
 276         NULL,                           /* mech_free */
 277         NULL,                           /* setpass */
 278         NULL,                           /* user_query */
 279         NULL,                           /* idle */
 280         NULL,                           /* mech_avail */
 281         NULL                            /* spare */
 282     }
 283 };
 284 
 285 int login_server_plug_init(sasl_utils_t *utils,
 286                            int maxversion,
 287                            int *out_version,
 288                            sasl_server_plug_t **pluglist,
 289                            int *plugcount)
 290 {
 291     if (maxversion < SASL_SERVER_PLUG_VERSION) {
 292         SETERROR(utils, "LOGIN version mismatch");
 293         return SASL_BADVERS;
 294     }
 295     
 296     *out_version = SASL_SERVER_PLUG_VERSION;
 297     *pluglist = login_server_plugins;
 298     *plugcount = 1;  
 299     
 300     return SASL_OK;
 301 }
 302 
 303 /*****************************  Client Section  *****************************/
 304 
 305 typedef struct client_context {
 306     int state;
 307 
 308 #ifdef _INTEGRATED_SOLARIS_
 309     void *h;
 310 #endif /* _INTEGRATED_SOLARIS_ */
 311     sasl_secret_t *password;
 312     unsigned int free_password; /* set if we need to free password */
 313 } client_context_t;
 314 
 315 static int login_client_mech_new(void *glob_context __attribute__((unused)),
 316                                  sasl_client_params_t *params,
 317                                  void **conn_context)
 318 {
 319     client_context_t *text;
 320     
 321     /* holds state are in */
 322     text = params->utils->malloc(sizeof(client_context_t));
 323     if (text == NULL) {
 324         MEMERROR(params->utils);
 325         return SASL_NOMEM;
 326     }
 327     
 328     memset(text, 0, sizeof(client_context_t));
 329     
 330     text->state = 1;
 331     
 332     *conn_context = text;
 333     
 334     return SASL_OK;
 335 }
 336 
 337 static int login_client_mech_step(void *conn_context,
 338                                   sasl_client_params_t *params,
 339                                   const char *serverin __attribute__((unused)),
 340                                   unsigned serverinlen __attribute__((unused)),
 341                                   sasl_interact_t **prompt_need,
 342                                   const char **clientout,
 343                                   unsigned *clientoutlen,
 344                                   sasl_out_params_t *oparams)
 345 {
 346     client_context_t *text = (client_context_t *) conn_context;
 347     
 348     *clientout = NULL;
 349     *clientoutlen = 0;
 350     
 351     switch (text->state) {
 352 
 353     case 1: {
 354         const char *user;
 355         int auth_result = SASL_OK;
 356         int pass_result = SASL_OK;
 357         int result;
 358         
 359         /* check if sec layer strong enough */
 360         if (params->props.min_ssf > params->external_ssf) {
 361 #ifdef _INTEGRATED_SOLARIS_
 362             params->utils->log(params->utils->conn, SASL_LOG_ERR,
 363                 gettext("SSF requested of LOGIN plugin"));
 364 #else
 365             SETERROR( params->utils, "SSF requested of LOGIN plugin");
 366 #endif /* _INTEGRATED_SOLARIS_ */
 367             return SASL_TOOWEAK;
 368         }
 369         
 370         /* try to get the userid */
 371         /* Note: we want to grab the authname and not the userid, which is
 372          *       who we AUTHORIZE as, and will be the same as the authname
 373          *       for the LOGIN mech.
 374          */
 375         if (oparams->user == NULL) {
 376             auth_result = _plug_get_authid(params->utils, &user, prompt_need);
 377             
 378             if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
 379                 return auth_result;
 380         }
 381         
 382         /* try to get the password */
 383         if (text->password == NULL) {
 384             pass_result = _plug_get_password(params->utils, &text->password,
 385                                              &text->free_password, prompt_need);
 386             
 387             if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
 388                 return pass_result;
 389         }
 390         
 391         /* free prompts we got */
 392         if (prompt_need && *prompt_need) {
 393             params->utils->free(*prompt_need);
 394             *prompt_need = NULL;
 395         }
 396         
 397         /* if there are prompts not filled in */
 398         if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) {
 399             /* make the prompt list */
 400             result =
 401 #ifdef _INTEGRATED_SOLARIS_
 402                 _plug_make_prompts(params->utils, &text->h, prompt_need,
 403                     NULL, NULL,
 404                     auth_result == SASL_INTERACT ?
 405                     gettext("Please enter your authentication name") : NULL,
 406                     NULL,
 407                     pass_result == SASL_INTERACT ?
 408                     gettext("Please enter your password") : NULL, NULL,
 409                     NULL, NULL, NULL,
 410                     NULL, NULL, NULL);
 411 #else
 412                 _plug_make_prompts(params->utils, prompt_need,
 413                                    NULL, NULL,
 414                                    auth_result == SASL_INTERACT ?
 415                                    "Please enter your authentication name" : NULL,
 416                                    NULL,
 417                                    pass_result == SASL_INTERACT ?
 418                                    "Please enter your password" : NULL, NULL,
 419                                    NULL, NULL, NULL,
 420                                    NULL, NULL, NULL);
 421 #endif /* _INTEGRATED_SOLARIS_ */
 422             if (result != SASL_OK) return result;
 423             
 424             return SASL_INTERACT;
 425         }
 426         
 427         if (!text->password) {
 428             PARAMERROR(params->utils);
 429             return SASL_BADPARAM;
 430         }
 431     
 432         result = params->canon_user(params->utils->conn, user, 0,
 433                                     SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
 434         if (result != SASL_OK) return result;
 435         
 436         /* server should have sent request for username - we ignore it */
 437         if (!serverin) {
 438 #ifdef _SUN_SDK_
 439             params->utils->log(params->utils->conn, SASL_LOG_ERR,
 440                       "Server didn't issue challenge for USERNAME");
 441 #else
 442             SETERROR( params->utils,
 443                       "Server didn't issue challenge for USERNAME");
 444 #endif /* _SUN_SDK_ */
 445             return SASL_BADPROT;
 446         }
 447         
 448         if (!clientout) {
 449             PARAMERROR( params->utils );
 450             return SASL_BADPARAM;
 451         }
 452         
 453         if (clientoutlen) *clientoutlen = oparams->alen;
 454         *clientout = oparams->authid;
 455         
 456         text->state = 2;
 457         
 458         return SASL_CONTINUE;
 459     }
 460 
 461     case 2:
 462         /* server should have sent request for password - we ignore it */
 463         if (!serverin) {
 464 #ifdef _SUN_SDK_
 465             params->utils->log(params->utils->conn, SASL_LOG_ERR,
 466                       "Server didn't issue challenge for PASSWORD");
 467 #else
 468             SETERROR( params->utils,
 469                       "Server didn't issue challenge for PASSWORD");
 470 #endif /* _SUN_SDK_ */
 471             return SASL_BADPROT;
 472         }
 473         
 474         if (!clientout) {
 475             PARAMERROR(params->utils);
 476             return SASL_BADPARAM;
 477         }
 478         
 479         if (clientoutlen) *clientoutlen = text->password->len;
 480         *clientout = (char *)text->password->data;
 481         
 482         /* set oparams */
 483         oparams->doneflag = 1;
 484         oparams->mech_ssf = 0;
 485         oparams->maxoutbuf = 0;
 486         oparams->encode_context = NULL;
 487         oparams->encode = NULL;
 488         oparams->decode_context = NULL;
 489         oparams->decode = NULL;
 490         oparams->param_version = 0;
 491         
 492         return SASL_OK;
 493 
 494     default:
 495         params->utils->log(NULL, SASL_LOG_ERR,
 496                            "Invalid LOGIN client step %d\n", text->state);
 497         return SASL_FAIL;
 498     }
 499 
 500     return SASL_FAIL; /* should never get here */
 501 }
 502 
 503 static void login_client_mech_dispose(void *conn_context,
 504                                       const sasl_utils_t *utils)
 505 {
 506     client_context_t *text = (client_context_t *) conn_context;
 507     
 508     if (!text) return;
 509     
 510     /* free sensitive info */
 511     if (text->free_password) _plug_free_secret(utils, &(text->password));
 512 #ifdef _INTEGRATED_SOLARIS_
 513     convert_prompt(utils, &text->h, NULL);
 514 #endif /* _INTEGRATED_SOLARIS_ */
 515     
 516     utils->free(text);
 517 }
 518 
 519 static sasl_client_plug_t login_client_plugins[] = 
 520 {
 521     {
 522         "LOGIN",                        /* mech_name */
 523         0,                              /* max_ssf */
 524         SASL_SEC_NOANONYMOUS,           /* security_flags */
 525         SASL_FEAT_SERVER_FIRST,         /* features */
 526         NULL,                           /* required_prompts */
 527         NULL,                           /* glob_context */
 528         &login_client_mech_new,             /* mech_new */
 529         &login_client_mech_step,    /* mech_step */
 530         &login_client_mech_dispose, /* mech_dispose */
 531         NULL,                           /* mech_free */
 532         NULL,                           /* idle */
 533         NULL,                           /* spare */
 534         NULL                            /* spare */
 535     }
 536 };
 537 
 538 int login_client_plug_init(sasl_utils_t *utils,
 539                            int maxversion,
 540                            int *out_version,
 541                            sasl_client_plug_t **pluglist,
 542                            int *plugcount)
 543 {
 544     if (maxversion < SASL_CLIENT_PLUG_VERSION) {
 545         SETERROR(utils, "Version mismatch in LOGIN");
 546         return SASL_BADVERS;
 547     }
 548     
 549     *out_version = SASL_CLIENT_PLUG_VERSION;
 550     *pluglist = login_client_plugins;
 551     *plugcount = 1;
 552     
 553     return SASL_OK;
 554 }