1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Door server routines for nfsmapid daemon 30 * Translate NFSv4 users and groups between numeric and string values 31 */ 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <alloca.h> 35 #include <signal.h> 36 #include <libintl.h> 37 #include <limits.h> 38 #include <errno.h> 39 #include <sys/types.h> 40 #include <string.h> 41 #include <memory.h> 42 #include <pwd.h> 43 #include <grp.h> 44 #include <door.h> 45 #include <syslog.h> 46 #include <fcntl.h> 47 #include <unistd.h> 48 #include <assert.h> 49 #include <deflt.h> 50 #include <nfs/nfs4.h> 51 #include <nfs/nfssys.h> 52 #include <nfs/nfsid_map.h> 53 #include <nfs/mapid.h> 54 #include <sys/sdt.h> 55 56 /* 57 * We cannot use the backend nscd as it may make syscalls that may 58 * cause further nfsmapid upcalls introducing deadlock. 59 * Use the internal uncached versions of get*_r. 60 */ 61 extern struct group *_uncached_getgrgid_r(gid_t, struct group *, char *, int); 62 extern struct group *_uncached_getgrnam_r(const char *, struct group *, 63 char *, int); 64 extern struct passwd *_uncached_getpwuid_r(uid_t, struct passwd *, char *, int); 65 extern struct passwd *_uncached_getpwnam_r(const char *, struct passwd *, 66 char *, int); 67 68 #define UID_MAX_STR_LEN 11 /* Digits in UID_MAX + 1 */ 69 #define DIAG_FILE "/var/run/nfs4_domain" 70 71 /* 72 * idmap_kcall() takes a door descriptor as it's argument when we 73 * need to (re)establish the in-kernel door handles. When we only 74 * want to flush the id kernel caches, we don't redo the door setup. 75 */ 76 #define FLUSH_KCACHES_ONLY (int)-1 77 78 FILE *n4_fp; 79 int n4_fd; 80 81 extern size_t pwd_buflen; 82 extern size_t grp_buflen; 83 extern thread_t sig_thread; 84 85 /* 86 * Prototypes 87 */ 88 extern void check_domain(int); 89 extern void idmap_kcall(int); 90 extern int _nfssys(int, void *); 91 extern int valid_domain(const char *); 92 extern int validate_id_str(const char *); 93 extern int extract_domain(char *, char **, char **); 94 extern void update_diag_file(char *); 95 extern void *cb_update_domain(void *); 96 extern int cur_domain_null(void); 97 98 void 99 nfsmapid_str_uid(struct mapid_arg *argp, size_t arg_size) 100 { 101 struct mapid_res result; 102 struct passwd pwd; 103 char *pwd_buf; 104 char *user; 105 char *domain; 106 107 if (argp->u_arg.len <= 0 || arg_size < MAPID_ARG_LEN(argp->u_arg.len)) { 108 result.status = NFSMAPID_INVALID; 109 result.u_res.uid = UID_NOBODY; 110 goto done; 111 } 112 113 if (!extract_domain(argp->str, &user, &domain)) { 114 long id; 115 116 /* 117 * Invalid "user@dns_domain" string. Still, the user 118 * part might be an encoded uid, so do a final check. 119 * Remember, domain part of string was not set since 120 * not a valid string. 121 */ 122 if (!validate_id_str(user)) { 123 result.status = NFSMAPID_UNMAPPABLE; 124 result.u_res.uid = UID_NOBODY; 125 goto done; 126 } 127 128 /* 129 * Since atoi() does not return proper errors for 130 * invalid translation, use strtol() instead. 131 */ 132 errno = 0; 133 id = strtol(user, (char **)NULL, 10); 134 135 if (errno || id < 0 || id > UID_MAX) { 136 result.status = NFSMAPID_UNMAPPABLE; 137 result.u_res.uid = UID_NOBODY; 138 goto done; 139 } 140 141 result.u_res.uid = (uid_t)id; 142 result.status = NFSMAPID_NUMSTR; 143 goto done; 144 } 145 146 /* 147 * String properly constructed. Now we check for domain and 148 * group validity. Note that we only look at the domain iff 149 * the local domain is configured. 150 */ 151 if (!cur_domain_null() && !valid_domain(domain)) { 152 result.status = NFSMAPID_BADDOMAIN; 153 result.u_res.uid = UID_NOBODY; 154 goto done; 155 } 156 157 if ((pwd_buf = malloc(pwd_buflen)) == NULL || 158 _uncached_getpwnam_r(user, &pwd, pwd_buf, pwd_buflen) == NULL) { 159 160 if (pwd_buf == NULL) 161 result.status = NFSMAPID_INTERNAL; 162 else { 163 /* 164 * Not a valid user 165 */ 166 result.status = NFSMAPID_NOTFOUND; 167 free(pwd_buf); 168 } 169 result.u_res.uid = UID_NOBODY; 170 goto done; 171 } 172 173 /* 174 * Valid user entry 175 */ 176 result.u_res.uid = pwd.pw_uid; 177 result.status = NFSMAPID_OK; 178 free(pwd_buf); 179 done: 180 (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); 181 } 182 183 /* ARGSUSED1 */ 184 void 185 nfsmapid_uid_str(struct mapid_arg *argp, size_t arg_size) 186 { 187 struct mapid_res result; 188 struct mapid_res *resp; 189 struct passwd pwd; 190 int pwd_len; 191 char *pwd_buf; 192 uid_t uid = argp->u_arg.uid; 193 size_t uid_str_len; 194 char *pw_str; 195 size_t pw_str_len; 196 char *at_str; 197 size_t at_str_len; 198 char dom_str[DNAMEMAX]; 199 size_t dom_str_len; 200 201 if (uid < 0 || uid > UID_MAX) { 202 /* 203 * Negative uid or greater than UID_MAX 204 */ 205 resp = &result; 206 resp->status = NFSMAPID_BADID; 207 resp->u_res.len = 0; 208 goto done; 209 } 210 211 /* 212 * Make local copy of domain for further manipuation 213 * NOTE: mapid_get_domain() returns a ptr to TSD. 214 */ 215 if (cur_domain_null()) { 216 dom_str_len = 0; 217 dom_str[0] = '\0'; 218 } else { 219 dom_str_len = strlcpy(dom_str, mapid_get_domain(), DNAMEMAX); 220 } 221 222 /* 223 * We want to encode the uid into a literal string... : 224 * 225 * - upon failure to allocate space from the heap 226 * - if there is no current domain configured 227 * - if there is no such uid in the passwd DB's 228 */ 229 if ((pwd_buf = malloc(pwd_buflen)) == NULL || dom_str_len == 0 || 230 _uncached_getpwuid_r(uid, &pwd, pwd_buf, pwd_buflen) == NULL) { 231 232 /* 233 * If we could not allocate from the heap, try 234 * allocating from the stack as a last resort. 235 */ 236 if (pwd_buf == NULL && (pwd_buf = 237 alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { 238 resp = &result; 239 resp->status = NFSMAPID_INTERNAL; 240 resp->u_res.len = 0; 241 goto done; 242 } 243 244 /* 245 * Constructing literal string without '@' so that 246 * we'll know that it's not a user, but rather a 247 * uid encoded string. Can't overflow because we 248 * already checked UID_MAX. 249 */ 250 pw_str = pwd_buf; 251 (void) sprintf(pw_str, "%d", (int)uid); 252 pw_str_len = strlen(pw_str); 253 at_str_len = dom_str_len = 0; 254 at_str = ""; 255 dom_str[0] = '\0'; 256 } else { 257 /* 258 * Otherwise, we construct the "user@domain" string 259 */ 260 pw_str = pwd.pw_name; 261 pw_str_len = strlen(pw_str); 262 at_str = "@"; 263 at_str_len = 1; 264 } 265 266 uid_str_len = pw_str_len + at_str_len + dom_str_len; 267 if ((resp = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { 268 resp = &result; 269 resp->status = NFSMAPID_INTERNAL; 270 resp->u_res.len = 0; 271 goto done; 272 } 273 /* LINTED format argument to sprintf */ 274 (void) sprintf(resp->str, "%s%s%s", pw_str, at_str, dom_str); 275 resp->u_res.len = uid_str_len; 276 free(pwd_buf); 277 resp->status = NFSMAPID_OK; 278 279 done: 280 /* 281 * There is a chance that the door_return will fail because the 282 * resulting string is too large, try to indicate that if possible 283 */ 284 if (door_return((char *)resp, 285 MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) { 286 resp->status = NFSMAPID_INTERNAL; 287 resp->u_res.len = 0; 288 (void) door_return((char *)&result, sizeof (struct mapid_res), 289 NULL, 0); 290 } 291 } 292 293 void 294 nfsmapid_str_gid(struct mapid_arg *argp, size_t arg_size) 295 { 296 struct mapid_res result; 297 struct group grp; 298 char *grp_buf; 299 char *group; 300 char *domain; 301 302 if (argp->u_arg.len <= 0 || 303 arg_size < MAPID_ARG_LEN(argp->u_arg.len)) { 304 result.status = NFSMAPID_INVALID; 305 result.u_res.gid = GID_NOBODY; 306 goto done; 307 } 308 309 if (!extract_domain(argp->str, &group, &domain)) { 310 long id; 311 312 /* 313 * Invalid "group@dns_domain" string. Still, the 314 * group part might be an encoded gid, so do a 315 * final check. Remember, domain part of string 316 * was not set since not a valid string. 317 */ 318 if (!validate_id_str(group)) { 319 result.status = NFSMAPID_UNMAPPABLE; 320 result.u_res.gid = GID_NOBODY; 321 goto done; 322 } 323 324 /* 325 * Since atoi() does not return proper errors for 326 * invalid translation, use strtol() instead. 327 */ 328 errno = 0; 329 id = strtol(group, (char **)NULL, 10); 330 331 if (errno || id < 0 || id > UID_MAX) { 332 result.status = NFSMAPID_UNMAPPABLE; 333 result.u_res.gid = GID_NOBODY; 334 goto done; 335 } 336 337 result.u_res.gid = (gid_t)id; 338 result.status = NFSMAPID_NUMSTR; 339 goto done; 340 } 341 342 /* 343 * String properly constructed. Now we check for domain and 344 * group validity. Note that we only look at the domain iff 345 * the local domain is configured. 346 */ 347 if (!cur_domain_null() && !valid_domain(domain)) { 348 result.status = NFSMAPID_BADDOMAIN; 349 result.u_res.gid = GID_NOBODY; 350 goto done; 351 } 352 353 if ((grp_buf = malloc(grp_buflen)) == NULL || 354 _uncached_getgrnam_r(group, &grp, grp_buf, grp_buflen) == NULL) { 355 356 if (grp_buf == NULL) 357 result.status = NFSMAPID_INTERNAL; 358 else { 359 /* 360 * Not a valid group 361 */ 362 result.status = NFSMAPID_NOTFOUND; 363 free(grp_buf); 364 } 365 result.u_res.gid = GID_NOBODY; 366 goto done; 367 } 368 369 /* 370 * Valid group entry 371 */ 372 result.status = NFSMAPID_OK; 373 result.u_res.gid = grp.gr_gid; 374 free(grp_buf); 375 done: 376 (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); 377 } 378 379 /* ARGSUSED1 */ 380 void 381 nfsmapid_gid_str(struct mapid_arg *argp, size_t arg_size) 382 { 383 struct mapid_res result; 384 struct mapid_res *resp; 385 struct group grp; 386 char *grp_buf; 387 gid_t gid = argp->u_arg.gid; 388 size_t gid_str_len; 389 char *gr_str; 390 size_t gr_str_len; 391 char *at_str; 392 size_t at_str_len; 393 char dom_str[DNAMEMAX]; 394 size_t dom_str_len; 395 396 if (gid < 0 || gid > UID_MAX) { 397 /* 398 * Negative gid or greater than UID_MAX 399 */ 400 resp = &result; 401 resp->status = NFSMAPID_BADID; 402 resp->u_res.len = 0; 403 goto done; 404 } 405 406 /* 407 * Make local copy of domain for further manipuation 408 * NOTE: mapid_get_domain() returns a ptr to TSD. 409 */ 410 if (cur_domain_null()) { 411 dom_str_len = 0; 412 dom_str[0] = '\0'; 413 } else { 414 dom_str_len = strlen(mapid_get_domain()); 415 bcopy(mapid_get_domain(), dom_str, dom_str_len); 416 dom_str[dom_str_len] = '\0'; 417 } 418 419 /* 420 * We want to encode the gid into a literal string... : 421 * 422 * - upon failure to allocate space from the heap 423 * - if there is no current domain configured 424 * - if there is no such gid in the group DB's 425 */ 426 if ((grp_buf = malloc(grp_buflen)) == NULL || dom_str_len == 0 || 427 _uncached_getgrgid_r(gid, &grp, grp_buf, grp_buflen) == NULL) { 428 429 /* 430 * If we could not allocate from the heap, try 431 * allocating from the stack as a last resort. 432 */ 433 if (grp_buf == NULL && (grp_buf = 434 alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { 435 resp = &result; 436 resp->status = NFSMAPID_INTERNAL; 437 resp->u_res.len = 0; 438 goto done; 439 } 440 441 /* 442 * Constructing literal string without '@' so that 443 * we'll know that it's not a group, but rather a 444 * gid encoded string. Can't overflow because we 445 * already checked UID_MAX. 446 */ 447 gr_str = grp_buf; 448 (void) sprintf(gr_str, "%d", (int)gid); 449 gr_str_len = strlen(gr_str); 450 at_str_len = dom_str_len = 0; 451 at_str = ""; 452 dom_str[0] = '\0'; 453 } else { 454 /* 455 * Otherwise, we construct the "group@domain" string 456 */ 457 gr_str = grp.gr_name; 458 gr_str_len = strlen(gr_str); 459 at_str = "@"; 460 at_str_len = 1; 461 } 462 463 gid_str_len = gr_str_len + at_str_len + dom_str_len; 464 if ((resp = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { 465 resp = &result; 466 resp->status = NFSMAPID_INTERNAL; 467 resp->u_res.len = 0; 468 goto done; 469 } 470 /* LINTED format argument to sprintf */ 471 (void) sprintf(resp->str, "%s%s%s", gr_str, at_str, dom_str); 472 resp->u_res.len = gid_str_len; 473 free(grp_buf); 474 resp->status = NFSMAPID_OK; 475 476 done: 477 /* 478 * There is a chance that the door_return will fail because the 479 * resulting string is too large, try to indicate that if possible 480 */ 481 if (door_return((char *)resp, 482 MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) { 483 resp->status = NFSMAPID_INTERNAL; 484 resp->u_res.len = 0; 485 (void) door_return((char *)&result, sizeof (struct mapid_res), 486 NULL, 0); 487 } 488 } 489 490 /* ARGSUSED */ 491 void 492 nfsmapid_func(void *cookie, char *argp, size_t arg_size, 493 door_desc_t *dp, uint_t n_desc) 494 { 495 struct mapid_arg *mapargp; 496 struct mapid_res mapres; 497 498 /* 499 * Make sure we have a valid argument 500 */ 501 if (arg_size < sizeof (struct mapid_arg)) { 502 mapres.status = NFSMAPID_INVALID; 503 mapres.u_res.len = 0; 504 (void) door_return((char *)&mapres, sizeof (struct mapid_res), 505 NULL, 0); 506 return; 507 } 508 509 /* LINTED pointer cast */ 510 mapargp = (struct mapid_arg *)argp; 511 switch (mapargp->cmd) { 512 case NFSMAPID_STR_UID: 513 nfsmapid_str_uid(mapargp, arg_size); 514 return; 515 case NFSMAPID_UID_STR: 516 nfsmapid_uid_str(mapargp, arg_size); 517 return; 518 case NFSMAPID_STR_GID: 519 nfsmapid_str_gid(mapargp, arg_size); 520 return; 521 case NFSMAPID_GID_STR: 522 nfsmapid_gid_str(mapargp, arg_size); 523 return; 524 default: 525 break; 526 } 527 mapres.status = NFSMAPID_INVALID; 528 mapres.u_res.len = 0; 529 (void) door_return((char *)&mapres, sizeof (struct mapid_res), NULL, 0); 530 } 531 532 /* 533 * mapid_get_domain() always returns a ptr to TSD, so the 534 * check for a NULL domain is not a simple comparison with 535 * NULL but we need to check the contents of the TSD data. 536 */ 537 int 538 cur_domain_null(void) 539 { 540 char *p; 541 542 if ((p = mapid_get_domain()) == NULL) 543 return (1); 544 545 return (p[0] == '\0'); 546 } 547 548 int 549 extract_domain(char *cp, char **upp, char **dpp) 550 { 551 /* 552 * Caller must insure that the string is valid 553 */ 554 *upp = cp; 555 556 if ((*dpp = strchr(cp, '@')) == NULL) 557 return (0); 558 *(*dpp)++ = '\0'; 559 return (1); 560 } 561 562 int 563 valid_domain(const char *dom) 564 { 565 const char *whoami = "valid_domain"; 566 567 if (!mapid_stdchk_domain(dom)) { 568 syslog(LOG_ERR, gettext("%s: Invalid inbound domain name %s."), 569 whoami, dom); 570 return (0); 571 } 572 573 /* 574 * NOTE: mapid_get_domain() returns a ptr to TSD. 575 */ 576 return (strcasecmp(dom, mapid_get_domain()) == 0); 577 } 578 579 int 580 validate_id_str(const char *id) 581 { 582 while (*id) { 583 if (!isdigit(*id++)) 584 return (0); 585 } 586 return (1); 587 } 588 589 void 590 idmap_kcall(int door_id) 591 { 592 struct nfsidmap_args args; 593 594 if (door_id >= 0) { 595 args.state = 1; 596 args.did = door_id; 597 } else { 598 args.state = 0; 599 args.did = 0; 600 } 601 (void) _nfssys(NFS_IDMAP, &args); 602 } 603 604 /* 605 * Get the current NFS domain. 606 * 607 * If NFSMAPID_DOMAIN is set in /etc/default/nfs, then it is the NFS domain; 608 * otherwise, the DNS domain is used. 609 */ 610 void 611 check_domain(int sighup) 612 { 613 const char *whoami = "check_domain"; 614 static int setup_done = 0; 615 static cb_t cb; 616 617 /* 618 * Construct the arguments to be passed to libmapid interface 619 * If called in response to a SIGHUP, reset any cached DNS TXT 620 * RR state. 621 */ 622 cb.fcn = cb_update_domain; 623 cb.signal = sighup; 624 mapid_reeval_domain(&cb); 625 626 /* 627 * Restart the signal handler thread if we're still setting up 628 */ 629 if (!setup_done) { 630 setup_done = 1; 631 if (thr_continue(sig_thread)) { 632 syslog(LOG_ERR, gettext("%s: Fatal error: signal " 633 "handler thread could not be restarted."), whoami); 634 exit(6); 635 } 636 } 637 } 638 639 /* 640 * Need to be able to open the DIAG_FILE before nfsmapid(1m) 641 * releases it's root priviledges. The DIAG_FILE then remains 642 * open for the duration of this nfsmapid instance via n4_fd. 643 */ 644 void 645 open_diag_file() 646 { 647 static int msg_done = 0; 648 649 if ((n4_fp = fopen(DIAG_FILE, "w+")) != NULL) { 650 n4_fd = fileno(n4_fp); 651 return; 652 } 653 654 if (msg_done) 655 return; 656 657 syslog(LOG_ERR, "Failed to create %s. Enable syslog " 658 "daemon.debug for more info", DIAG_FILE); 659 msg_done = 1; 660 } 661 662 /* 663 * When a new domain name is configured, save to DIAG_FILE 664 * and log to syslog, with LOG_DEBUG level (if configured). 665 */ 666 void 667 update_diag_file(char *new) 668 { 669 char buf[DNAMEMAX]; 670 ssize_t n; 671 size_t len; 672 673 (void) lseek(n4_fd, (off_t)0, SEEK_SET); 674 (void) ftruncate(n4_fd, 0); 675 (void) snprintf(buf, DNAMEMAX, "%s\n", new); 676 677 len = strlen(buf); 678 n = write(n4_fd, buf, len); 679 if (n < 0 || n < len) 680 syslog(LOG_DEBUG, "Could not write %s to diag file", new); 681 fsync(n4_fd); 682 683 syslog(LOG_DEBUG, "nfsmapid domain = %s", new); 684 } 685 686 /* 687 * Callback function for libmapid. This will be called 688 * by the lib, everytime the nfsmapid(1m) domain changes. 689 */ 690 void * 691 cb_update_domain(void *arg) 692 { 693 char *new_dname = (char *)arg; 694 695 DTRACE_PROBE1(nfsmapid, daemon__domain, new_dname); 696 update_diag_file(new_dname); 697 idmap_kcall(FLUSH_KCACHES_ONLY); 698 699 return (NULL); 700 } 701