root/usr/src/lib/libnisdb/yptol/shim.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2015 Gary Mills
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * DESCRIPTION: Contains the top level shim hook functions. These must have
 *              identical interfaces to the equivalent standard dbm calls.
 *
 *              Unfortunately many of these will do a copy of a datum structure
 *              on return. This is a side effect of the original DBM function
 *              being written to pass structures rather than pointers.
 *
 * NOTE :       There is a major bug/feature in dbm. A key obtained by
 *              dbm_nextkey() of dbm_firstkey() cannot be passed to dbm_store().
 *              When the store occurs dbm's internal memory get's reorganized
 *              and the static strings pointed to by the key are destroyed. The
 *              data is then stored in the wrong place. We attempt to get round
 *              this by dbm_firstkey() and dbm_nextkey() making a copy of the
 *              key data in malloced memory. This is freed when map_ctrl is
 *              freed.
 */

#include <unistd.h>
#include <syslog.h>
#include <ndbm.h>
#include <strings.h>
#include "ypsym.h"
#include "ypdefs.h"
#include "shim.h"
#include "yptol.h"
#include "stubs.h"
#include "../ldap_parse.h"
#include "../ldap_util.h"

/*
 * Globals
 */
bool_t yptol_mode = FALSE;      /* Set if in N2L mode */
bool_t yptol_newlock = FALSE;
                                /*
                                 * Set if in N2L mode and we want to use the new
                                 * lock mapping mechanism
                                 */
bool_t ypxfrd_flag = FALSE;     /* Set if called from ypxfrd */
pid_t parent_pid;                       /* ID of calling parent process */


/*
 * Decs
 */
void check_old_map_date(map_ctrl *);

/*
 * Constants
 */
/* Number of times to try to update a map before giving up */
/* #define MAX_UPDATE_ATTEMPTS 3 */
#define MAX_UPDATE_ATTEMPTS 1

/*
 * FUNCTION:    shim_dbm_close();
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
void
shim_dbm_close(DBM *db)
{
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return;

        free_map_ctrl(map);
}

/*
 * FUNCTION:    shim_dbm_delete();
 *
 * DESCRIPTION: This function is currently unused but is present so that the
 *              set of shim_dbm_xxx() interfaces is complete if required in
 *              future.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
int
shim_dbm_delete(DBM *db, datum key)
{
        int ret;
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (FAILURE);
        if (1 != lock_map_ctrl(map))
                return (FAILURE);

        if (yptol_mode) {
                /* Delete from and ttl map. Not a huge disaster if it fails. */
                dbm_delete(map->ttl, key);
        }

        ret = dbm_delete(map->entries, key);

        unlock_map_ctrl(map);

        return (ret);
}


/*
 * FUNCTION:    shim_dbm_fetch()
 *
 * DESCRIPTION: N2L function used to handle 'normal' dbm_fetch() operations.
 *
 * INPUTS:      First two identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
datum
shim_dbm_fetch(DBM *db, datum key)
{
        datum ret = {0, NULL};
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (ret);
        if (1 != lock_map_ctrl(map))
                return (ret);

        if (yptol_mode) {
                if (SUCCESS == update_entry_if_required(map, &key)) {
                        /* Update thinks we should return something */
                        ret = dbm_fetch(map->entries, key);
                }
        } else {
                /* Non yptol mode do a normal fetch */
                ret = dbm_fetch(map->entries, key);
        }

        unlock_map_ctrl(map);

        return (ret);
}

/*
 * FUNCTION:    shim_dbm_fetch_noupdate()
 *
 * DESCRIPTION: A special version of shim_dbm_fetch() that never checks TTLs
 *              or updates entries.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
datum
shim_dbm_fetch_noupdate(DBM *db, datum key)
{
        datum ret = {0, NULL};
        map_ctrl *map;

        /* Get the map control block */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (ret);

        /* Not updating so no need to lock */
        ret = dbm_fetch(map->entries, key);

        return (ret);
}

