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