xref: /freebsd/crypto/krb5/src/util/profile/prof_init.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * prof_init.c --- routines that manipulate the user-visible profile_t
4  *      object.
5  */
6 
7 #include "prof_int.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 #ifdef HAVE_STDLIB_H
12 #include <stdlib.h>
13 #endif
14 #include <errno.h>
15 
16 /* Create a vtable profile, possibly with a library handle.  The new profile
17  * takes ownership of the handle refcount on success. */
18 static errcode_t
init_module(struct profile_vtable * vtable,void * cbdata,prf_lib_handle_t handle,profile_t * ret_profile)19 init_module(struct profile_vtable *vtable, void *cbdata,
20             prf_lib_handle_t handle, profile_t *ret_profile)
21 {
22     profile_t profile;
23     struct profile_vtable *vt_copy;
24 
25     /* Check that the vtable's minor version is sane and that mandatory methods
26      * are implemented. */
27     if (vtable->minor_ver < 1 || !vtable->get_values || !vtable->free_values)
28         return EINVAL;
29     if (vtable->cleanup && !vtable->copy)
30         return EINVAL;
31     if (vtable->iterator_create &&
32         (!vtable->iterator || !vtable->iterator_free || !vtable->free_string))
33         return EINVAL;
34 
35     profile = malloc(sizeof(*profile));
36     if (!profile)
37         return ENOMEM;
38     memset(profile, 0, sizeof(*profile));
39 
40     vt_copy = malloc(sizeof(*vt_copy));
41     if (!vt_copy) {
42         free(profile);
43         return ENOMEM;
44     }
45     /* It's safe to just copy the caller's vtable for now.  If the minor
46      * version is bumped, we'll need to copy individual fields. */
47     *vt_copy = *vtable;
48 
49     profile->vt = vt_copy;
50     profile->cbdata = cbdata;
51     profile->lib_handle = handle;
52     profile->magic = PROF_MAGIC_PROFILE;
53     *ret_profile = profile;
54     return 0;
55 }
56 
57 /* Parse modspec into the module path and residual string. */
58 static errcode_t
parse_modspec(const char * modspec,char ** ret_path,char ** ret_residual)59 parse_modspec(const char *modspec, char **ret_path, char **ret_residual)
60 {
61     const char *p;
62     char *path, *fullpath, *residual;
63     errcode_t ret;
64 
65     *ret_path = *ret_residual = NULL;
66 
67     /* Find the separator, skipping a Windows drive letter if present. */
68     p = (*modspec != '\0' && modspec[1] == ':') ? modspec + 2 : modspec;
69     p = strchr(p, ':');
70     if (p == NULL)
71         return PROF_MODULE_SYNTAX;
72 
73     /* Copy the path. */
74     path = malloc(p - modspec + 1);
75     if (path == NULL)
76         return ENOMEM;
77     memcpy(path, modspec, p - modspec);
78     path[p - modspec] = '\0';
79 
80     /* Compose the path with LIBDIR if it's not absolute. */
81     ret = k5_path_join(LIBDIR, path, &fullpath);
82     free(path);
83     if (ret)
84         return ret;
85 
86     residual = strdup(p + 1);
87     if (residual == NULL) {
88         free(fullpath);
89         return ENOMEM;
90     }
91 
92     *ret_path = fullpath;
93     *ret_residual = residual;
94     return 0;
95 }
96 
97 /* Load a dynamic profile module as specified by modspec and create a vtable
98  * profile for it in *ret_profile. */
99 static errcode_t
init_load_module(const char * modspec,profile_t * ret_profile)100 init_load_module(const char *modspec, profile_t *ret_profile)
101 {
102     char *modpath = NULL, *residual = NULL;
103     struct errinfo einfo = { 0 };
104     prf_lib_handle_t lib_handle = NULL;
105     struct plugin_file_handle *plhandle = NULL;
106     void *cbdata = NULL, (*fptr)();
107     int have_lock = 0, have_cbdata = 0;
108     struct profile_vtable vtable = { 1 };  /* Set minor_ver to 1, rest null. */
109     errcode_t err;
110     profile_module_init_fn initfn;
111 
112     err = parse_modspec(modspec, &modpath, &residual);
113     if (err)
114         goto cleanup;
115 
116     /* Allocate a reference-counted library handle container. */
117     lib_handle = malloc(sizeof(*lib_handle));
118     if (lib_handle == NULL)
119         goto cleanup;
120     err = k5_mutex_init(&lib_handle->lock);
121     if (err)
122         goto cleanup;
123     have_lock = 1;
124 
125     /* Open the module and get its initializer. */
126     err = krb5int_open_plugin(modpath, &plhandle, &einfo);
127     if (err)
128         goto cleanup;
129     err = krb5int_get_plugin_func(plhandle, "profile_module_init", &fptr,
130                                   &einfo);
131     if (err == ENOENT)
132         err = PROF_MODULE_INVALID;
133     if (err)
134         goto cleanup;
135 
136     /* Get the profile vtable and callback data pointer. */
137     initfn = (profile_module_init_fn)fptr;
138     err = (*initfn)(residual, &vtable, &cbdata);
139     if (err)
140         goto cleanup;
141     have_cbdata = 1;
142 
143     /* Create a vtable profile with the information obtained. */
144     lib_handle->plugin_handle = plhandle;
145     lib_handle->refcount = 1;
146     err = init_module(&vtable, cbdata, lib_handle, ret_profile);
147 
148 cleanup:
149     free(modpath);
150     free(residual);
151     k5_clear_error(&einfo);
152     if (err) {
153         if (have_cbdata && vtable.cleanup)
154             vtable.cleanup(cbdata);
155         if (have_lock)
156             k5_mutex_destroy(&lib_handle->lock);
157         free(lib_handle);
158         if (plhandle)
159             krb5int_close_plugin(plhandle);
160     }
161     return err;
162 }
163 
164 errcode_t KRB5_CALLCONV
profile_init_flags(const_profile_filespec_t * files,int flags,profile_t * ret_profile)165 profile_init_flags(const_profile_filespec_t *files, int flags,
166                    profile_t *ret_profile)
167 {
168     const_profile_filespec_t *fs;
169     profile_t profile;
170     prf_file_t  new_file, last = 0;
171     errcode_t retval = 0, access_retval = 0;
172     char *modspec = NULL, **modspec_arg;
173 
174     profile = malloc(sizeof(struct _profile_t));
175     if (!profile)
176         return ENOMEM;
177     memset(profile, 0, sizeof(struct _profile_t));
178     profile->magic = PROF_MAGIC_PROFILE;
179 
180     /*
181      * If the filenames list is not specified or empty, return an empty
182      * profile.
183      */
184     if ( files && !PROFILE_LAST_FILESPEC(*files) ) {
185         for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) {
186             /* Allow a module declaration if it is permitted by flags and this
187              * is the first file parsed. */
188             modspec_arg = ((flags & PROFILE_INIT_ALLOW_MODULE) && !last) ?
189                 &modspec : NULL;
190             retval = profile_open_file(*fs, &new_file, modspec_arg);
191             if (retval == PROF_MODULE && modspec) {
192                 /* Stop parsing files and load a dynamic module instead. */
193                 free(profile);
194                 retval = init_load_module(modspec, ret_profile);
195                 free(modspec);
196                 return retval;
197             }
198             /* if this file is missing, skip to the next */
199             if (retval == ENOENT) {
200                 continue;
201             }
202             /* If we can't read this file, remember it but keep going. */
203             if (retval == EACCES || retval == EPERM) {
204                 access_retval = retval;
205                 continue;
206             }
207             if (retval) {
208                 profile_release(profile);
209                 return retval;
210             }
211             if (last)
212                 last->next = new_file;
213             else
214                 profile->first_file = new_file;
215             last = new_file;
216         }
217         /*
218          * If last is still null after the loop, then all the files were
219          * missing or unreadable, so return the appropriate error.
220          */
221         if (!last) {
222             profile_release(profile);
223             return access_retval ? access_retval : ENOENT;
224         }
225     }
226 
227     *ret_profile = profile;
228     return 0;
229 }
230 
231 errcode_t KRB5_CALLCONV
profile_init(const_profile_filespec_t * files,profile_t * ret_profile)232 profile_init(const_profile_filespec_t *files, profile_t *ret_profile)
233 {
234     return profile_init_flags(files, 0, ret_profile);
235 }
236 
237 errcode_t KRB5_CALLCONV
profile_init_vtable(struct profile_vtable * vtable,void * cbdata,profile_t * ret_profile)238 profile_init_vtable(struct profile_vtable *vtable, void *cbdata,
239                     profile_t *ret_profile)
240 {
241     return init_module(vtable, cbdata, NULL, ret_profile);
242 }
243 
244 /* Copy a vtable profile. */
245 static errcode_t
copy_vtable_profile(profile_t profile,profile_t * ret_new_profile)246 copy_vtable_profile(profile_t profile, profile_t *ret_new_profile)
247 {
248     errcode_t err;
249     void *cbdata;
250     profile_t new_profile;
251 
252     *ret_new_profile = NULL;
253 
254     if (profile->vt->copy) {
255         /* Make a copy of profile's cbdata for the new profile. */
256         err = profile->vt->copy(profile->cbdata, &cbdata);
257         if (err)
258             return err;
259         err = init_module(profile->vt, cbdata, profile->lib_handle,
260                           &new_profile);
261         if (err && profile->vt->cleanup)
262             profile->vt->cleanup(cbdata);
263     } else {
264         /* Use the same cbdata as the old profile. */
265         err = init_module(profile->vt, profile->cbdata, profile->lib_handle,
266                           &new_profile);
267     }
268     if (err)
269         return err;
270 
271     /* Increment the refcount on the library handle if there is one. */
272     if (profile->lib_handle) {
273         k5_mutex_lock(&profile->lib_handle->lock);
274         profile->lib_handle->refcount++;
275         k5_mutex_unlock(&profile->lib_handle->lock);
276     }
277 
278     *ret_new_profile = new_profile;
279     return 0;
280 }
281 
282 #define COUNT_LINKED_LIST(COUNT, PTYPE, START, FIELD)   \
283     {                                                   \
284         size_t cll_counter = 0;                         \
285         PTYPE cll_ptr = (START);                        \
286         while (cll_ptr != NULL) {                       \
287             cll_counter++;                              \
288             cll_ptr = cll_ptr->FIELD;                   \
289         }                                               \
290         (COUNT) = cll_counter;                          \
291     }
292 
293 errcode_t KRB5_CALLCONV
profile_copy(profile_t old_profile,profile_t * new_profile)294 profile_copy(profile_t old_profile, profile_t *new_profile)
295 {
296     size_t size, i;
297     const_profile_filespec_t *files;
298     prf_file_t file;
299     errcode_t err;
300 
301     if (old_profile->vt)
302         return copy_vtable_profile(old_profile, new_profile);
303 
304     /* The fields we care about are read-only after creation, so
305        no locking is needed.  */
306     COUNT_LINKED_LIST (size, prf_file_t, old_profile->first_file, next);
307     files = malloc ((size+1) * sizeof(*files));
308     if (files == NULL)
309         return ENOMEM;
310     for (i = 0, file = old_profile->first_file; i < size; i++, file = file->next)
311         files[i] = file->data->filespec;
312     files[size] = NULL;
313     err = profile_init (files, new_profile);
314     free (files);
315     return err;
316 }
317 
318 errcode_t KRB5_CALLCONV
profile_init_path(const_profile_filespec_list_t filepath,profile_t * ret_profile)319 profile_init_path(const_profile_filespec_list_t filepath,
320                   profile_t *ret_profile)
321 {
322     unsigned int n_entries;
323     int i;
324     unsigned int ent_len;
325     const char *s, *t;
326     profile_filespec_t *filenames;
327     errcode_t retval;
328 
329     /* count the distinct filename components */
330     for(s = filepath, n_entries = 1; *s; s++) {
331         if (*s == ':')
332             n_entries++;
333     }
334 
335     /* the array is NULL terminated */
336     filenames = (profile_filespec_t*) malloc((n_entries+1) * sizeof(char*));
337     if (filenames == 0)
338         return ENOMEM;
339 
340     /* measure, copy, and skip each one */
341     for(s = filepath, i=0; (t = strchr(s, ':')) || (t=s+strlen(s)); s=t+1, i++) {
342         ent_len = (unsigned int) (t-s);
343         filenames[i] = (char*) malloc(ent_len + 1);
344         if (filenames[i] == 0) {
345             /* if malloc fails, free the ones that worked */
346             while(--i >= 0) free(filenames[i]);
347             free(filenames);
348             return ENOMEM;
349         }
350         strncpy(filenames[i], s, ent_len);
351         filenames[i][ent_len] = 0;
352         if (*t == 0) {
353             i++;
354             break;
355         }
356     }
357     /* cap the array */
358     filenames[i] = 0;
359 
360     retval = profile_init_flags((const_profile_filespec_t *) filenames, 0,
361                                 ret_profile);
362 
363     /* count back down and free the entries */
364     while(--i >= 0) free(filenames[i]);
365     free(filenames);
366 
367     return retval;
368 }
369 
370 errcode_t KRB5_CALLCONV
profile_is_writable(profile_t profile,int * writable)371 profile_is_writable(profile_t profile, int *writable)
372 {
373     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
374         return PROF_MAGIC_PROFILE;
375 
376     if (!writable)
377         return EINVAL;
378     *writable = 0;
379 
380     if (profile->vt) {
381         if (profile->vt->writable)
382             return profile->vt->writable(profile->cbdata, writable);
383         else
384             return 0;
385     }
386 
387     if (profile->first_file)
388         *writable = profile_file_is_writable(profile->first_file);
389 
390     return 0;
391 }
392 
393 errcode_t KRB5_CALLCONV
profile_is_modified(profile_t profile,int * modified)394 profile_is_modified(profile_t profile, int *modified)
395 {
396     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
397         return PROF_MAGIC_PROFILE;
398 
399     if (!modified)
400         return EINVAL;
401     *modified = 0;
402 
403     if (profile->vt) {
404         if (profile->vt->modified)
405             return profile->vt->modified(profile->cbdata, modified);
406         else
407             return 0;
408     }
409 
410     if (profile->first_file)
411         *modified = (profile->first_file->data->flags & PROFILE_FILE_DIRTY);
412 
413     return 0;
414 }
415 
416 errcode_t KRB5_CALLCONV
profile_flush(profile_t profile)417 profile_flush(profile_t profile)
418 {
419     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
420         return PROF_MAGIC_PROFILE;
421 
422     if (profile->vt) {
423         if (profile->vt->flush)
424             return profile->vt->flush(profile->cbdata);
425         return 0;
426     }
427 
428     if (profile->first_file)
429         return profile_flush_file(profile->first_file);
430 
431     return 0;
432 }
433 
434 errcode_t KRB5_CALLCONV
profile_flush_to_file(profile_t profile,const_profile_filespec_t outfile)435 profile_flush_to_file(profile_t profile, const_profile_filespec_t outfile)
436 {
437     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
438         return PROF_MAGIC_PROFILE;
439 
440     if (profile->vt)
441         return PROF_UNSUPPORTED;
442 
443     if (profile->first_file)
444         return profile_flush_file_to_file(profile->first_file,
445                                           outfile);
446 
447     return 0;
448 }
449 
450 errcode_t KRB5_CALLCONV
profile_flush_to_buffer(profile_t profile,char ** buf)451 profile_flush_to_buffer(profile_t profile, char **buf)
452 {
453     if (profile->vt)
454         return PROF_UNSUPPORTED;
455     return profile_flush_file_data_to_buffer(profile->first_file->data, buf);
456 }
457 
458 void KRB5_CALLCONV
profile_free_buffer(profile_t profile,char * buf)459 profile_free_buffer(profile_t profile, char *buf)
460 {
461     free(buf);
462 }
463 
464 void KRB5_CALLCONV
profile_abandon(profile_t profile)465 profile_abandon(profile_t profile)
466 {
467     prf_file_t      p, next;
468 
469     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
470         return;
471 
472     if (profile->vt) {
473         if (profile->vt->cleanup)
474             profile->vt->cleanup(profile->cbdata);
475         if (profile->lib_handle) {
476             /* Decrement the refcount on the handle and maybe free it. */
477             k5_mutex_lock(&profile->lib_handle->lock);
478             if (--profile->lib_handle->refcount == 0) {
479                 krb5int_close_plugin(profile->lib_handle->plugin_handle);
480                 k5_mutex_unlock(&profile->lib_handle->lock);
481                 k5_mutex_destroy(&profile->lib_handle->lock);
482                 free(profile->lib_handle);
483             } else
484                 k5_mutex_unlock(&profile->lib_handle->lock);
485         }
486         free(profile->vt);
487     } else {
488         for (p = profile->first_file; p; p = next) {
489             next = p->next;
490             profile_free_file(p);
491         }
492     }
493     profile->magic = 0;
494     free(profile);
495 }
496 
497 void KRB5_CALLCONV
profile_release(profile_t profile)498 profile_release(profile_t profile)
499 {
500     prf_file_t      p, next;
501 
502     if (!profile || profile->magic != PROF_MAGIC_PROFILE)
503         return;
504 
505     if (profile->vt) {
506         /* Flush the profile and then delegate to profile_abandon. */
507         if (profile->vt->flush)
508             profile->vt->flush(profile->cbdata);
509         profile_abandon(profile);
510         return;
511     } else {
512         for (p = profile->first_file; p; p = next) {
513             next = p->next;
514             profile_close_file(p);
515         }
516     }
517     profile->magic = 0;
518     free(profile);
519 }
520 
521 /*
522  * Here begins the profile serialization functions.
523  */
profile_ser_size(const char * unused,profile_t profile,size_t * sizep)524 errcode_t profile_ser_size(const char *unused, profile_t profile,
525                            size_t *sizep)
526 {
527     size_t      required;
528     prf_file_t  pfp;
529 
530     required = 3*sizeof(int32_t);
531     for (pfp = profile->first_file; pfp; pfp = pfp->next) {
532         required += sizeof(int32_t);
533         required += strlen(pfp->data->filespec);
534     }
535     *sizep += required;
536     return 0;
537 }
538 
pack_int32(int32_t oval,unsigned char ** bufpp,size_t * remainp)539 static void pack_int32(int32_t oval, unsigned char **bufpp, size_t *remainp)
540 {
541     store_32_be(oval, *bufpp);
542     *bufpp += sizeof(int32_t);
543     *remainp -= sizeof(int32_t);
544 }
545 
profile_ser_externalize(const char * unused,profile_t profile,unsigned char ** bufpp,size_t * remainp)546 errcode_t profile_ser_externalize(const char *unused, profile_t profile,
547                                   unsigned char **bufpp, size_t *remainp)
548 {
549     errcode_t           retval;
550     size_t              required;
551     unsigned char       *bp;
552     size_t              remain;
553     prf_file_t          pfp;
554     int32_t             fcount, slen;
555 
556     required = 0;
557     bp = *bufpp;
558     remain = *remainp;
559     retval = EINVAL;
560     if (profile) {
561         retval = ENOMEM;
562         (void) profile_ser_size(unused, profile, &required);
563         if (required <= remain) {
564             fcount = 0;
565             for (pfp = profile->first_file; pfp; pfp = pfp->next)
566                 fcount++;
567             pack_int32(PROF_MAGIC_PROFILE, &bp, &remain);
568             pack_int32(fcount, &bp, &remain);
569             for (pfp = profile->first_file; pfp; pfp = pfp->next) {
570                 slen = (int32_t) strlen(pfp->data->filespec);
571                 pack_int32(slen, &bp, &remain);
572                 if (slen) {
573                     memcpy(bp, pfp->data->filespec, (size_t) slen);
574                     bp += slen;
575                     remain -= (size_t) slen;
576                 }
577             }
578             pack_int32(PROF_MAGIC_PROFILE, &bp, &remain);
579             retval = 0;
580             *bufpp = bp;
581             *remainp = remain;
582         }
583     }
584     return(retval);
585 }
586 
unpack_int32(int32_t * intp,unsigned char ** bufpp,size_t * remainp)587 static int unpack_int32(int32_t *intp, unsigned char **bufpp,
588                         size_t *remainp)
589 {
590     if (*remainp >= sizeof(int32_t)) {
591         *intp = load_32_be(*bufpp);
592         *bufpp += sizeof(int32_t);
593         *remainp -= sizeof(int32_t);
594         return 0;
595     }
596     else
597         return 1;
598 }
599 
profile_ser_internalize(const char * unused,profile_t * profilep,unsigned char ** bufpp,size_t * remainp)600 errcode_t profile_ser_internalize(const char *unused, profile_t *profilep,
601                                   unsigned char **bufpp, size_t *remainp)
602 {
603     errcode_t               retval;
604     unsigned char   *bp;
605     size_t          remain;
606     int                     i;
607     int32_t                 fcount, tmp;
608     profile_filespec_t              *flist = 0;
609 
610     bp = *bufpp;
611     remain = *remainp;
612     fcount = 0;
613 
614     if (remain >= 12)
615         (void) unpack_int32(&tmp, &bp, &remain);
616     else
617         tmp = 0;
618 
619     if (tmp != PROF_MAGIC_PROFILE) {
620         retval = EINVAL;
621         goto cleanup;
622     }
623 
624     (void) unpack_int32(&fcount, &bp, &remain);
625     retval = ENOMEM;
626 
627     flist = (profile_filespec_t *) malloc(sizeof(profile_filespec_t) * (size_t) (fcount + 1));
628     if (!flist)
629         goto cleanup;
630 
631     memset(flist, 0, sizeof(char *) * (size_t) (fcount+1));
632     for (i=0; i<fcount; i++) {
633         if (!unpack_int32(&tmp, &bp, &remain)) {
634             flist[i] = (char *) malloc((size_t) (tmp+1));
635             if (!flist[i])
636                 goto cleanup;
637             memcpy(flist[i], bp, (size_t) tmp);
638             flist[i][tmp] = '\0';
639             bp += tmp;
640             remain -= (size_t) tmp;
641         }
642     }
643 
644     if (unpack_int32(&tmp, &bp, &remain) ||
645         (tmp != PROF_MAGIC_PROFILE)) {
646         retval = EINVAL;
647         goto cleanup;
648     }
649 
650     if ((retval = profile_init((const_profile_filespec_t *) flist,
651                                profilep)))
652         goto cleanup;
653 
654     *bufpp = bp;
655     *remainp = remain;
656 
657 cleanup:
658     if (flist) {
659         for (i=0; i<fcount; i++) {
660             if (flist[i])
661                 free(flist[i]);
662         }
663         free(flist);
664     }
665     return(retval);
666 }
667