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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * DESCRIPTION: Contains the top level shim hook functions. These must have 31 * identical interfaces to the equivalent standard dbm calls. 32 * 33 * Unfortunately many of these will do a copy of a datum structure 34 * on return. This is a side effect of the original DBM function 35 * being written to pass structures rather than pointers. 36 * 37 * NOTE : There is a major bug/feature in dbm. A key obtained by 38 * dbm_nextkey() of dbm_firstkey() cannot be passed to dbm_store(). 39 * When the store occurs dbm's internal memory get's reorganized 40 * and the static strings pointed to by the key are destroyed. The 41 * data is then stored in the wrong place. We attempt to get round 42 * this by dbm_firstkey() and dbm_nextkey() making a copy of the 43 * key data in malloced memory. This is freed when map_ctrl is 44 * freed. 45 */ 46 47 #include <unistd.h> 48 #include <syslog.h> 49 #include <ndbm.h> 50 #include <strings.h> 51 #include "ypsym.h" 52 #include "ypdefs.h" 53 #include "shim.h" 54 #include "yptol.h" 55 #include "../ldap_parse.h" 56 #include "../ldap_util.h" 57 58 /* 59 * Switch on DBM support 60 */ 61 USE_DBM 62 63 /* 64 * Globals 65 */ 66 extern bool_t yptol_mode = FALSE; /* Set if in N2L mode */ 67 extern bool_t ypxfrd_flag = FALSE; /* Set if called from ypxfrd */ 68 pid_t parent_pid; /* ID of calling parent process */ 69 70 71 /* 72 * Decs 73 */ 74 void check_old_map_date(map_ctrl *); 75 76 /* 77 * Constants 78 */ 79 /* Number of times to try to update a map before giving up */ 80 /* #define MAX_UPDATE_ATTEMPTS 3 */ 81 #define MAX_UPDATE_ATTEMPTS 1 82 83 /* 84 * FUNCTION: shim_dbm_close(); 85 * 86 * INPUTS: Identical to equivalent dbm call. 87 * 88 * OUTPUTS: Identical to equivalent dbm call. 89 * 90 */ 91 void 92 shim_dbm_close(DBM *db) 93 { 94 map_ctrl *map; 95 96 /* Lock the map */ 97 map = get_map_ctrl(db); 98 if (map == NULL) 99 return; 100 101 free_map_ctrl(map); 102 } 103 104 /* 105 * FUNCTION: shim_dbm_delete(); 106 * 107 * DESCRIPTION: This function is currently unused but is present so that the 108 * set of shim_dbm_xxx() interfaces is complete if required in 109 * future. 110 * 111 * INPUTS: Identical to equivalent dbm call. 112 * 113 * OUTPUTS: Identical to equivalent dbm call. 114 * 115 */ 116 int 117 shim_dbm_delete(DBM *db, datum key) 118 { 119 int ret; 120 map_ctrl *map; 121 122 /* Lock the map */ 123 map = get_map_ctrl(db); 124 if (map == NULL) 125 return (FAILURE); 126 if (1 != lock_map_ctrl(map)) 127 return (FAILURE); 128 129 if (yptol_mode) { 130 /* Delete from and ttl map. Not a huge disaster if it fails. */ 131 dbm_delete(map->ttl, key); 132 } 133 134 ret = dbm_delete(map->entries, key); 135 136 unlock_map_ctrl(map); 137 138 return (ret); 139 } 140 141 142 /* 143 * FUNCTION: shim_dbm_fetch() 144 * 145 * DESCRIPTION: N2L function used to handle 'normal' dbm_fetch() operations. 146 * 147 * INPUTS: First two identical to equivalent dbm call. 148 * 149 * OUTPUTS: Identical to equivalent dbm call. 150 * 151 */ 152 datum 153 shim_dbm_fetch(DBM *db, datum key) 154 { 155 datum ret = {0, NULL}; 156 map_ctrl *map; 157 158 /* Lock the map */ 159 map = get_map_ctrl(db); 160 if (map == NULL) 161 return (ret); 162 if (1 != lock_map_ctrl(map)) 163 return (ret); 164 165 if (yptol_mode) { 166 if (SUCCESS == update_entry_if_required(map, &key)) { 167 /* Update thinks we should return something */ 168 ret = dbm_fetch(map->entries, key); 169 } 170 } else { 171 /* Non yptol mode do a normal fetch */ 172 ret = dbm_fetch(map->entries, key); 173 } 174 175 unlock_map_ctrl(map); 176 177 return (ret); 178 } 179 180 /* 181 * FUNCTION: shim_dbm_fetch_noupdate() 182 * 183 * DESCRIPTION: A special version of shim_dbm_fetch() that never checks TTLs 184 * or updates entries. 185 * 186 * INPUTS: Identical to equivalent dbm call. 187 * 188 * OUTPUTS: Identical to equivalent dbm call. 189 * 190 */ 191 datum 192 shim_dbm_fetch_noupdate(DBM *db, datum key) 193 { 194 datum ret = {0, NULL}; 195 map_ctrl *map; 196 197 /* Get the map control block */ 198 map = get_map_ctrl(db); 199 if (map == NULL) 200 return (ret); 201 202 /* Not updating so no need to lock */ 203 ret = dbm_fetch(map->entries, key); 204 205 return (ret); 206 } 207 208 /* 209 * FUNCTION: shim_dbm_firstkey() 210 * 211 * DESCRIPTION: Get firstkey in an enumeration. If the map is out of date then 212 * this is the time to scan it and see if any new entries have been 213 * created. 214 * 215 * INPUTS: Identical to equivalent dbm call. 216 * 217 * OUTPUTS: Identical to equivalent dbm call. 218 * 219 */ 220 datum 221 shim_dbm_firstkey(DBM *db) 222 { 223 int count; 224 bool_t wait_flag; 225 226 datum ret = {0, NULL}; 227 map_ctrl *map; 228 229 /* Lock the map */ 230 map = get_map_ctrl(db); 231 if (map == NULL) 232 return (ret); 233 if (1 != lock_map_ctrl(map)) 234 return (ret); 235 236 if (yptol_mode) { 237 /* 238 * Due to the limitations in the hashing algorithm ypxfrd 239 * may end up waiting on the wrong update. It must thus loop 240 * until the right map has been updated. 241 */ 242 for (count = 0; has_map_expired(map) && 243 (MAX_UPDATE_ATTEMPTS > count); count++) { 244 /* 245 * Ideally ypxfr should wait for the map update 246 * to complete i.e. pass ypxfrd_flag into 247 * update_map_if_required(). This cannot be done 248 * because if there is a large map update the client 249 * side, ypxfr, can time out while waiting. 250 */ 251 wait_flag = FALSE; 252 update_map_if_required(map, wait_flag); 253 254 if (wait_flag) { 255 /* 256 * Because ypxfrd does weird things with DBMs 257 * internal structures it's a good idea to 258 * reopen here. (Code that uses the real DBM 259 * API appears not to need this.) 260 * 261 * This should not be necessary all we have 262 * done is 'mv' the new file over the old one. 263 * Open handles should get the old data but if 264 * these lines are removed the first ypxfrd 265 * read access fail with bad file handle. 266 * 267 * NOTE : If we don't wait, because of the 268 * ypxfr timeout problem, there is no point 269 * doing this. 270 */ 271 dbm_close(map->entries); 272 dbm_close(map->ttl); 273 if (FAILURE == open_yptol_files(map)) { 274 logmsg(MSG_NOTIMECHECK, LOG_ERR, 275 "Could not reopen DBM files"); 276 } 277 } else { 278 /* For daemons that don't wait just try once */ 279 break; 280 } 281 } 282 283 if (MAX_UPDATE_ATTEMPTS < count) 284 logmsg(MSG_NOTIMECHECK, LOG_ERR, 285 "Cannot update map %s", map->map_name); 286 } 287 288 ret = dbm_firstkey(map->entries); 289 290 /* Move key data out of static memory. See NOTE in file header above */ 291 if (yptol_mode) { 292 set_key_data(map, &ret); 293 } 294 unlock_map_ctrl(map); 295 296 return (ret); 297 } 298 299 /* 300 * FUNCTION: shim_dbm_nextkey() 301 * 302 * DESCRIPTION: Get next key in an enumeration. Since updating an entry would 303 * invalidate the enumeration we never do it. 304 * 305 * INPUTS: Identical to equivalent dbm call. 306 * 307 * OUTPUTS: Identical to equivalent dbm call. 308 * 309 */ 310 datum 311 shim_dbm_nextkey(DBM *db) 312 { 313 datum ret; 314 map_ctrl *map; 315 316 /* Lock the map */ 317 map = get_map_ctrl(db); 318 if (map == NULL) 319 return (ret); 320 if (1 != lock_map_ctrl(map)) 321 return (ret); 322 323 ret = dbm_nextkey(map->entries); 324 325 /* Move key data out of static memory. See NOTE in file header above */ 326 if (yptol_mode) { 327 set_key_data(map, &ret); 328 } 329 330 unlock_map_ctrl(map); 331 332 return (ret); 333 } 334 335 /* 336 * FUNCTION: shim_dbm_do_nextkey() 337 * 338 * DESCRIPTION: Get next key in an enumeration. Since updating an entry would 339 * invalidate the enumeration we never do it. 340 * 341 * NOTE : dbm_do_nextkey is not a documented or legal DBM API. 342 * Despite this the existing NIS code calls it. One gross hack 343 * deserves another so we have this extra shim function to handle 344 * the illegal call. 345 * 346 * INPUTS: Identical to equivalent dbm call. 347 * 348 * OUTPUTS: Identical to equivalent dbm call. 349 * 350 */ 351 datum 352 shim_dbm_do_nextkey(DBM *db, datum inkey) 353 { 354 datum ret; 355 map_ctrl *map; 356 357 /* Lock the map */ 358 map = get_map_ctrl(db); 359 if (map == NULL) 360 return (ret); 361 if (1 != lock_map_ctrl(map)) 362 return (ret); 363 364 ret = dbm_do_nextkey(map->entries, inkey); 365 366 /* Move key data out of static memory. See NOTE in file header above */ 367 if (yptol_mode) { 368 set_key_data(map, &ret); 369 } 370 371 unlock_map_ctrl(map); 372 373 return (ret); 374 } 375 /* 376 * FUNCTION: shim_dbm_open() 377 * 378 * INPUTS: Identical to equivalent dbm call. 379 * 380 * OUTPUTS: Identical to equivalent dbm call. 381 * 382 */ 383 DBM * 384 shim_dbm_open(const char *file, int open_flags, mode_t file_mode) 385 { 386 map_ctrl *map; 387 DBM *dbm_ptr; 388 suc_code ret = FAILURE; 389 390 /* Find or create map_ctrl for this map */ 391 map = create_map_ctrl((char *)file); 392 393 if (map == NULL) 394 return (NULL); 395 396 /* Lock map */ 397 if (1 != lock_map_ctrl(map)) 398 return (NULL); 399 400 /* Remember flags and mode in case we have to reopen */ 401 map->open_flags = open_flags; 402 map->open_mode = file_mode; 403 404 if (yptol_mode) { 405 ret = open_yptol_files(map); 406 407 /* 408 * This is a good place to check that the 409 * equivalent old style map file has not been 410 * updated. 411 */ 412 if (SUCCESS == ret) 413 check_old_map_date(map); 414 415 } else { 416 /* Open entries map */ 417 map->entries = dbm_open(map->map_path, map->open_flags, 418 map->open_mode); 419 420 if (NULL != map->entries) 421 ret = SUCCESS; 422 } 423 424 /* If we were not successful unravel what we have done so far */ 425 if (ret != SUCCESS) { 426 unlock_map_ctrl(map); 427 free_map_ctrl(map); 428 return (NULL); 429 } 430 431 unlock_map_ctrl(map); 432 433 /* Return map_ctrl pointer as a DBM *. To the outside world it is */ 434 /* opaque. */ 435 return ((DBM *)map); 436 } 437 438 /* 439 * FUNCTION: shim_dbm_store() 440 * 441 * DESCRIPTION: Shim for dbm_store. 442 * 443 * In N2L mode if we are asked to store in DBM_INSERT mode 444 * then first an attempt is made to write to the DIT (in the same 445 * mode). If this is successful then the value is forced into DBM 446 * using DBM_REPLACE. This is because the DIT is authoritative. 447 * The success of failure of an 'insert' is determined by the 448 * presence or otherwise of an entry in the DIT not DBM. 449 * 450 * INPUTS: Identical to equivalent dbm call. 451 * 452 * OUTPUTS: Identical to equivalent dbm call. 453 * 454 */ 455 int 456 shim_dbm_store(DBM *db, datum key, datum content, int store_mode) 457 { 458 int ret; 459 map_ctrl *map; 460 461 /* Get map name */ 462 map = get_map_ctrl(db); 463 if (map == NULL) 464 return (FAILURE); 465 466 if (yptol_mode) { 467 /* Write to the DIT before doing anything else */ 468 if (!write_to_dit(map->map_name, map->domain, key, content, 469 DBM_REPLACE == store_mode, FALSE)) 470 return (FAILURE); 471 } 472 473 /* Lock the map */ 474 if (1 != lock_map_ctrl(map)) 475 return (FAILURE); 476 477 if (yptol_mode) { 478 if (!is_map_updating(map)) { 479 ret = dbm_store(map->entries, key, content, 480 DBM_REPLACE); 481 482 if (SUCCESS == ret) 483 /* Update TTL */ 484 update_entry_ttl(map, &key, TTL_RAND); 485 } 486 } else { 487 ret = dbm_store(map->entries, key, content, store_mode); 488 } 489 490 unlock_map_ctrl(map); 491 492 return (ret); 493 } 494 495 /* 496 * FUNCTION : shim_exit() 497 * 498 * DESCRIPTION: Intercepts exit() calls made by N2L compatible NIS components. 499 * This is required because any call to the shim_dbm... series 500 * of functions may have started an update thread. If the process 501 * exits normally then this thread may be killed before it can 502 * complete its work. We thus wait here for the thread to complete. 503 * 504 * GIVEN : Same arg as exit() 505 * 506 * RETURNS : Never 507 */ 508 void 509 shim_exit(int code) 510 { 511 thr_join(NULL, NULL, NULL); 512 exit(code); 513 } 514 515 /* 516 * FUNCTION : init_yptol_flag() 517 * 518 * DESCRIPTION: Initializes two flags these are similar but their function is 519 * subtly different. 520 * 521 * yp2ldap tells the mapping system if it is to work in NIS or 522 * NIS+ mode. For N2L this is always set to NIS mode. 523 * 524 * yptol tells the shim if it is to work in N2L or traditional 525 * NIS mode. For N2L this is turned on if the N2L mapping file 526 * is found to be present. In NIS+ mode it is meaningless. 527 */ 528 void 529 init_yptol_flag() 530 { 531 /* 532 * yp2ldap is used to switch appropriate code in the 533 * common libnisdb library used by rpc.nisd and ypserv. 534 */ 535 yp2ldap = 1; 536 yptol_mode = is_yptol_mode(); 537 } 538 539 /* 540 * FUNCTION : set_yxfrd_flag() 541 */ 542 void 543 set_ypxfrd_flag() 544 { 545 ypxfrd_flag = TRUE; 546 } 547 548 /* 549 * FUNCTION : check_old_map_date() 550 * 551 * DESCRIPTION: Checks that an old style map has not been updated. If it has 552 * then ypmake has probably erroneously been run and an error is 553 * logged. 554 * 555 * GIVEN : A map_ctrl containing details of the NEW STYLE map. 556 * 557 * RETURNS : Nothing 558 */ 559 void 560 check_old_map_date(map_ctrl *map) 561 { 562 datum key; 563 datum value; 564 struct stat stats; 565 time_t old_time; 566 567 /* Get date of last update */ 568 if (0 != stat(map->trad_map_path, &stats)) { 569 /* 570 * No problem. We have a new style map but no old style map 571 * this will occur if the original data came from native LDAP 572 * instead of NIS. 573 */ 574 return; 575 } 576 577 /* Set up datum with key for recorded old map update time */ 578 key.dsize = strlen(MAP_OLD_MAP_DATE_KEY); 579 key.dptr = MAP_OLD_MAP_DATE_KEY; 580 value = dbm_fetch(map->ttl, key); 581 582 if (NULL != value.dptr) { 583 /* 584 * Because dptr may not be int aligned need to build an int 585 * out of what it points to or will get a bus error. 586 */ 587 bcopy(value.dptr, &old_time, sizeof (time_t)); 588 589 590 /* Do the comparison */ 591 if (stats.st_mtime <= old_time) { 592 /* All is well, has not been updated */ 593 return; 594 } 595 596 /* If we get here the file has been updated */ 597 logmsg(MSG_NOTIMECHECK, LOG_ERR, 598 "Caution. ypmake may have been run in N2L " 599 "mode. This will NOT initiate a NIS map push. In " 600 "this mode pushes should be initiated with yppush"); 601 } 602 603 /* 604 * If we get here then either the file was updated or there was not 605 * a valid old map date (no problem, maybe this is the first time we 606 * checked). In either case the old map date entry must be update. 607 */ 608 value.dptr = (char *)&(stats.st_mtime); 609 value.dsize = sizeof (time_t); 610 dbm_store(map->ttl, key, value, DBM_REPLACE); 611 } 612 613 /* 614 * FUNCTION : init_lock_system() 615 * 616 * DESCRIPTION: Initializes all the systems related to map locking. This must 617 * be called before any access to the shim functions. 618 * 619 * GIVEN : A flag indicating if we are being called from ypserv, which does 620 * not wait for map updates to complete, or other NIS components 621 * which do. 622 * 623 * RETURNS : TRUE = Everything worked 624 * FALSE = There were problems 625 */ 626 bool_t 627 init_lock_system(bool_t ypxfrd) 628 { 629 /* Remember what called us */ 630 if (ypxfrd) 631 set_ypxfrd_flag(); 632 633 /* 634 * Remember PID of process which called us. This enables update threads 635 * created by YP children to be handled differently to those created 636 * by YP parents. 637 */ 638 parent_pid = getpid(); 639 640 /* Init map locks */ 641 if (!init_lock_map()) { 642 logmsg(MSG_NOTIMECHECK, LOG_ERR, 643 "Failed to init process synchronization"); 644 return (FALSE); 645 } 646 647 /* If we are in yptol mode set flag indicating the fact */ 648 init_yptol_flag(); 649 650 /* 651 * If boot random number system. For now go for reproducible 652 * random numbers. 653 */ 654 srand48(0x12345678); 655 656 /* 657 * If not N2L mode then no error but do not bother initializing update 658 * flags. 659 */ 660 if (yptol_mode) { 661 if (!init_update_lock_map()) { 662 logmsg(MSG_NOTIMECHECK, LOG_ERR, 663 "Failed to init update synchronization"); 664 return (FALSE); 665 } 666 } 667 668 return (TRUE); 669 } 670