/*
 * FUNCTION:    shim_dbm_firstkey()
 *
 * DESCRIPTION: Get firstkey in an enumeration. If the map is out of date then
 *            this is the time to scan it and see if any new entries have been
 *            created.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
datum
shim_dbm_firstkey(DBM *db)
{
        int count;
        bool_t wait_flag;

        datum ret = {0, NULL};
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (ret);
        if (1 != lock_map_ctrl(map))
                return (ret);

        if (yptol_mode) {
                /*
                 * Due to the limitations in the hashing algorithm ypxfrd
                 * may end up waiting on the wrong update. It must thus loop
                 * until the right map has been updated.
                 */
                for (count = 0; has_map_expired(map) &&
                                (MAX_UPDATE_ATTEMPTS > count); count++) {
                        /*
                         * Ideally ypxfr should wait for the map update
                         * to complete i.e. pass ypxfrd_flag into
                         * update_map_if_required(). This cannot be done
                         * because if there is a large map update the client
                         * side, ypxfr, can time out while waiting.
                         */
                        wait_flag = FALSE;
                        update_map_if_required(map, wait_flag);

                        if (wait_flag) {
                                /*
                                 * Because ypxfrd does weird things with DBMs
                                 * internal structures it's a good idea to
                                 * reopen here. (Code that uses the real DBM
                                 * API appears not to need this.)
                                 *
                                 * This should not be necessary all we have
                                 * done is 'mv' the new file over the old one.
                                 * Open handles should get the old data but if
                                 * these lines are removed the first ypxfrd
                                 * read access fail with bad file handle.
                                 *
                                 * NOTE : If we don't wait, because of the
                                 * ypxfr timeout problem, there is no point
                                 * doing this.
                                 */
                                dbm_close(map->entries);
                                dbm_close(map->ttl);
                                if (FAILURE == open_yptol_files(map)) {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                                "Could not reopen DBM files");
                                }
                        } else {
                                /* For daemons that don't wait just try once */
                                break;
                        }
                }

                if (MAX_UPDATE_ATTEMPTS < count)
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                        "Cannot update map %s", map->map_name);
        }

        ret = dbm_firstkey(map->entries);

        /* Move key data out of static memory. See NOTE in file header above */
        if (yptol_mode) {
                set_key_data(map, &ret);
        }
        unlock_map_ctrl(map);

        return (ret);
}

/*
 * FUNCTION:    shim_dbm_nextkey()
 *
 * DESCRIPTION: Get next key in an enumeration. Since updating an entry would
 *            invalidate the enumeration we never do it.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
datum
shim_dbm_nextkey(DBM *db)
{
        datum ret;
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (ret);
        if (1 != lock_map_ctrl(map))
                return (ret);

        ret = dbm_nextkey(map->entries);

        /* Move key data out of static memory. See NOTE in file header above */
        if (yptol_mode) {
                set_key_data(map, &ret);
        }

        unlock_map_ctrl(map);

        return (ret);
}

