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