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
shim_dbm_close(DBM * db)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
shim_dbm_delete(DBM * db,datum key)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
shim_dbm_fetch(DBM * db,datum key)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
shim_dbm_fetch_noupdate(DBM * db,datum key)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
shim_dbm_firstkey(DBM * db)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
shim_dbm_nextkey(DBM * db)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
shim_dbm_do_nextkey(DBM * db,datum inkey)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 *
shim_dbm_open(const char * file,int open_flags,mode_t file_mode)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
shim_dbm_store(DBM * db,datum key,datum content,int store_mode)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
shim_exit(int code)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
init_yptol_flag()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
set_ypxfrd_flag()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
check_old_map_date(map_ctrl * map)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
init_lock_system(bool_t ypxfrd)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 }
673