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 */
46USE_YP_PREFIX
47USE_YP_MASTER_NAME
48USE_YP_LAST_MODIFIED
49USE_YP_INPUT_FILE
50USE_YP_OUTPUT_NAME
51USE_YP_DOMAIN_NAME
52USE_YP_SECURE
53USE_YP_INTERDOMAIN
54
55/*
56 * Decs
57 */
58suc_code update_from_dit(map_ctrl *, datum *);
59void * update_thread(void *);
60
61/*
62 * Globals
63 */
64extern 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 */
81suc_code
82update_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 */
127suc_code
128update_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 */
205suc_code
206update_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
341void *
342update_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 */
371bool_t
372is_special_key(datum *key)
373{
374	if (0 == strncmp(key->dptr, yp_prefix, yp_prefix_sz))
375		return (TRUE);
376
377	return (FALSE);
378}
379