/*
 * FUNCTION:    shim_dbm_do_nextkey()
 *
 * DESCRIPTION: Get next key in an enumeration. Since updating an entry would
 *            invalidate the enumeration we never do it.
 *
 * NOTE :       dbm_do_nextkey is not a documented or legal DBM API.
 *              Despite this the existing NIS code calls it. One gross hack
 *              deserves another so we have this extra shim function to handle
 *              the illegal call.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
datum
shim_dbm_do_nextkey(DBM *db, datum inkey)
{
        datum ret;
        map_ctrl *map;

        /* Lock the map */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (ret);
        if (1 != lock_map_ctrl(map))
                return (ret);

        ret = dbm_do_nextkey(map->entries, inkey);

        /* Move key data out of static memory. See NOTE in file header above */
        if (yptol_mode) {
                set_key_data(map, &ret);
        }

        unlock_map_ctrl(map);

        return (ret);
}
/*
 * FUNCTION:    shim_dbm_open()
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
DBM *
shim_dbm_open(const char *file, int open_flags, mode_t file_mode)
{
        map_ctrl *map;
        suc_code ret = FAILURE;

        /* Find or create map_ctrl for this map */
        map = create_map_ctrl((char *)file);

        if (map == NULL)
                return (NULL);

        /* Lock map */
        if (1 != lock_map_ctrl(map))
                return (NULL);

        /* Remember flags and mode in case we have to reopen */
        map->open_flags = open_flags;
        map->open_mode = file_mode;

        if (yptol_mode) {
                ret = open_yptol_files(map);

                /*
                 * This is a good place to check that the
                 * equivalent old style map file has not been
                 * updated.
                 */
                if (SUCCESS == ret)
                        check_old_map_date(map);

        } else {
                /* Open entries map */
                map->entries = dbm_open(map->map_path, map->open_flags,
                                                                map->open_mode);

                if (NULL != map->entries)
                        ret = SUCCESS;
        }

        /* If we were not successful unravel what we have done so far */
        if (ret != SUCCESS) {
                unlock_map_ctrl(map);
                free_map_ctrl(map);
                return (NULL);
        }

        unlock_map_ctrl(map);

        /* Return map_ctrl pointer as a DBM *. To the outside world it is */
        /* opaque. */
        return ((DBM *)map);
}

/*
 * FUNCTION:    shim_dbm_store()
 *
 * DESCRIPTION: Shim for dbm_store.
 *
 *              In N2L mode if we are asked to store in DBM_INSERT mode
 *              then first an attempt is made to write to the DIT (in the same
 *              mode). If this is successful then the value is forced into DBM
 *              using DBM_REPLACE. This is because the DIT is authoritative.
 *              The success of failure of an 'insert' is determined by the
 *              presence or otherwise of an entry in the DIT not DBM.
 *
 * INPUTS:      Identical to equivalent dbm call.
 *
 * OUTPUTS:     Identical to equivalent dbm call.
 *
 */
int
shim_dbm_store(DBM  *db,  datum  key,  datum  content, int store_mode)
{
        int ret;
        map_ctrl *map;

        /* Get map name */
        map = get_map_ctrl(db);
        if (map == NULL)
                return (FAILURE);

        if (yptol_mode) {
                /* Write to the DIT before doing anything else */
                if (!write_to_dit(map->map_name, map->domain, key, content,
                                        DBM_REPLACE == store_mode, FALSE))
                        return (FAILURE);
        }

        /* Lock the map */
        if (1 != lock_map_ctrl(map))
                return (FAILURE);

        if (yptol_mode) {
                if (!is_map_updating(map)) {
                        ret = dbm_store(map->entries, key, content,
                                                                DBM_REPLACE);

                        if (SUCCESS == ret)
                                /* Update TTL */
                                update_entry_ttl(map, &key, TTL_RAND);
                }
        } else {
                ret = dbm_store(map->entries, key, content, store_mode);
        }

        unlock_map_ctrl(map);

        return (ret);
}

/*
 * FUNCTION :   shim_exit()
 *
 * DESCRIPTION: Intercepts exit() calls made by N2L compatible NIS components.
 *              This is required because any call to the shim_dbm... series
 *              of functions may have started an update thread. If the process
 *              exits normally then this thread may be killed before it can
 *              complete its work. We thus wait here for the thread to complete.
 *
 * GIVEN :      Same arg as exit()
 *
 * RETURNS :    Never
 */
void
shim_exit(int code)
{
        thr_join(NULL, NULL, NULL);
        exit(code);
}

/*
 * FUNCTION :   init_yptol_flag()
 *
 * DESCRIPTION: Initializes two flags these are similar but their function is
 *              subtly different.
 *
 *              yp2ldap tells the mapping system if it is to work in NIS or
 *              NIS+ mode. For N2L this is always set to NIS mode.
 *
 *              yptol tells the shim if it is to work in N2L or traditional
 *              NIS mode. For N2L this is turned on if the N2L mapping file
 *              is found to be present. In NIS+ mode it is meaningless.
 */
