1 /* 2 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 #pragma ident "%Z%%M% %I% %E% SMI" 6 7 /* SASL server API implementation 8 * Rob Siemborski 9 * Tim Martin 10 * $Id: checkpw.c,v 1.62 2003/03/19 18:25:27 rjs3 Exp $ 11 */ 12 /* 13 * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. 14 * 15 * Redistribution and use in source and binary forms, with or without 16 * modification, are permitted provided that the following conditions 17 * are met: 18 * 19 * 1. Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 22 * 2. Redistributions in binary form must reproduce the above copyright 23 * notice, this list of conditions and the following disclaimer in 24 * the documentation and/or other materials provided with the 25 * distribution. 26 * 27 * 3. The name "Carnegie Mellon University" must not be used to 28 * endorse or promote products derived from this software without 29 * prior written permission. For permission or any other legal 30 * details, please contact 31 * Office of Technology Transfer 32 * Carnegie Mellon University 33 * 5000 Forbes Avenue 34 * Pittsburgh, PA 15213-3890 35 * (412) 268-4387, fax: (412) 268-7395 36 * tech-transfer@andrew.cmu.edu 37 * 38 * 4. Redistributions of any form whatsoever must retain the following 39 * acknowledgment: 40 * "This product includes software developed by Computing Services 41 * at Carnegie Mellon University (http://www.cmu.edu/computing/)." 42 * 43 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO 44 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 45 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE 46 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 47 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 48 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 49 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 50 */ 51 52 #include <config.h> 53 54 /* checkpw stuff */ 55 56 #include <stdio.h> 57 #include "sasl.h" 58 #include "saslutil.h" 59 #include "saslplug.h" 60 #include "saslint.h" 61 62 #include <assert.h> 63 #ifdef HAVE_UNISTD_H 64 #include <unistd.h> 65 #endif 66 #include <fcntl.h> 67 #ifdef USE_DOORS 68 #include <sys/mman.h> 69 #include <door.h> 70 #endif 71 72 #include <stdlib.h> 73 74 #ifndef WIN32 75 #include <strings.h> 76 #include <netdb.h> 77 #include <netinet/in.h> 78 #include <sys/un.h> 79 #else 80 #include <string.h> 81 #endif 82 83 #include <sys/types.h> 84 #include <ctype.h> 85 86 #ifdef HAVE_PWD_H 87 #include <pwd.h> 88 #endif /* HAVE_PWD_H */ 89 #ifdef HAVE_SHADOW_H 90 #include <shadow.h> 91 #endif /* HAVE_SHADOW_H */ 92 93 #if defined(HAVE_PWCHECK) || defined(HAVE_SASLAUTHD) 94 # include <errno.h> 95 # include <sys/types.h> 96 # include <sys/socket.h> 97 # include <sys/un.h> 98 # ifdef HAVE_UNISTD_H 99 # include <unistd.h> 100 # endif 101 102 extern int errno; 103 #endif 104 105 106 /* we store the following secret to check plaintext passwords: 107 * 108 * <salt> \0 <secret> 109 * 110 * where <secret> = MD5(<salt>, "sasldb", <pass>) 111 */ 112 #ifdef _SUN_SDK_ 113 static int _sasl_make_plain_secret(const sasl_utils_t *utils, const char *salt, 114 const char *passwd, size_t passlen, 115 sasl_secret_t **secret) 116 #else 117 static int _sasl_make_plain_secret(const char *salt, 118 const char *passwd, size_t passlen, 119 sasl_secret_t **secret) 120 #endif /* _SUN_SDK_ */ 121 { 122 MD5_CTX ctx; 123 unsigned sec_len = 16 + 1 + 16; /* salt + "\0" + hash */ 124 125 #ifdef _SUN_SDK_ 126 *secret = (sasl_secret_t *)utils->malloc(sizeof(sasl_secret_t) + 127 sec_len * sizeof(char)); 128 #else 129 *secret = (sasl_secret_t *) sasl_ALLOC(sizeof(sasl_secret_t) + 130 sec_len * sizeof(char)); 131 #endif /* _SUN_SDK_ */ 132 if (*secret == NULL) { 133 return SASL_NOMEM; 134 } 135 136 _sasl_MD5Init(&ctx); 137 _sasl_MD5Update(&ctx, salt, 16); 138 _sasl_MD5Update(&ctx, "sasldb", 6); 139 _sasl_MD5Update(&ctx, passwd, passlen); 140 memcpy((*secret)->data, salt, 16); 141 (*secret)->data[16] = '\0'; 142 _sasl_MD5Final((*secret)->data + 17, &ctx); 143 (*secret)->len = sec_len; 144 145 return SASL_OK; 146 } 147 148 /* erase & dispose of a sasl_secret_t 149 */ 150 static int auxprop_verify_password(sasl_conn_t *conn, 151 const char *userstr, 152 const char *passwd, 153 const char *service __attribute__((unused)), 154 const char *user_realm __attribute__((unused))) 155 { 156 int ret = SASL_FAIL; 157 char *userid = NULL; 158 #ifndef _SUN_SDK_ 159 char *realm = NULL; 160 #endif /* !_SUN_SDK_ */ 161 int result = SASL_OK; 162 sasl_server_conn_t *sconn = (sasl_server_conn_t *)conn; 163 const char *password_request[] = { SASL_AUX_PASSWORD, 164 "*cmusaslsecretPLAIN", 165 NULL }; 166 struct propval auxprop_values[3]; 167 168 if (!conn || !userstr) 169 return SASL_BADPARAM; 170 171 /* We need to clear any previous results and re-canonify to 172 * ensure correctness */ 173 174 prop_clear(sconn->sparams->propctx, 0); 175 176 /* ensure its requested */ 177 result = prop_request(sconn->sparams->propctx, password_request); 178 179 if(result != SASL_OK) return result; 180 181 result = _sasl_canon_user(conn, userstr, 0, 182 SASL_CU_AUTHID | SASL_CU_AUTHZID, 183 &(conn->oparams)); 184 if(result != SASL_OK) return result; 185 186 result = prop_getnames(sconn->sparams->propctx, password_request, 187 auxprop_values); 188 if(result < 0) 189 return result; 190 191 if((!auxprop_values[0].name 192 || !auxprop_values[0].values || !auxprop_values[0].values[0]) 193 && (!auxprop_values[1].name 194 || !auxprop_values[1].values || !auxprop_values[1].values[0])) 195 return SASL_NOUSER; 196 197 /* It is possible for us to get useful information out of just 198 * the lookup, so we won't check that we have a password until now */ 199 if(!passwd) { 200 ret = SASL_BADPARAM; 201 goto done; 202 } 203 204 /* At the point this has been called, the username has been canonified 205 * and we've done the auxprop lookup. This should be easy. */ 206 if(auxprop_values[0].name 207 && auxprop_values[0].values 208 && auxprop_values[0].values[0] 209 && !strcmp(auxprop_values[0].values[0], passwd)) { 210 /* We have a plaintext version and it matched! */ 211 return SASL_OK; 212 } else if(auxprop_values[1].name 213 && auxprop_values[1].values 214 && auxprop_values[1].values[0]) { 215 const char *db_secret = auxprop_values[1].values[0]; 216 sasl_secret_t *construct; 217 218 #ifdef _SUN_SDK_ 219 ret = _sasl_make_plain_secret(sconn->sparams->utils, db_secret, 220 passwd, strlen(passwd), 221 &construct); 222 #else 223 ret = _sasl_make_plain_secret(db_secret, passwd, 224 strlen(passwd), 225 &construct); 226 #endif /* _SUN_SDK_ */ 227 if (ret != SASL_OK) { 228 goto done; 229 } 230 231 if (!memcmp(db_secret, construct->data, construct->len)) { 232 /* password verified! */ 233 ret = SASL_OK; 234 } else { 235 /* passwords do not match */ 236 ret = SASL_BADAUTH; 237 } 238 239 #ifdef _SUN_SDK_ 240 sconn->sparams->utils->free(construct); 241 #else 242 sasl_FREE(construct); 243 #endif /* _SUN_SDK_ */ 244 } else { 245 /* passwords do not match */ 246 ret = SASL_BADAUTH; 247 } 248 249 done: 250 #ifdef _SUN_SDK_ 251 if (userid) sconn->sparams->utils->free(userid); 252 #else 253 if (userid) sasl_FREE(userid); 254 if (realm) sasl_FREE(realm); 255 #endif /* _SUN_SDK_ */ 256 257 /* We're not going to erase the property here because other people 258 * may want it */ 259 return ret; 260 } 261 262 #ifdef DO_SASL_CHECKAPOP 263 int _sasl_auxprop_verify_apop(sasl_conn_t *conn, 264 const char *userstr, 265 const char *challenge, 266 const char *response, 267 const char *user_realm __attribute__((unused))) 268 { 269 int ret = SASL_BADAUTH; 270 char *userid = NULL; 271 #ifndef _SUN_SDK_ 272 char *realm = NULL; 273 #endif /* !_SUN_SDK_ */ 274 unsigned char digest[16]; 275 char digeststr[33]; 276 const char *password_request[] = { SASL_AUX_PASSWORD, NULL }; 277 struct propval auxprop_values[2]; 278 sasl_server_conn_t *sconn = (sasl_server_conn_t *)conn; 279 MD5_CTX ctx; 280 int i; 281 282 if (!conn || !userstr || !challenge || !response) 283 PARAMERROR(conn) 284 285 /* We've done the auxprop lookup already (in our caller) */ 286 /* sadly, APOP has no provision for storing secrets */ 287 ret = prop_getnames(sconn->sparams->propctx, password_request, 288 auxprop_values); 289 if(ret < 0) { 290 #ifdef _SUN_SDK_ 291 _sasl_log(conn, SASL_LOG_ERR, "could not perform password lookup"); 292 #else 293 sasl_seterror(conn, 0, "could not perform password lookup"); 294 #endif /* _SUN_SDK_ */ 295 goto done; 296 } 297 298 if(!auxprop_values[0].name || 299 !auxprop_values[0].values || 300 !auxprop_values[0].values[0]) { 301 #ifdef _INTEGRATED_SOLARIS_ 302 sasl_seterror(conn, 0, gettext("could not find password")); 303 #else 304 sasl_seterror(conn, 0, "could not find password"); 305 #endif /* _INTEGRATED_SOLARIS_ */ 306 ret = SASL_NOUSER; 307 goto done; 308 } 309 310 _sasl_MD5Init(&ctx); 311 _sasl_MD5Update(&ctx, challenge, strlen(challenge)); 312 _sasl_MD5Update(&ctx, auxprop_values[0].values[0], 313 strlen(auxprop_values[0].values[0])); 314 _sasl_MD5Final(digest, &ctx); 315 316 /* convert digest from binary to ASCII hex */ 317 for (i = 0; i < 16; i++) 318 sprintf(digeststr + (i*2), "%02x", digest[i]); 319 320 if (!strncasecmp(digeststr, response, 32)) { 321 /* password verified! */ 322 ret = SASL_OK; 323 } else { 324 /* passwords do not match */ 325 ret = SASL_BADAUTH; 326 } 327 328 done: 329 #ifdef _INTEGRATED_SOLARIS_ 330 if (ret == SASL_BADAUTH) sasl_seterror(conn, SASL_NOLOG, 331 gettext("login incorrect")); 332 #else 333 if (ret == SASL_BADAUTH) sasl_seterror(conn, SASL_NOLOG, 334 "login incorrect"); 335 #endif /* _INTEGRATED_SOLARIS_ */ 336 #ifdef _SUN_SDK_ 337 if (userid) sconn->sparams->utils->free(userid); 338 #else 339 if (userid) sasl_FREE(userid); 340 if (realm) sasl_FREE(realm); 341 #endif /* _SUN_SDK_ */ 342 343 return ret; 344 } 345 #endif /* DO_SASL_CHECKAPOP */ 346 347 #if defined(HAVE_PWCHECK) || defined(HAVE_SASLAUTHD) 348 /* 349 * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt' 350 * until all the data is written out or an error occurs. 351 */ 352 static int retry_writev(int fd, struct iovec *iov, int iovcnt) 353 { 354 int n; 355 int i; 356 int written = 0; 357 static int iov_max = 358 #ifdef MAXIOV 359 MAXIOV 360 #else 361 #ifdef IOV_MAX 362 IOV_MAX 363 #else 364 8192 365 #endif 366 #endif 367 ; 368 369 for (;;) { 370 while (iovcnt && iov[0].iov_len == 0) { 371 iov++; 372 iovcnt--; 373 } 374 375 if (!iovcnt) return written; 376 377 n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt); 378 if (n == -1) { 379 if (errno == EINVAL && iov_max > 10) { 380 iov_max /= 2; 381 continue; 382 } 383 if (errno == EINTR) continue; 384 return -1; 385 } 386 387 written += n; 388 389 for (i = 0; i < iovcnt; i++) { 390 if (iov[i].iov_len > (unsigned) n) { 391 iov[i].iov_base = (char *)iov[i].iov_base + n; 392 iov[i].iov_len -= n; 393 break; 394 } 395 n -= iov[i].iov_len; 396 iov[i].iov_len = 0; 397 } 398 399 if (i == iovcnt) return written; 400 } 401 } 402 403 #endif 404 405 #ifdef HAVE_PWCHECK 406 /* pwcheck daemon-authenticated login */ 407 static int pwcheck_verify_password(sasl_conn_t *conn, 408 const char *userid, 409 const char *passwd, 410 const char *service __attribute__((unused)), 411 const char *user_realm 412 __attribute__((unused))) 413 { 414 int s; 415 struct sockaddr_un srvaddr; 416 int r; 417 struct iovec iov[10]; 418 static char response[1024]; 419 unsigned start, n; 420 char pwpath[1024]; 421 422 if (strlen(PWCHECKDIR)+8+1 > sizeof(pwpath)) return SASL_FAIL; 423 424 strcpy(pwpath, PWCHECKDIR); 425 strcat(pwpath, "/pwcheck"); 426 427 s = socket(AF_UNIX, SOCK_STREAM, 0); 428 if (s == -1) return errno; 429 430 memset((char *)&srvaddr, 0, sizeof(srvaddr)); 431 srvaddr.sun_family = AF_UNIX; 432 strncpy(srvaddr.sun_path, pwpath, sizeof(srvaddr.sun_path)); 433 r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr)); 434 if (r == -1) { 435 sasl_seterror(conn,0,"cannot connect to pwcheck server"); 436 return SASL_FAIL; 437 } 438 439 iov[0].iov_base = (char *)userid; 440 iov[0].iov_len = strlen(userid)+1; 441 iov[1].iov_base = (char *)passwd; 442 iov[1].iov_len = strlen(passwd)+1; 443 444 retry_writev(s, iov, 2); 445 446 start = 0; 447 while (start < sizeof(response) - 1) { 448 n = read(s, response+start, sizeof(response) - 1 - start); 449 if (n < 1) break; 450 start += n; 451 } 452 453 close(s); 454 455 if (start > 1 && !strncmp(response, "OK", 2)) { 456 return SASL_OK; 457 } 458 459 response[start] = '\0'; 460 sasl_seterror(conn,0,response); 461 return SASL_BADAUTH; 462 } 463 464 #endif 465 466 #ifdef HAVE_SASLAUTHD 467 468 /* 469 * Keep calling the read() system call with 'fd', 'buf', and 'nbyte' 470 * until all the data is read in or an error occurs. 471 */ 472 static int retry_read(int fd, void *buf0, unsigned nbyte) 473 { 474 int n; 475 int nread = 0; 476 char *buf = buf0; 477 478 if (nbyte == 0) return 0; 479 480 for (;;) { 481 n = read(fd, buf, nbyte); 482 if (n == -1 || n == 0) { 483 if (errno == EINTR || errno == EAGAIN) continue; 484 return -1; 485 } 486 487 nread += n; 488 489 if (nread >= (int) nbyte) return nread; 490 491 buf += n; 492 nbyte -= n; 493 } 494 } 495 496 /* saslauthd-authenticated login */ 497 static int saslauthd_verify_password(sasl_conn_t *conn, 498 const char *userid, 499 const char *passwd, 500 const char *service, 501 const char *user_realm) 502 { 503 char response[1024]; 504 char query[8192]; 505 char *query_end = query; 506 int s; 507 struct sockaddr_un srvaddr; 508 sasl_getopt_t *getopt; 509 void *context; 510 char pwpath[sizeof(srvaddr.sun_path)]; 511 const char *p = NULL; 512 #ifdef USE_DOORS 513 door_arg_t arg; 514 #endif 515 516 /* check to see if the user configured a rundir */ 517 if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { 518 getopt(context, NULL, "saslauthd_path", &p, NULL); 519 } 520 if (p) { 521 strncpy(pwpath, p, sizeof(pwpath)); 522 } else { 523 if (strlen(PATH_SASLAUTHD_RUNDIR) + 4 + 1 > sizeof(pwpath)) 524 return SASL_FAIL; 525 526 strcpy(pwpath, PATH_SASLAUTHD_RUNDIR); 527 strcat(pwpath, "/mux"); 528 } 529 530 /* 531 * build request of the form: 532 * 533 * count authid count password count service count realm 534 */ 535 { 536 unsigned short u_len, p_len, s_len, r_len; 537 538 u_len = (strlen(userid)); 539 p_len = (strlen(passwd)); 540 s_len = (strlen(service)); 541 r_len = ((user_realm ? strlen(user_realm) : 0)); 542 543 if (u_len + p_len + s_len + r_len + 30 > (unsigned short) sizeof(query)) { 544 /* request just too damn big */ 545 sasl_seterror(conn, 0, "saslauthd request too large"); 546 return SASL_FAIL; 547 } 548 549 u_len = htons(u_len); 550 p_len = htons(p_len); 551 s_len = htons(s_len); 552 r_len = htons(r_len); 553 554 memcpy(query_end, &u_len, sizeof(unsigned short)); 555 query_end += sizeof(unsigned short); 556 while (*userid) *query_end++ = *userid++; 557 558 memcpy(query_end, &p_len, sizeof(unsigned short)); 559 query_end += sizeof(unsigned short); 560 while (*passwd) *query_end++ = *passwd++; 561 562 memcpy(query_end, &s_len, sizeof(unsigned short)); 563 query_end += sizeof(unsigned short); 564 while (*service) *query_end++ = *service++; 565 566 memcpy(query_end, &r_len, sizeof(unsigned short)); 567 query_end += sizeof(unsigned short); 568 if (user_realm) while (*user_realm) *query_end++ = *user_realm++; 569 } 570 571 #ifdef USE_DOORS 572 s = open(pwpath, O_RDONLY); 573 if (s < 0) { 574 sasl_seterror(conn, 0, "cannot open door to saslauthd server: %m", errno); 575 return SASL_FAIL; 576 } 577 578 arg.data_ptr = query; 579 arg.data_size = query_end - query; 580 arg.desc_ptr = NULL; 581 arg.desc_num = 0; 582 arg.rbuf = response; 583 arg.rsize = sizeof(response); 584 585 door_call(s, &arg); 586 587 if (arg.data_ptr != response || arg.data_size >= sizeof(response)) { 588 /* oh damn, we got back a really long response */ 589 munmap(arg.rbuf, arg.rsize); 590 sasl_seterror(conn, 0, "saslauthd sent an overly long response"); 591 return SASL_FAIL; 592 } 593 response[arg.data_size] = '\0'; 594 595 close(s); 596 #else 597 /* unix sockets */ 598 599 s = socket(AF_UNIX, SOCK_STREAM, 0); 600 if (s == -1) { 601 sasl_seterror(conn, 0, "cannot create socket for saslauthd: %m", errno); 602 return SASL_FAIL; 603 } 604 605 memset((char *)&srvaddr, 0, sizeof(srvaddr)); 606 srvaddr.sun_family = AF_UNIX; 607 strncpy(srvaddr.sun_path, pwpath, sizeof(srvaddr.sun_path)); 608 609 { 610 int r = connect(s, (struct sockaddr *) &srvaddr, sizeof(srvaddr)); 611 if (r == -1) { 612 sasl_seterror(conn, 0, "cannot connect to saslauthd server: %m", errno); 613 return SASL_FAIL; 614 } 615 } 616 617 { 618 struct iovec iov[8]; 619 620 iov[0].iov_len = query_end - query; 621 iov[0].iov_base = query; 622 623 if (retry_writev(s, iov, 1) == -1) { 624 sasl_seterror(conn, 0, "write failed"); 625 return SASL_FAIL; 626 } 627 } 628 629 { 630 unsigned short count = 0; 631 632 /* 633 * read response of the form: 634 * 635 * count result 636 */ 637 if (retry_read(s, &count, sizeof(count)) < (int) sizeof(count)) { 638 sasl_seterror(conn, 0, "size read failed"); 639 return SASL_FAIL; 640 } 641 642 count = ntohs(count); 643 if (count < 2) { /* MUST have at least "OK" or "NO" */ 644 close(s); 645 sasl_seterror(conn, 0, "bad response from saslauthd"); 646 return SASL_FAIL; 647 } 648 649 count = (int)sizeof(response) < count ? sizeof(response) : count; 650 if (retry_read(s, response, count) < count) { 651 close(s); 652 sasl_seterror(conn, 0, "read failed"); 653 return SASL_FAIL; 654 } 655 response[count] = '\0'; 656 } 657 658 close(s); 659 #endif /* USE_DOORS */ 660 661 if (!strncmp(response, "OK", 2)) { 662 return SASL_OK; 663 } 664 665 sasl_seterror(conn, SASL_NOLOG, "authentication failed"); 666 return SASL_BADAUTH; 667 } 668 669 #endif 670 671 #ifdef HAVE_ALWAYSTRUE 672 static int always_true(sasl_conn_t *conn, 673 const char *userstr, 674 const char *passwd __attribute__((unused)), 675 const char *service __attribute__((unused)), 676 const char *user_realm __attribute__((unused))) 677 { 678 _sasl_log(conn, SASL_LOG_WARN, "AlwaysTrue Password Verifier Verified: %s", 679 userstr); 680 return SASL_OK; 681 } 682 #endif 683 684 struct sasl_verify_password_s _sasl_verify_password[] = { 685 { "auxprop", &auxprop_verify_password }, 686 #ifdef HAVE_PWCHECK 687 { "pwcheck", &pwcheck_verify_password }, 688 #endif 689 #ifdef HAVE_SASLAUTHD 690 { "saslauthd", &saslauthd_verify_password }, 691 #endif 692 #ifdef HAVE_ALWAYSTRUE 693 { "alwaystrue", &always_true }, 694 #endif 695 { NULL, NULL } 696 }; 697