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  * DESCRIPTION: Contains the top level shim hook functions. These must have
  30  *              identical interfaces to the equivalent standard dbm calls.
  31  *
  32  *              Unfortunately many of these will do a copy of a datum structure
  33  *              on return. This is a side effect of the original DBM function
  34  *              being written to pass structures rather than pointers.
  35  *
  36  * NOTE :       There is a major bug/feature in dbm. A key obtained by
  37  *              dbm_nextkey() of dbm_firstkey() cannot be passed to dbm_store().
  38  *              When the store occurs dbm's internal memory get's reorganized
  39  *              and the static strings pointed to by the key are destroyed. The
  40  *              data is then stored in the wrong place. We attempt to get round
  41  *              this by dbm_firstkey() and dbm_nextkey() making a copy of the
  42  *              key data in malloced memory. This is freed when map_ctrl is
  43  *              freed.
  44  */
  45 
  46 #include <unistd.h>
  47 #include <syslog.h>
  48 #include <ndbm.h>
  49 #include <strings.h>
  50 #include "ypsym.h"
  51 #include "ypdefs.h"
  52 #include "shim.h"
  53 #include "yptol.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 }