1/*
2 * prof_file.c ---- routines that manipulate an individual profile file.
3 */
4
5#include "prof_int.h"
6
7#include <stdio.h>
8#ifdef HAVE_STDLIB_H
9#include <stdlib.h>
10#endif
11#ifdef HAVE_UNISTD_H
12#include <unistd.h>
13#endif
14#include <string.h>
15#include <stddef.h>
16
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <errno.h>
20
21#ifdef HAVE_PWD_H
22#include <pwd.h>
23#endif
24
25#if defined(_WIN32)
26#include <io.h>
27#define HAVE_STAT
28#define stat _stat
29#endif
30
31#ifdef __APPLE__
32#include <notify.h>
33#include <syslog.h>
34#endif
35
36struct global_shared_profile_data {
37	/* This is the head of the global list of shared trees */
38	prf_data_t trees;
39	/* Lock for above list.  */
40	pthread_mutex_t mutex;
41};
42#define g_shared_trees		(krb5int_profile_shared_data.trees)
43#define g_shared_trees_mutex	(krb5int_profile_shared_data.mutex)
44
45
46#define APPLE_NOTIFICATION_NAME "com.apple.Kerberos.configuration-changed"
47static int notify_token = -1;
48
49static struct global_shared_profile_data krb5int_profile_shared_data = {
50    0,
51    PTHREAD_MUTEX_INITIALIZER
52};
53
54void
55profile_library_initializer(void)
56{
57}
58
59static void profile_free_file_data(prf_data_t);
60
61#if 0
62
63#define scan_shared_trees_locked()				\
64	{							\
65	    prf_data_t d;					\
66	    k5_mutex_assert_locked(&g_shared_trees_mutex);	\
67	    for (d = g_shared_trees; d; d = d->next) {		\
68		assert(d->magic == PROF_MAGIC_FILE_DATA);	\
69		assert((d->flags & PROFILE_FILE_SHARED) != 0);	\
70		assert(d->filespec[0] != 0);			\
71		assert(d->fslen <= 1000); /* XXX */		\
72		assert(d->filespec[d->fslen] == 0);		\
73		assert(d->fslen = strlen(d->filespec));		\
74		assert(d->root != NULL);			\
75	    }							\
76	}
77
78#define scan_shared_trees_unlocked()			\
79	{						\
80	    int r;					\
81	    r = pthread_mutex_lock(&g_shared_trees_mutex);	\
82	    assert (r == 0);				\
83	    scan_shared_trees_locked();			\
84	    pthread_mutex_unlock(&g_shared_trees_mutex);	\
85	}
86
87#else
88
89#define scan_shared_trees_locked()	{ ; }
90#define scan_shared_trees_unlocked()	{ ; }
91
92#endif
93
94static int rw_access(const_profile_filespec_t filespec)
95{
96#ifdef HAVE_ACCESS
97	if (access(filespec, W_OK) == 0)
98		return 1;
99	else
100		return 0;
101#else
102	/*
103	 * We're on a substandard OS that doesn't support access.  So
104	 * we kludge a test using stdio routines, and hope fopen
105	 * checks the r/w permissions.
106	 */
107	FILE	*f;
108
109	f = fopen(filespec, "r+");
110	if (f) {
111		fclose(f);
112		return 1;
113	}
114	return 0;
115#endif
116}
117
118static int r_access(prf_data_t data)
119{
120#ifdef __APPLE__
121    if (data->uid != geteuid())
122	return 0;
123    return 1;
124#else
125    const_profile_filespec_t filespec = data->filespec;
126#ifdef HAVE_ACCESS
127	if (access(filespec, R_OK) == 0)
128		return 1;
129	else
130		return 0;
131#else
132	/*
133	 * We're on a substandard OS that doesn't support access.  So
134	 * we kludge a test using stdio routines, and hope fopen
135	 * checks the r/w permissions.
136	 */
137	FILE	*f;
138
139	f = fopen(filespec, "r");
140	if (f) {
141		fclose(f);
142		return 1;
143	}
144	return 0;
145#endif
146#endif /* __APPLE__ */
147}
148
149int profile_file_is_writable(prf_file_t profile)
150{
151    if (profile && profile->data) {
152        return rw_access(profile->data->filespec);
153    } else {
154        return 0;
155    }
156}
157
158prf_data_t
159profile_make_prf_data(const char *filename)
160{
161    prf_data_t d;
162    size_t len, flen, slen;
163    char *fcopy;
164
165    flen = strlen(filename);
166    slen = offsetof(struct _prf_data_t, filespec);
167    len = slen + flen + 1;
168    if (len < sizeof(struct _prf_data_t))
169	len = sizeof(struct _prf_data_t);
170    d = malloc(len);
171    if (d == NULL)
172	return NULL;
173    memset(d, 0, len);
174    fcopy = (char *) d + slen;
175    assert(fcopy == d->filespec);
176    strlcpy(fcopy, filename, flen + 1);
177    d->refcount = 1;
178    d->comment = NULL;
179    d->magic = PROF_MAGIC_FILE_DATA;
180    d->root = NULL;
181    d->next = NULL;
182    d->fslen = flen;
183#ifdef __APPLE__
184    d->uid = geteuid();
185    d->flags |= PROFILE_FILE_INVALID;
186#endif
187    return d;
188}
189
190errcode_t profile_open_file(const_profile_filespec_t filespec,
191			    prf_file_t *ret_prof)
192{
193	prf_file_t	prf;
194	errcode_t	retval;
195	char		*home_env = 0;
196	prf_data_t	data;
197	char		*expanded_filename;
198
199	scan_shared_trees_unlocked();
200
201	prf = malloc(sizeof(struct _prf_file_t));
202	if (!prf)
203		return ENOMEM;
204	memset(prf, 0, sizeof(struct _prf_file_t));
205	prf->magic = PROF_MAGIC_FILE;
206
207	if (filespec[0] == '~' && filespec[1] == '/') {
208		home_env = getenv("HOME");
209#ifdef HAVE_PWD_H
210		if (home_env == NULL) {
211		    uid_t uid;
212		    struct passwd *pw, pwx;
213		    char pwbuf[BUFSIZ];
214
215		    uid = getuid();
216		    if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
217			&& pw != NULL && pw->pw_dir[0] != 0)
218			home_env = pw->pw_dir;
219		}
220#endif
221	}
222	if (home_env) {
223	    if (asprintf(&expanded_filename, "%s%s", home_env,
224			 filespec + 1) < 0)
225		expanded_filename = 0;
226	} else
227	    expanded_filename = strdup(filespec);
228	if (expanded_filename == 0) {
229	    free(prf);
230	    return ENOMEM;
231	}
232
233	retval = pthread_mutex_lock(&g_shared_trees_mutex);
234	if (retval) {
235	    free(expanded_filename);
236	    free(prf);
237	    scan_shared_trees_unlocked();
238	    return retval;
239	}
240
241#ifdef __APPLE__
242	/*
243	 * Check semaphore, and if set, invalidate all shared pages.
244	 */
245	if (notify_token != -1) {
246	    int check = 0;
247	    if (notify_check(notify_token, &check) == 0 && check)
248		for (data = g_shared_trees; data; data = data->next)
249		    data->flags |= PROFILE_FILE_INVALID;
250	}
251#endif
252
253	scan_shared_trees_locked();
254	for (data = g_shared_trees; data; data = data->next) {
255	    if (!strcmp(data->filespec, expanded_filename)
256		/* Check that current uid has read access.  */
257		&& r_access(data))
258		break;
259	}
260	if (data) {
261	    data->refcount++;
262	    (void) pthread_mutex_unlock(&g_shared_trees_mutex);
263	    retval = profile_update_file_data(data);
264	    free(expanded_filename);
265	    if (retval == 0) {
266		prf->data = data;
267		*ret_prof = prf;
268	    } else {
269		data->refcount--;
270		free(prf);
271		*ret_prof = NULL;
272	    }
273	    scan_shared_trees_unlocked();
274	    return retval;
275	}
276#ifdef __APPLE__
277	/* lets find a matching cache entry  */
278	for (data = g_shared_trees; data; data = data->next) {
279	    if (!strcmp(data->filespec, expanded_filename) &&
280		(data->flags & PROFILE_FILE_SHARED))
281	    {
282		profile_dereference_data_locked(data);
283		break;
284	    }
285	}
286	/* Didn't fine one, so lets remove last cached entry. This
287	   make this a LRU with the length of the maxium length is was
288	   the concurrent used entries.
289	*/
290	if (data == NULL) {
291	    prf_data_t	last = NULL;
292
293	    for (data = g_shared_trees; data; data = data->next) {
294		if ((data->flags & PROFILE_FILE_SHARED) && data->refcount == 1)
295		    last = data;
296	    }
297	    if (last)
298		profile_dereference_data_locked(last);
299	}
300#endif
301	(void) pthread_mutex_unlock(&g_shared_trees_mutex);
302	data = profile_make_prf_data(expanded_filename);
303	if (data == NULL) {
304	    free(prf);
305	    free(expanded_filename);
306	    return ENOMEM;
307	}
308	free(expanded_filename);
309	prf->data = data;
310
311	retval = pthread_mutex_init(&data->lock, NULL);
312	if (retval) {
313	    free(data);
314	    free(prf);
315	    return retval;
316	}
317
318	retval = profile_update_file_data(prf->data);
319	if (retval != 0 && retval != ENOENT) {
320		profile_close_file(prf);
321		return retval;
322	}
323
324	retval = pthread_mutex_lock(&g_shared_trees_mutex);
325	if (retval != 0 && retval != ENOENT) {
326	    profile_close_file(prf);
327	    scan_shared_trees_unlocked();
328	    return retval;
329	}
330	scan_shared_trees_locked();
331	data->flags |= PROFILE_FILE_SHARED;
332#ifdef __APPLE__
333	data->refcount++;
334#endif
335	data->next = g_shared_trees;
336	g_shared_trees = data;
337	scan_shared_trees_locked();
338
339#ifdef __APPLE__
340	if (notify_token == -1)
341	    notify_register_check(APPLE_NOTIFICATION_NAME, &notify_token);
342#endif
343
344	(void) pthread_mutex_unlock(&g_shared_trees_mutex);
345
346	*ret_prof = prf;
347	return 0;
348}
349
350void profile_configuration_updated(void)
351{
352#ifdef __APPLE__
353    notify_post(APPLE_NOTIFICATION_NAME);
354#endif
355}
356
357errcode_t profile_update_file_data(prf_data_t data)
358{
359	errcode_t retval;
360#ifndef __APPLE__
361#ifdef HAVE_STAT
362	struct stat st;
363	unsigned long frac;
364	time_t now;
365#endif
366#endif /* !__APPLE__ */
367	FILE *f;
368
369	retval = pthread_mutex_lock(&data->lock);
370	if (retval)
371	    return retval;
372
373#ifdef __APPLE__
374	retval = (data->flags & (PROFILE_FILE_INVALID|PROFILE_FILE_HAVE_DATA));
375	if (retval == PROFILE_FILE_HAVE_DATA) {
376	    retval = 0;
377	    if (data->root == NULL)
378		retval = ENOENT;
379	    pthread_mutex_unlock(&data->lock);
380	    return retval;
381	}
382	data->flags &= ~PROFILE_FILE_INVALID; /* invalid would be striped of below, but humor us */
383#else /* !__APPLE__ */
384#ifdef HAVE_STAT
385	now = time(0);
386	if (now == data->last_stat && data->root != NULL) {
387	    pthread_mutex_unlock(&data->lock);
388	    return 0;
389	}
390	if (stat(data->filespec, &st)) {
391	    retval = errno;
392	    pthread_mutex_unlock(&data->lock);
393	    return retval;
394	}
395	data->last_stat = now;
396#if defined HAVE_STRUCT_STAT_ST_MTIMENSEC
397	frac = st.st_mtimensec;
398#elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
399	frac = st.st_mtimespec.tv_nsec;
400#elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
401	frac = st.st_mtim.tv_nsec;
402#else
403	frac = 0;
404#endif
405	if (st.st_mtime == data->timestamp
406	    && frac == data->frac_ts
407	    && data->root != NULL) {
408	    pthread_mutex_unlock(&data->lock);
409	    return 0;
410	}
411#else
412	/*
413	 * If we don't have the stat() call, assume that our in-core
414	 * memory image is correct.  That is, we won't reread the
415	 * profile file if it changes.
416	 */
417	if (data->root) {
418	    pthread_mutex_unlock(&data->lock);
419	    return 0;
420	}
421#endif
422#endif /* !__APPLE__ */
423	if (data->root) {
424		profile_free_node(data->root);
425		data->root = 0;
426	}
427	if (data->comment) {
428		free(data->comment);
429		data->comment = 0;
430	}
431
432	data->upd_serial++;
433	data->flags &= PROFILE_FILE_SHARED;  /* FIXME same as '=' operator */
434
435	errno = 0;
436	f = fopen(data->filespec, "r");
437	if (f == NULL) {
438		retval = errno;
439		if (retval == 0)
440			retval = ENOENT;
441		if (retval == ENOENT)
442			data->flags |= PROFILE_FILE_HAVE_DATA;
443		pthread_mutex_unlock(&data->lock);
444		return retval;
445	}
446#if 0
447	set_cloexec_file(f);
448#endif
449	retval = profile_parse_file(f, &data->root);
450	fclose(f);
451	if (retval) {
452	    pthread_mutex_unlock(&data->lock);
453	    return retval;
454	}
455	assert(data->root != NULL);
456#ifndef __APPLE__
457#ifdef HAVE_STAT
458	data->timestamp = st.st_mtime;
459	data->frac_ts = frac;
460#endif
461#endif
462	data->flags |= PROFILE_FILE_HAVE_DATA;
463	pthread_mutex_unlock(&data->lock);
464	return 0;
465}
466
467static int
468make_hard_link(const char *oldpath, const char *newpath)
469{
470#ifdef _WIN32
471    return -1;
472#else
473    return link(oldpath, newpath);
474#endif
475}
476
477static errcode_t write_data_to_file(prf_data_t data, const char *outfile,
478				    int can_create)
479{
480	FILE		*f;
481	profile_filespec_t new_file;
482	profile_filespec_t old_file;
483	errcode_t	retval = 0;
484
485	retval = ENOMEM;
486
487	new_file = old_file = 0;
488	if (asprintf(&new_file, "%s.$$$", outfile) < 0) {
489	    new_file = NULL;
490	    goto errout;
491	}
492	if (asprintf(&old_file, "%s.bak", outfile) < 0) {
493	    old_file = NULL;
494	    goto errout;
495	}
496
497	errno = 0;
498
499	f = fopen(new_file, "w");
500	if (!f) {
501		retval = errno;
502		if (retval == 0)
503			retval = PROF_FAIL_OPEN;
504		goto errout;
505	}
506#if 0
507	set_cloexec_file(f);
508#endif
509	if (data->root)
510	    profile_write_tree_file(data->root, f);
511	if (fclose(f) != 0) {
512		retval = errno;
513		goto errout;
514	}
515
516	unlink(old_file);
517	if (make_hard_link(outfile, old_file) == 0) {
518	    /* Okay, got the hard link.  Yay.  Now we've got our
519	       backup version, so just put the new version in
520	       place.  */
521	    if (rename(new_file, outfile)) {
522		/* Weird, the rename didn't work.  But the old version
523		   should still be in place, so no special cleanup is
524		   needed.  */
525		retval = errno;
526		goto errout;
527	    }
528	} else if (errno == ENOENT && can_create) {
529	    if (rename(new_file, outfile)) {
530		retval = errno;
531		goto errout;
532	    }
533	} else {
534	    /* Couldn't make the hard link, so there's going to be a
535	       small window where data->filespec does not refer to
536	       either version.  */
537#ifndef _WIN32
538	    sync();
539#endif
540	    if (rename(outfile, old_file)) {
541		retval = errno;
542		goto errout;
543	    }
544	    if (rename(new_file, outfile)) {
545		retval = errno;
546		rename(old_file, outfile); /* back out... */
547		goto errout;
548	    }
549	}
550
551	data->flags = 0;
552	retval = 0;
553
554	profile_configuration_updated();
555
556errout:
557	if (new_file)
558		free(new_file);
559	if (old_file)
560		free(old_file);
561	return retval;
562}
563
564errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
565{
566	errcode_t	retval;
567	retval = pthread_mutex_lock(&data->lock);
568	if (retval)
569		return retval;
570	retval = profile_write_tree_to_buffer(data->root, bufp);
571	pthread_mutex_unlock(&data->lock);
572	return retval;
573}
574
575errcode_t profile_flush_file_data(prf_data_t data)
576{
577	errcode_t	retval = 0;
578
579	if (!data || data->magic != PROF_MAGIC_FILE_DATA)
580		return PROF_MAGIC_FILE_DATA;
581
582	retval = pthread_mutex_lock(&data->lock);
583	if (retval)
584	    return retval;
585
586	if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
587	    pthread_mutex_unlock(&data->lock);
588	    return 0;
589	}
590
591	retval = write_data_to_file(data, data->filespec, 0);
592	pthread_mutex_unlock(&data->lock);
593	return retval;
594}
595
596errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
597{
598    errcode_t retval = 0;
599
600    if (!data || data->magic != PROF_MAGIC_FILE_DATA)
601	return PROF_MAGIC_FILE_DATA;
602
603    retval = pthread_mutex_lock(&data->lock);
604    if (retval)
605	return retval;
606    retval = write_data_to_file(data, outfile, 1);
607    pthread_mutex_unlock(&data->lock);
608    return retval;
609}
610
611
612
613void profile_dereference_data(prf_data_t data)
614{
615    int err;
616    err = pthread_mutex_lock(&g_shared_trees_mutex);
617    if (err)
618	return;
619    profile_dereference_data_locked(data);
620    (void) pthread_mutex_unlock(&g_shared_trees_mutex);
621}
622void profile_dereference_data_locked(prf_data_t data)
623{
624    scan_shared_trees_locked();
625    data->refcount--;
626    if (data->refcount == 0)
627	profile_free_file_data(data);
628    scan_shared_trees_locked();
629}
630
631int profile_lock_global()
632{
633    return pthread_mutex_lock(&g_shared_trees_mutex);
634}
635int profile_unlock_global()
636{
637    return pthread_mutex_unlock(&g_shared_trees_mutex);
638}
639
640void profile_free_file(prf_file_t prf)
641{
642    profile_dereference_data(prf->data);
643    free(prf);
644}
645
646/* Call with mutex locked!  */
647static void profile_free_file_data(prf_data_t data)
648{
649    scan_shared_trees_locked();
650    if (data->flags & PROFILE_FILE_SHARED) {
651	/* Remove from linked list.  */
652	if (g_shared_trees == data)
653	    g_shared_trees = data->next;
654	else {
655	    prf_data_t prev, next;
656	    prev = g_shared_trees;
657	    next = prev->next;
658	    while (next) {
659		if (next == data) {
660		    prev->next = next->next;
661		    break;
662		}
663		prev = next;
664		next = next->next;
665	    }
666	}
667    }
668    if (data->root)
669	profile_free_node(data->root);
670    if (data->comment)
671	free(data->comment);
672    data->magic = 0;
673    pthread_mutex_destroy(&data->lock);
674    free(data);
675    scan_shared_trees_locked();
676}
677
678errcode_t profile_close_file(prf_file_t prf)
679{
680	errcode_t	retval;
681
682	if (prf == NULL)
683		return 0;
684
685	retval = profile_flush_file(prf);
686	profile_free_file(prf);
687	if (retval)
688		return retval;
689	return 0;
690}
691