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