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 2015 Gary Mills
  24  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
  25  * Use is subject to license terms.
  26  */
  27 
  28 /*
  29  * DESCRIPTION: Contains the map update thread and related code.
  30  */
  31 
  32 #include <unistd.h>
  33 #include <syslog.h>
  34 #include <ndbm.h>
  35 #include <thread.h>
  36 #include <unistd.h>
  37 #include <strings.h>
  38 #include "ypsym.h"
  39 #include "ypdefs.h"
  40 #include "shim.h"
  41 #include "yptol.h"
  42 #include "../ldap_util.h"
  43 
  44 /* Enable standard YP code features defined in ypdefs.h */
  45 USE_YP_PREFIX
  46 USE_YP_MASTER_NAME
  47 USE_YP_LAST_MODIFIED
  48 USE_YP_INPUT_FILE
  49 USE_YP_OUTPUT_NAME
  50 USE_YP_DOMAIN_NAME
  51 USE_YP_SECURE
  52 USE_YP_INTERDOMAIN
  53 
  54 /*
  55  * Decs
  56  */
  57 suc_code update_from_dit(map_ctrl *, datum *);
  58 void * update_thread(void *);
  59 
  60 /*
  61  * Globals
  62  */
  63 extern pid_t parent_pid;
  64 
  65 /*
  66  * FUNCTION:    update_entry_if_required()
  67  *
  68  * DESCRIPTION: Determines if an entry is to be updated and if it is does the
  69  *              update.
  70  *
  71  * GIVEN :      Pointer to the open map ctrl
  72  *              Pointer to the entry key
  73  *
  74  * RETURNS :    SUCCESS = Entry is in a state to be returned to the client
  75  *              i.e. either got updated, did not need to be updated or we are
  76  *              in a mode where it is acceptable to return out of date
  77  *              information.
  78  *              FAILURE = Entry need an update but it could not be done.
  79  */
  80 suc_code
  81 update_entry_if_required(map_ctrl *map, datum *key)
  82 {
  83 
  84         /* Only update individual entries if entire map is */
  85         /* not being updated */
  86         if (is_map_updating(map))
  87                 return (SUCCESS);
  88 
  89         /*
  90          * If we are being asked for the order then need to check if
  91          * the map is in need of an update. If it is then fake a
  92          * recent order. The client will then read the map, using
  93          * dbm_firstkey and this will do the update.
  94          */
  95         if (0 == strncmp(key->dptr, yp_last_modified, yp_last_modified_sz)) {
  96                 if (has_map_expired(map))
  97                         update_timestamp(map->entries);
  98                 return (SUCCESS);
  99         }
 100 
 101         /* Never update special keys. Have no TTLs */
 102         if (is_special_key(key))
 103                 return (SUCCESS);
 104 
 105         if (!has_entry_expired(map, key))
 106                 /* Didn't need an update */
 107                 return (SUCCESS);
 108 
 109         /* Do the update */
 110         return (update_from_dit(map, key));
 111 }
 112 
 113 /*
 114  * FUNCTION:    update_from_dit()
 115  *
 116  * DESCRIPTION: Called to update an entry from the DIT
 117  *
 118  * INPUTS:      Map control structure for an open map
 119  *              Entry key
 120  *
 121  * OUTPUTS:     SUCCESS = Update complete or we are in a mode where it is
 122  *              acceptable to return out of date information.
 123  *              FAILURE =  Update failed
 124  *
 125  */
 126 suc_code
 127 update_from_dit(map_ctrl *map, datum *key)
 128 {
 129         datum dat;
 130         int ret;
 131         suc_code res;
 132 
 133         /*
 134          * Netgroup maps are a special case we cannot update just one entry so
 135          * update the entire map instead.
 136          */
 137         if ((0 == strcmp(map->map_name, NETGROUP_BYHOST)) ||
 138                 (0 == strcmp(map->map_name, NETGROUP_BYUSER))) {
 139                         return (update_map_if_required(map, FALSE));
 140         }
 141 
 142         /* Read entry from the DIT */
 143         ret = read_from_dit(map->map_name, map->domain, key, &dat);
 144 
 145         /* Check that we got something */
 146         if (NULL == dat.dptr) {
 147                 if (0 == ret) {
 148                         /*
 149                          * In a mode where it is acceptable to return out of
 150                          * date information.
 151                          */
 152                         logmsg(MSG_NOTIMECHECK, LOG_INFO,
 153                                 "LDAP inaccessible returning old information");
 154                         return (SUCCESS);
 155                 } else {
 156                         /*
 157                          * In a mode where it is not acceptable to return out
 158                          * of date information.
 159                          *
 160                          * If the error positviely indicates that there is no
 161                          * such entry delete it. For errors where object may
 162                          * still exist in the DIT leave it.
 163                          */
 164                         if (MAP_NO_MATCHING_KEY == ret) {
 165                                 /*
 166                                  * Don't log errors. If the entry was not
 167                                  * already present then no problem. The user
 168                                  * just asked us for a non existant entry.
 169                                  */
 170                                 dbm_delete(map->entries, *key);
 171                                 dbm_delete(map->ttl, *key);
 172                         }
 173                         return (FAILURE);
 174                 }
 175         }
 176 
 177         /* Write it to DBM */
 178         res = dbm_store(map->entries, *key, dat, DBM_REPLACE);
 179         sfree(dat.dptr);
 180 
 181         if (SUCCESS != res)
 182                 return (FAILURE);
 183 
 184         /* Update TTL */
 185         update_entry_ttl(map, key, TTL_RUNNING);
 186 
 187         return (SUCCESS);
 188 }
 189 
 190 /*
 191  * FUNCTION:    update_map_if_required()
 192  *
 193  * DESCRIPTION: Called to update an entire map if it is out of date. Map ctrl
 194  *              must be locked before this is called. This handles checking if
 195  *              the map is already being updated. It is important that this is
 196  *              done atomically with obtaining the maps update lock.
 197  *
 198  * INPUTS:      Map control structure for an open map
 199  *              Flag indication if we should wait for completion
 200  *
 201  * OUTPUTS:     SUCCESS = Map update initiated
 202  *              FAILURE =  Map update not initiated
 203  */
 204 suc_code
 205 update_map_if_required(map_ctrl *map, bool_t wait)
 206 {
 207         thread_t tid;
 208         map_ctrl *new_map;
 209         suc_code res;
 210         long     flags;
 211 
 212         if (wait) {
 213                 /*
 214                  * Actually get the lock
 215                  *
 216                  * May block so unlock map_ctrl while it is done
 217                  */
 218                 unlock_map_ctrl(map);
 219                 res = lock_map_update(map);
 220                 lock_map_ctrl(map);
 221                 if (SUCCESS != res) {
 222                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 223                                 "Could not lock map %s for update",
 224                                                                 map->map_name);
 225                         return (FAILURE);
 226                 }
 227         } else {
 228                 /* If not waiting try to get the lock */
 229                 switch (try_lock_map_update(map)) {
 230                         case 0:
 231                                 /*
 232                                  * We got the lock. Continue to start an update.
 233                                  */
 234                                 break;
 235 
 236                         case EBUSY:
 237                                 /*
 238                                  * Some one else got the lock. OK they are
 239                                  * doing the update so we can just return.
 240                                  */
 241                                 return (SUCCESS);
 242 
 243                         default:
 244                                 /*
 245                                  * Some serious problem with lock.
 246                                  */
 247                                 return (FAILURE);
 248                 }
 249         }
 250 
 251         /*
 252          * If we get here are holding the update lock. Make a final check that
 253          * nobody beat us to the map update while we were getting it.
 254          */
 255         if (!has_map_expired(map)) {
 256                 /* A big waste of time. Somebody else did the update */
 257                 unlock_map_update(map);
 258                 return (SUCCESS);
 259         }
 260 
 261         /*
 262          * We got the lock and nobody beat us to doing the update. Start our
 263          * own update.
 264          *
 265          * Thread will free the update lock when update is complete.
 266          */
 267 
 268 
 269         /*
 270          * Make a copy of the map_ctrl structure so the update thread has an
 271          * independent version to work with. Note: Must not be on stack.
 272          *
 273          * On exit the update thread must free this.
 274          */
 275         new_map = dup_map_ctrl(map);
 276         if (NULL == new_map) {
 277                 unlock_map_update(map);
 278                 return (FAILURE);
 279         }
 280 
 281         /*
 282          * While thread is running unlock map so other processes can
 283          * execute non update related accesses
 284          */
 285         unlock_map_ctrl(map);
 286 
 287         flags = THR_BOUND | THR_NEW_LWP;
 288 
 289         /*
 290          * If we are not going to thr_join then need to create detached.
 291          * This prevents a zombie being left when nobody joins us.
 292          */
 293         if (!wait && (getpid() == parent_pid))
 294                 flags |= THR_DETACHED;
 295 
 296         /* Kick off update thread */
 297         if (0 != thr_create(NULL, NULL, update_thread, new_map,
 298                                                         flags, &tid)) {
 299                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
 300                                 "Could not create NIS update thread");
 301                 free_map_ctrl(new_map);
 302                 unlock_map_update(map);
 303                 if (SUCCESS != lock_map_ctrl(map))
 304                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 305                         "Could not acquire update lock for %s", map->map_name);
 306                 return (FAILURE);
 307         }
 308 
 309         if (wait) {
 310                 /* May block but no problem map_ctrl is already unlocked. */
 311                 thr_join(tid, NULL, NULL);
 312         }
 313 
 314         /* Re acquire lock */
 315         if (1 != lock_map_ctrl(map)) {
 316                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
 317                         "Could not re-acquire lock for %s", map->map_name);
 318                 return (FAILURE);
 319         }
 320 
 321         return (SUCCESS);
 322 }
 323 
 324 /*
 325  * FUNCTION:    update_thread()
 326  *
 327  * DESCRIPTION: The update thread this is called to update an entire NIS map.
 328  *              if several NIS maps are found to be out of date several
 329  *              instances of this may be running at the same time.
 330  *
 331  *              Since we are using a duplicate map_ctrl we do not have to lock
 332  *              it. If we did would end up using the same mutex as the parent
 333  *              map ctrl an possibly deadlocking.
 334  *
 335  * INPUTS:      Map handle (because we need access to name and lock)
 336  *
 337  * OUTPUTS:     None exits when finished.
 338  */
 339 
 340 void *
 341 update_thread(void *arg)
 342 {
 343         void *ret = (void *)-1;
 344         map_ctrl *map;
 345 
 346         /* Cast argument pointer to correct type */
 347         map = (map_ctrl *)arg;
 348 
 349         /* Actually do the work */
 350         if (SUCCESS == update_map_from_dit(map, FALSE))
 351                 ret = 0;
 352 
 353         /* Update complete or failed */
 354         unlock_map_update(map);
 355 
 356         /* Free up duplicate copy of the map_ctrl */
 357         free_map_ctrl(map);
 358 
 359         thr_exit(ret);
 360 
 361         return (NULL);
 362 }
 363 
 364 /*
 365  * FUNCTION :   is_special_key()
 366  *
 367  * DESCRIPTION: Works out if a given key is one of the special ones. We just
 368  *              check for the "YP_" prefix. This is not 100% safe but if
 369  *              valid keys with a "YP_" prefix exist in the DIT then a lot of
 370  *              other parts of NIS wont work.
 371  */
 372 bool_t
 373 is_special_key(datum *key)
 374 {
 375         if (0 == strncmp(key->dptr, yp_prefix, yp_prefix_sz))
 376                 return (TRUE);
 377 
 378         return (FALSE);
 379 }