void
init_yptol_flag()
{
        /*
         * yp2ldap is used to switch appropriate code in the
         * common libnisdb library used by rpc.nisd and ypserv.
         */
        yp2ldap = 1;
        yptol_mode = is_yptol_mode();
        /*
         * Use the new lock mapping mechanism
         * if in N2L mode.
         */
        yptol_newlock = yptol_mode;
}

/*
 * FUNCTION :   set_yxfrd_flag()
 */
void
set_ypxfrd_flag()
{
        ypxfrd_flag = TRUE;
}

/*
 * FUNCTION :   check_old_map_date()
 *
 * DESCRIPTION: Checks that an old style map has not been updated. If it has
 *              then ypmake has probably erroneously been run and an error is
 *              logged.
 *
 * GIVEN :      A map_ctrl containing details of the NEW STYLE map.
 *
 * RETURNS :    Nothing
 */
void
check_old_map_date(map_ctrl *map)
{
        datum key;
        datum value;
        struct stat stats;
        time_t old_time;

        /* Get date of last update */
        if (0 != stat(map->trad_map_path, &stats)) {
                /*
                 * No problem. We have a new style map but no old style map
                 * this will occur if the original data came from native LDAP
                 * instead of NIS.
                 */
                return;
        }

        /* Set up datum with key for recorded old map update time */
        key.dsize = strlen(MAP_OLD_MAP_DATE_KEY);
        key.dptr = MAP_OLD_MAP_DATE_KEY;
        value = dbm_fetch(map->ttl, key);

        if (NULL != value.dptr) {
                /*
                 * Because dptr may not be int aligned need to build an int
                 * out of what it points to or will get a bus error.
                 */
                bcopy(value.dptr, &old_time, sizeof (time_t));


                /* Do the comparison */
                if (stats.st_mtime <= old_time) {
                        /* All is well, has not been updated */
                        return;
                }

                /* If we get here the file has been updated */
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                        "Caution. ypmake may have been run in N2L "
                        "mode. This will NOT initiate a NIS map push. In "
                        "this mode pushes should be initiated with yppush");
        }

        /*
         * If we get here then either the file was updated or there was not
         * a valid old map date (no problem, maybe this is the first time we
         * checked). In either case the old map date entry must be update.
         */
        value.dptr = (char *)&(stats.st_mtime);
        value.dsize = sizeof (time_t);
        dbm_store(map->ttl, key, value, DBM_REPLACE);
}

/*
 * FUNCTION :   init_lock_system()
 *
 * DESCRIPTION: Initializes all the systems related to map locking. This must
 *              be called before any access to the shim functions.
 *
 * GIVEN :      A flag indicating if we are being called from ypserv, which does
 *              not wait for map updates to complete, or other NIS components
 *              which do.
 *
 * RETURNS :    TRUE = Everything worked
 *              FALSE = There were problems
 */
bool_t
init_lock_system(bool_t ypxfrd)
{
        /* Remember what called us */
        if (ypxfrd)
                set_ypxfrd_flag();

        /*
         * Remember PID of process which called us. This enables update threads
         * created by YP children to be handled differently to those created
         * by YP parents.
         */
        parent_pid = getpid();

        /* Init map locks */
        if (!init_lock_map()) {
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Failed to init process synchronization");
                return (FALSE);
        }

        /* If we are in yptol mode set flag indicating the fact */
        init_yptol_flag();

        /*
         * If boot random number system. For now go for reproducible
         * random numbers.
         */
        srand48(0x12345678);

        /*
         * If not N2L mode then no error but do not bother initializing update
         * flags.
         */
        if (yptol_mode) {
                if (!init_update_lock_map()) {
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Failed to init update synchronization");
                        return (FALSE);
                }
        }

        return (TRUE);
}