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 2004 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 */
27
28/*
29 * DESCRIPTION: Contains utilities relating to TTL calculation.
30 */
31#include <unistd.h>
32#include <syslog.h>
33#include <errno.h>
34#include <strings.h>
35#include <ndbm.h>
36#include "ypsym.h"
37#include "ypdefs.h"
38#include "shim.h"
39#include "yptol.h"
40#include "../ldap_util.h"
41
42/*
43 * Constants used in time calculations
44 */
45#define	MILLION 1000000
46
47/*
48 * Decs
49 */
50suc_code is_greater_timeval(struct timeval *, struct timeval *);
51suc_code add_to_timeval(struct timeval *, int);
52
53/*
54 * FUNCTION:	has_entry_expired()
55 *
56 * DESCRIPTION:	Determines if an individual entry has expired.
57 *
58 * INPUTS:	Map control structure for an open map
59 *		Entry key
60 *
61 * OUTPUTS:	TRUE =  Entry has expired or cannot be found this will cause
62 *			missing entries to be pulled out of the DIT.
63 *		FALSE = Entry has not expired
64 *
65 */
66bool_t
67has_entry_expired(map_ctrl *map, datum *key)
68{
69	datum ttl;
70	struct timeval	now;
71	struct timeval	old_time;
72	char	*key_name;
73	char *myself = "has_entry_expired";
74
75	if ((map == NULL) || (map->ttl == NULL))
76		return (FALSE);
77
78	/* Get expiry time entry for key */
79	ttl = dbm_fetch(map->ttl, *key);
80
81	if (NULL == ttl.dptr) {
82		/*
83		 * If we failed to get a map expiry key, which must always be
84		 * present, then something is seriously wrong. Try to recreate
85		 * the map.
86		 */
87		if ((key->dsize == strlen(MAP_EXPIRY_KEY)) &&
88			(0 == strncmp(key->dptr, MAP_EXPIRY_KEY, key->dsize))) {
89			logmsg(MSG_NOTIMECHECK, LOG_ERR, "Cannot find %s TTL "
90				"for map %s. Will attempt to recreate map",
91				MAP_EXPIRY_KEY, map->map_name);
92			return (TRUE);
93		}
94
95		/*
96		 * Not a problem just no TTL entry for this entry. Maybe it has
97		 * not yet been downloaded. Maybe it will be handled by a
98		 * service other than NIS. Check if the entire map has expired.
99		 * This prevents repeated LDAP reads when requests are made for
100		 * nonexistant entries.
101		 */
102		if (has_map_expired(map)) {
103			/* Kick of a map update */
104			update_map_if_required(map, FALSE);
105		}
106
107		/* Don't update the entry */
108		return (FALSE);
109	}
110
111	if (ttl.dsize != sizeof (struct timeval)) {
112		/*
113		 * Need to malloc some memory before can syslog the key name
114		 * but this may fail. Solution log a simple message first THEn
115		 * a more detailed one if it works.
116		 */
117		logmsg(MSG_NOTIMECHECK, LOG_ERR,
118			"Invalid TTL key in map %s. error %d",
119					map->map_name, dbm_error(map->ttl));
120
121		/* Log the key name */
122		key_name = (char *)am(myself, key->dsize + 1);
123		if (NULL == key_name) {
124			logmsg(MSG_NOMEM, LOG_ERR,
125					"Could not alloc memory for keyname");
126		} else {
127			strncpy(key_name, key->dptr, key->dsize);
128			key_name[key->dsize] = '\0';
129			logmsg(MSG_NOTIMECHECK, LOG_ERR,
130						"Key name was %s", key_name);
131			sfree(key_name);
132		}
133		/* Update it Anyway */
134		return (TRUE);
135	}
136
137	/* Get current time */
138	gettimeofday(&now, NULL);
139
140	/*
141	 * Because dptr may not be int aligned need to build an int
142	 * out of what it points to or will get a bus error
143	 */
144	bcopy(ttl.dptr, &old_time, sizeof (struct timeval));
145
146	return (is_greater_timeval(&now, &old_time));
147}
148
149/*
150 * FUNCTION:	has_map_expired()
151 *
152 * DESCRIPTION:	Determines if an entire map has expire
153 *
154 * INPUTS:	Map control structure for an open map
155 *
156 * OUTPUTS:	TRUE = Map has expired
157 *		FALSE  Map has not expired
158 *
159 */
160bool_t
161has_map_expired(map_ctrl *map)
162{
163	datum key;
164
165	/* Set up datum with magic expiry key */
166	key.dsize = strlen(MAP_EXPIRY_KEY);
167	key.dptr = MAP_EXPIRY_KEY;
168
169	/* Call has_entry_expired() with magic map expiry key */
170	return (has_entry_expired(map, &key));
171}
172
173/*
174 * FUNCTION:	update_entry_ttl()
175 *
176 * DESCRIPTION:	Updates the TTL for one map entry
177 *
178 * INPUTS:	Map control structure for an open map
179 *		Entry key
180 *		Flag indication if TTL should be max, min or random
181 *
182 * OUTPUTS:	SUCCESS = TTL updated
183 *		FAILURE = TTL not updated
184 *
185 */
186
187suc_code
188update_entry_ttl(map_ctrl *map, datum *key, TTL_TYPE type)
189{
190	datum expire;
191	struct timeval	now;
192	int	ttl;
193
194	/* Get current time */
195	gettimeofday(&now, NULL);
196
197	/* Get TTL from mapping file */
198	ttl = get_ttl_value(map, type);
199
200	if (FAILURE == add_to_timeval(&now, ttl))
201		return (FAILURE);
202
203	/* Convert time into a datum */
204	expire.dsize = sizeof (struct timeval);
205	expire.dptr = (char *)&now;
206
207	/* Set expiry time entry for key */
208	errno = 0;
209	if (0 > dbm_store(map->ttl, *key, expire, DBM_REPLACE)) {
210		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not write TTL entry "
211						"(errno=%d)", errno);
212		return (FAILURE);
213	}
214
215	return (SUCCESS);
216}
217
218/*
219 * FUNCTION:	update_map_ttl()
220 *
221 * DESCRIPTION:	Updates the TTL for entire map. This can be called either with
222 *		the map open (map_ctrl DBM pointer set up) or the map closed
223 *		(map_ctrl DBM pointers not set). The latter case will occur
224 *		when we have just created a new map.
225 *
226 *		This function must open the TTL map but, in either case, must
227 *		return with the map_ctrl in it's original state.
228 *
229 * INPUTS:	Map control structure for an open map
230 *
231 * OUTPUTS:	SUCCESS = TTL updated
232 *		FAILURE = TTL not updated
233 *
234 */
235suc_code
236update_map_ttl(map_ctrl *map)
237{
238	datum key;
239	bool_t map_was_open = TRUE;
240	suc_code ret;
241
242	/* Set up datum with magic expiry key */
243	key.dsize = strlen(MAP_EXPIRY_KEY);
244	key.dptr = MAP_EXPIRY_KEY;
245
246	/* If TTL not open open it */
247	if (NULL == map->ttl) {
248		map->ttl = dbm_open(map->ttl_path, O_RDWR, 0644);
249		if (NULL == map->ttl)
250			return (FAILURE);
251		map_was_open = FALSE;
252	}
253
254	/* Call update_entry_ttl() with magic map expiry key */
255	ret = update_entry_ttl(map, &key, TTL_MIN);
256
257	/* If we had to open TTL file close it */
258	if (!map_was_open) {
259		dbm_close(map->ttl);
260		map->ttl_path = NULL;
261	}
262
263	return (ret);
264}
265
266/*
267 * FUNCTION:	add_to_timeval()
268 *
269 * DESCRIPTION:	Adds an int to a timeval
270 *
271 * NOTE :	Seems strange that there is not a library function to do this
272 *		if one exists then this function can be removed.
273 *
274 * NOTE :	Does not handle UNIX clock wrap round but this is a much bigger
275 *		problem.
276 *
277 * INPUTS:	Time value to add to
278 *		Time value to add in seconds
279 *
280 * OUTPUTS:	SUCCESS = Addition successful
281 *		FAILURE = Addition failed (probably wrapped)
282 *
283 */
284suc_code
285add_to_timeval(struct timeval *t1, int t2)
286{
287	struct timeval oldval;
288
289	oldval.tv_sec = t1->tv_sec;
290
291	/* Add seconds part */
292	t1->tv_sec += t2;
293
294	/* Check for clock wrap */
295	if (!(t1->tv_sec >= oldval.tv_sec)) {
296		logmsg(MSG_NOTIMECHECK, LOG_ERR,
297			"Wrap when adding %d to %d", t2, oldval.tv_sec);
298		return (FAILURE);
299	}
300
301	return (SUCCESS);
302}
303
304/*
305 * FUNCTION:	is_greater_timeval()
306 *
307 * DESCRIPTION:	Compares two timevals
308 *
309 * NOTE :	Seems strange that there is not a library function to do this
310 *		if one exists then this function can be removed.
311 *
312 * INPUTS:	First time value
313 *		Time value to compare it with
314 *
315 * OUTPUTS:	TRUE t1 > t2
316 *		FALSE t1 <= t2
317 *
318 */
319suc_code
320is_greater_timeval(struct timeval *t1, struct timeval *t2)
321{
322	if (t1->tv_sec > t2->tv_sec)
323		return (TRUE);
324
325	if (t1->tv_sec == t2->tv_sec) {
326		if (t1->tv_usec > t2->tv_usec)
327			return (TRUE);
328		else
329			return (FALSE);
330	}
331
332	return (FALSE);
333}
334