xref: /freebsd/crypto/krb5/src/util/profile/prof_file.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * prof_file.c ---- routines that manipulate an individual profile file.
4  */
5 
6 #include "prof_int.h"
7 
8 #include <stdio.h>
9 #ifdef HAVE_STDLIB_H
10 #include <stdlib.h>
11 #endif
12 #ifdef HAVE_UNISTD_H
13 #include <unistd.h>
14 #endif
15 #include <string.h>
16 #include <stddef.h>
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <errno.h>
21 
22 #ifdef HAVE_PWD_H
23 #include <pwd.h>
24 #endif
25 
26 #if defined(_WIN32)
27 #include <io.h>
28 #define HAVE_STAT
29 #define stat _stat
30 #ifndef S_ISDIR
31 #define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
32 #endif
33 #endif
34 
35 #include "k5-platform.h"
36 
37 struct global_shared_profile_data {
38     /* This is the head of the global list of shared trees */
39     prf_data_t trees;
40     /* Lock for above list.  */
41     k5_mutex_t mutex;
42 };
43 #define g_shared_trees          (krb5int_profile_shared_data.trees)
44 #define g_shared_trees_mutex    (krb5int_profile_shared_data.mutex)
45 
46 static struct global_shared_profile_data krb5int_profile_shared_data = {
47     0,
48     K5_MUTEX_PARTIAL_INITIALIZER
49 };
50 
51 MAKE_INIT_FUNCTION(profile_library_initializer);
52 MAKE_FINI_FUNCTION(profile_library_finalizer);
53 
profile_library_initializer(void)54 int profile_library_initializer(void)
55 {
56 #ifdef SHOW_INITFINI_FUNCS
57     printf("profile_library_initializer\n");
58 #endif
59     add_error_table(&et_prof_error_table);
60 
61     return k5_mutex_finish_init(&g_shared_trees_mutex);
62 }
profile_library_finalizer(void)63 void profile_library_finalizer(void)
64 {
65     if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING()) {
66 #ifdef SHOW_INITFINI_FUNCS
67         printf("profile_library_finalizer: skipping\n");
68 #endif
69         return;
70     }
71 #ifdef SHOW_INITFINI_FUNCS
72     printf("profile_library_finalizer\n");
73 #endif
74     k5_mutex_destroy(&g_shared_trees_mutex);
75 
76     remove_error_table(&et_prof_error_table);
77 }
78 
79 static void profile_free_file_data(prf_data_t);
80 
rw_access(const_profile_filespec_t filespec)81 static int rw_access(const_profile_filespec_t filespec)
82 {
83 #ifdef HAVE_ACCESS
84     if (access(filespec, W_OK) == 0)
85         return 1;
86     else
87         return 0;
88 #else
89     /*
90      * We're on a substandard OS that doesn't support access.  So
91      * we kludge a test using stdio routines, and hope fopen
92      * checks the r/w permissions.
93      */
94     FILE    *f;
95 
96     f = fopen(filespec, "r+");
97     if (f) {
98         fclose(f);
99         return 1;
100     }
101     return 0;
102 #endif
103 }
104 
r_access(const_profile_filespec_t filespec)105 static int r_access(const_profile_filespec_t filespec)
106 {
107 #ifdef HAVE_ACCESS
108     if (access(filespec, R_OK) == 0)
109         return 1;
110     else
111         return 0;
112 #else
113     /*
114      * We're on a substandard OS that doesn't support access.  So
115      * we kludge a test using stdio routines, and hope fopen
116      * checks the r/w permissions.
117      */
118     FILE    *f;
119 
120     f = fopen(filespec, "r");
121     if (f) {
122         fclose(f);
123         return 1;
124     }
125     return 0;
126 #endif
127 }
128 
profile_file_is_writable(prf_file_t profile)129 int profile_file_is_writable(prf_file_t profile)
130 {
131     if (profile && profile->data) {
132         return rw_access(profile->data->filespec);
133     } else {
134         return 0;
135     }
136 }
137 
138 prf_data_t
profile_make_prf_data(const char * filename)139 profile_make_prf_data(const char *filename)
140 {
141     prf_data_t d;
142     size_t len, flen, slen;
143     char *fcopy;
144 
145     flen = strlen(filename);
146     slen = offsetof(struct _prf_data_t, filespec);
147     len = slen + flen + 1;
148     if (len < sizeof(struct _prf_data_t))
149         len = sizeof(struct _prf_data_t);
150     d = malloc(len);
151     if (d == NULL)
152         return NULL;
153     memset(d, 0, len);
154     fcopy = (char *) d + slen;
155     assert(fcopy == d->filespec);
156     strlcpy(fcopy, filename, flen + 1);
157     d->refcount = 1;
158     d->magic = PROF_MAGIC_FILE_DATA;
159     d->root = NULL;
160     d->next = NULL;
161     d->fslen = flen;
162     return d;
163 }
164 
profile_open_file(const_profile_filespec_t filespec,prf_file_t * ret_prof,char ** ret_modspec)165 errcode_t profile_open_file(const_profile_filespec_t filespec,
166                             prf_file_t *ret_prof, char **ret_modspec)
167 {
168     prf_file_t      prf;
169     errcode_t       retval;
170     char            *home_env = 0;
171     prf_data_t      data;
172     char            *expanded_filename;
173 
174     retval = CALL_INIT_FUNCTION(profile_library_initializer);
175     if (retval)
176         return retval;
177 
178     prf = malloc(sizeof(struct _prf_file_t));
179     if (!prf)
180         return ENOMEM;
181     memset(prf, 0, sizeof(struct _prf_file_t));
182     prf->magic = PROF_MAGIC_FILE;
183 
184     if (filespec[0] == '~' && filespec[1] == '/') {
185         home_env = secure_getenv("HOME");
186 #ifdef HAVE_PWD_H
187         if (home_env == NULL) {
188             uid_t uid;
189             struct passwd *pw, pwx;
190             char pwbuf[BUFSIZ];
191 
192             uid = getuid();
193             if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
194                 && pw != NULL && pw->pw_dir[0] != 0)
195                 home_env = pw->pw_dir;
196         }
197 #endif
198     }
199     if (home_env) {
200         if (asprintf(&expanded_filename, "%s%s", home_env,
201                      filespec + 1) < 0)
202             expanded_filename = 0;
203     } else
204         expanded_filename = strdup(filespec);
205     if (expanded_filename == 0) {
206         free(prf);
207         return ENOMEM;
208     }
209 
210     k5_mutex_lock(&g_shared_trees_mutex);
211     for (data = g_shared_trees; data; data = data->next) {
212         if (!strcmp(data->filespec, expanded_filename)
213             /* Check that current uid has read access.  */
214             && r_access(data->filespec))
215             break;
216     }
217     if (data) {
218         data->refcount++;
219         data->last_stat = 0;    /* Make sure to stat when updating. */
220         k5_mutex_unlock(&g_shared_trees_mutex);
221         retval = profile_update_file_data(data, NULL);
222         free(expanded_filename);
223         if (retval) {
224             profile_dereference_data(data);
225             free(prf);
226             return retval;
227         }
228         prf->data = data;
229         *ret_prof = prf;
230         return 0;
231     }
232     k5_mutex_unlock(&g_shared_trees_mutex);
233     data = profile_make_prf_data(expanded_filename);
234     if (data == NULL) {
235         free(prf);
236         free(expanded_filename);
237         return ENOMEM;
238     }
239     free(expanded_filename);
240     prf->data = data;
241 
242     retval = k5_mutex_init(&data->lock);
243     if (retval) {
244         free(data);
245         free(prf);
246         return retval;
247     }
248 
249     retval = profile_update_file(prf, ret_modspec);
250     if (retval) {
251         profile_close_file(prf);
252         return retval;
253     }
254 
255     k5_mutex_lock(&g_shared_trees_mutex);
256     data->flags |= PROFILE_FILE_SHARED;
257     data->next = g_shared_trees;
258     g_shared_trees = data;
259     k5_mutex_unlock(&g_shared_trees_mutex);
260 
261     *ret_prof = prf;
262     return 0;
263 }
264 
profile_update_file_data_locked(prf_data_t data,char ** ret_modspec)265 errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec)
266 {
267     errcode_t retval;
268 #ifdef HAVE_STAT
269     struct stat st;
270     unsigned long frac;
271     time_t now;
272 #endif
273     FILE *f;
274     int isdir = 0;
275 
276     if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL)
277         return 0;
278 
279 #ifdef HAVE_STAT
280     now = time(0);
281     if (now == data->last_stat && data->root != NULL) {
282         return 0;
283     }
284     if (stat(data->filespec, &st)) {
285         return errno;
286     }
287     data->last_stat = now;
288 #if defined HAVE_STRUCT_STAT_ST_MTIMENSEC
289     frac = st.st_mtimensec;
290 #elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
291     frac = st.st_mtimespec.tv_nsec;
292 #elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
293     frac = st.st_mtim.tv_nsec;
294 #else
295     frac = 0;
296 #endif
297     if (st.st_mtime == data->timestamp
298         && frac == data->frac_ts
299         && data->root != NULL) {
300         return 0;
301     }
302     if (data->root) {
303         profile_free_node(data->root);
304         data->root = 0;
305     }
306 
307     /* Only try to reload regular files, not devices such as pipes. */
308     if ((st.st_mode & S_IFMT) != S_IFREG)
309         data->flags |= PROFILE_FILE_NO_RELOAD;
310 #else
311     /*
312      * If we don't have the stat() call, assume that our in-core
313      * memory image is correct.  That is, we won't reread the
314      * profile file if it changes.
315      */
316     if (data->root) {
317         return 0;
318     }
319 #endif
320 
321 #ifdef HAVE_STAT
322     isdir = S_ISDIR(st.st_mode);
323 #endif
324     if (!isdir) {
325         errno = 0;
326         f = fopen(data->filespec, "r");
327         if (f == NULL)
328             return (errno != 0) ? errno : ENOENT;
329         set_cloexec_file(f);
330     }
331 
332     data->upd_serial++;
333     data->flags &= ~PROFILE_FILE_DIRTY;
334 
335     if (isdir) {
336         retval = profile_process_directory(data->filespec, &data->root);
337     } else {
338         retval = profile_parse_file(f, &data->root, ret_modspec);
339         (void)fclose(f);
340     }
341     if (retval) {
342         return retval;
343     }
344     assert(data->root != NULL);
345 #ifdef HAVE_STAT
346     data->timestamp = st.st_mtime;
347     data->frac_ts = frac;
348 #endif
349     return 0;
350 }
351 
profile_update_file_data(prf_data_t data,char ** ret_modspec)352 errcode_t profile_update_file_data(prf_data_t data, char **ret_modspec)
353 {
354     errcode_t retval;
355 
356     k5_mutex_lock(&data->lock);
357     retval = profile_update_file_data_locked(data, ret_modspec);
358     k5_mutex_unlock(&data->lock);
359     return retval;
360 }
361 
362 static int
make_hard_link(const char * oldpath,const char * newpath)363 make_hard_link(const char *oldpath, const char *newpath)
364 {
365 #ifdef _WIN32
366     return -1;
367 #else
368     return link(oldpath, newpath);
369 #endif
370 }
371 
write_data_to_file(prf_data_t data,const char * outfile,int can_create)372 static errcode_t write_data_to_file(prf_data_t data, const char *outfile,
373                                     int can_create)
374 {
375     FILE            *f;
376     profile_filespec_t new_file;
377     profile_filespec_t old_file;
378     errcode_t       retval = 0;
379 
380     retval = ENOMEM;
381 
382     new_file = old_file = 0;
383     if (asprintf(&new_file, "%s.$$$", outfile) < 0) {
384         new_file = NULL;
385         goto errout;
386     }
387     if (asprintf(&old_file, "%s.bak", outfile) < 0) {
388         old_file = NULL;
389         goto errout;
390     }
391 
392     errno = 0;
393 
394     f = fopen(new_file, "w");
395     if (!f) {
396         retval = errno;
397         if (retval == 0)
398             retval = PROF_FAIL_OPEN;
399         goto errout;
400     }
401 
402     set_cloexec_file(f);
403     profile_write_tree_file(data->root, f);
404     if (fclose(f) != 0) {
405         retval = errno;
406         goto errout;
407     }
408 
409     unlink(old_file);
410     if (make_hard_link(outfile, old_file) == 0) {
411         /* Okay, got the hard link.  Yay.  Now we've got our
412            backup version, so just put the new version in
413            place.  */
414         if (rename(new_file, outfile)) {
415             /* Weird, the rename didn't work.  But the old version
416                should still be in place, so no special cleanup is
417                needed.  */
418             retval = errno;
419             goto errout;
420         }
421     } else if (errno == ENOENT && can_create) {
422         if (rename(new_file, outfile)) {
423             retval = errno;
424             goto errout;
425         }
426     } else {
427         /* Couldn't make the hard link, so there's going to be a
428            small window where data->filespec does not refer to
429            either version.  */
430 #ifndef _WIN32
431         sync();
432 #endif
433         if (rename(outfile, old_file)) {
434             retval = errno;
435             goto errout;
436         }
437         if (rename(new_file, outfile)) {
438             retval = errno;
439             rename(old_file, outfile); /* back out... */
440             goto errout;
441         }
442     }
443 
444     retval = 0;
445 
446 errout:
447     if (new_file)
448         free(new_file);
449     if (old_file)
450         free(old_file);
451     return retval;
452 }
453 
profile_flush_file_data_to_buffer(prf_data_t data,char ** bufp)454 errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
455 {
456     errcode_t       retval;
457 
458     k5_mutex_lock(&data->lock);
459     retval = profile_write_tree_to_buffer(data->root, bufp);
460     k5_mutex_unlock(&data->lock);
461     return retval;
462 }
463 
profile_flush_file_data(prf_data_t data)464 errcode_t profile_flush_file_data(prf_data_t data)
465 {
466     errcode_t       retval = 0;
467 
468     if (!data || data->magic != PROF_MAGIC_FILE_DATA)
469         return PROF_MAGIC_FILE_DATA;
470 
471     k5_mutex_lock(&data->lock);
472 
473     if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
474         k5_mutex_unlock(&data->lock);
475         return 0;
476     }
477 
478     retval = write_data_to_file(data, data->filespec, 0);
479     data->flags &= ~PROFILE_FILE_DIRTY;
480     k5_mutex_unlock(&data->lock);
481     return retval;
482 }
483 
profile_flush_file_data_to_file(prf_data_t data,const char * outfile)484 errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
485 {
486     errcode_t retval = 0;
487 
488     if (!data || data->magic != PROF_MAGIC_FILE_DATA)
489         return PROF_MAGIC_FILE_DATA;
490 
491     k5_mutex_lock(&data->lock);
492     retval = write_data_to_file(data, outfile, 1);
493     k5_mutex_unlock(&data->lock);
494     return retval;
495 }
496 
497 
498 
profile_dereference_data(prf_data_t data)499 void profile_dereference_data(prf_data_t data)
500 {
501     k5_mutex_lock(&g_shared_trees_mutex);
502     profile_dereference_data_locked(data);
503     k5_mutex_unlock(&g_shared_trees_mutex);
504 }
profile_dereference_data_locked(prf_data_t data)505 void profile_dereference_data_locked(prf_data_t data)
506 {
507     data->refcount--;
508     if (data->refcount == 0)
509         profile_free_file_data(data);
510 }
511 
profile_lock_global()512 void profile_lock_global()
513 {
514     k5_mutex_lock(&g_shared_trees_mutex);
515 }
profile_unlock_global()516 void profile_unlock_global()
517 {
518     k5_mutex_unlock(&g_shared_trees_mutex);
519 }
520 
profile_free_file(prf_file_t prf)521 void profile_free_file(prf_file_t prf)
522 {
523     profile_dereference_data(prf->data);
524     free(prf);
525 }
526 
527 /* Call with mutex locked!  */
profile_free_file_data(prf_data_t data)528 static void profile_free_file_data(prf_data_t data)
529 {
530     if (data->flags & PROFILE_FILE_SHARED) {
531         /* Remove from linked list.  */
532         if (g_shared_trees == data)
533             g_shared_trees = data->next;
534         else {
535             prf_data_t prev, next;
536             prev = g_shared_trees;
537             next = prev->next;
538             while (next) {
539                 if (next == data) {
540                     prev->next = next->next;
541                     break;
542                 }
543                 prev = next;
544                 next = next->next;
545             }
546         }
547     }
548     if (data->root)
549         profile_free_node(data->root);
550     data->magic = 0;
551     k5_mutex_destroy(&data->lock);
552     free(data);
553 }
554 
profile_close_file(prf_file_t prf)555 errcode_t profile_close_file(prf_file_t prf)
556 {
557     errcode_t       retval;
558 
559     retval = profile_flush_file(prf);
560     if (retval)
561         return retval;
562     profile_free_file(prf);
563     return 0;
564 }
565