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 2015 Gary Mills
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * DESCRIPTION: Contains the top level shim hook functions. These must have
29 *		identical interfaces to the equivalent standard dbm calls.
30 *
31 *		Unfortunately many of these will do a copy of a datum structure
32 *		on return. This is a side effect of the original DBM function
33 *		being written to pass structures rather than pointers.
34 *
35 * NOTE :	There is a major bug/feature in dbm. A key obtained by
36 *		dbm_nextkey() of dbm_firstkey() cannot be passed to dbm_store().
37 *		When the store occurs dbm's internal memory get's reorganized
38 *		and the static strings pointed to by the key are destroyed. The
39 *		data is then stored in the wrong place. We attempt to get round
40 *		this by dbm_firstkey() and dbm_nextkey() making a copy of the
41 *		key data in malloced memory. This is freed when map_ctrl is
42 *		freed.
43 */
44
45#include <unistd.h>
46#include <syslog.h>
47#include <ndbm.h>
48#include <strings.h>
49#include "ypsym.h"
50#include "ypdefs.h"
51#include "shim.h"
52#include "yptol.h"
53#include "stubs.h"
54#include "../ldap_parse.h"
55#include "../ldap_util.h"
56
57/*
58 * Globals
59 */
60bool_t yptol_mode = FALSE;	/* Set if in N2L mode */
61bool_t yptol_newlock = FALSE;
62				/*
63				 * Set if in N2L mode and we want to use the new
64				 * lock mapping mechanism
65				 */
66bool_t ypxfrd_flag = FALSE;	/* Set if called from ypxfrd */
67pid_t parent_pid;			/* ID of calling parent process */
68
69
70/*
71 * Decs
72 */
73void 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 */
90void
91shim_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 */
115int
116shim_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 */
151datum
152shim_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 */
190datum
191shim_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 */
219datum
220shim_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 */
309datum
310shim_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 */
350datum
351shim_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 */
382DBM *
383shim_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 */
453int
454shim_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 */
506void
507shim_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 */
526void
527init_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 */
545void
546set_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 */
562void
563check_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 */
629bool_t
630init_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