source: libabac/abac_aspect.c @ d037f54

mei_rt2mei_rt2_fix_1
Last change on this file since d037f54 was d037f54, checked in by Mei <mei@…>, 12 years ago

1) able to programmatially build structure, bake attribute credential

write it out to a .der file and use prover to bring it back and
process just like other .der files.
-- tested with rt1 like policy without constraint.

2) changed abac_term structure alittle to ready it for 2 pass code

gen.

  • Property mode set to 100644
File size: 19.3 KB
Line 
1
2/**
3**  abac_aspect.c
4**/
5
6
7#include <assert.h>
8#include <stdlib.h>
9#include <stdio.h>
10#include <string.h>
11
12#include "abac_list.h"
13#include "abac_util.h"
14
15#include "abac_internal.h"
16
17static int debug=0;
18/*
19   A.role <- B
20   A.role <- B.role
21   A.role <- B.role.role
22
23   A.oset <- B
24   A.oset <- Obj
25   A.oset <- B.oset
26   A.oset <- B.role.oset
27*/
28
29/* can store either a role or oset */
30struct _abac_aspect_t {
31    int aspect_type;
32    int is_object;
33
34    char *principal_name;
35    char *principal_name_p; // pPrincipal_name
36    abac_term_t *principal_object;
37
38    char *linked_role_name;
39    abac_param_list_t *linked_role_params;
40
41    char *aspect_name;
42    abac_param_list_t *aspect_params;
43
44    char *type_string; // "oset" or "role"
45
46    abac_list_t *prereqs;
47    int refcount;
48};
49
50/**************************************************************/
51bool abac_aspect_is_oset(abac_aspect_t *ptr)
52{
53    assert(ptr);
54    if(ptr->aspect_type==e_ASPECTTYPE_OSET)
55        return 1;
56    return 0;
57}
58
59
60bool abac_aspect_is_role(abac_aspect_t *ptr)
61{
62    assert(ptr);
63    if(ptr->aspect_type==e_ASPECTTYPE_ROLE)
64        return 1;
65    return 0;
66}
67
68bool abac_aspect_is_intersecting(abac_aspect_t *ptr)
69{
70    assert(ptr);
71    if(ptr->aspect_type==e_ASPECTTYPE_INTERSECTING)
72        return 1;
73    return 0;
74}
75
76/* A.role->B or A.oset->B */
77bool abac_aspect_is_principal(abac_aspect_t *ptr) {
78    assert(ptr != NULL);
79    return ptr->is_object == 0 &&
80             ptr->aspect_name == NULL && 
81               ptr->linked_role_name == NULL && 
82                 ptr->prereqs == NULL;
83}
84
85/* A.oset->O */
86bool abac_aspect_is_object(abac_aspect_t *ptr)
87{
88    assert(ptr);
89    if(ptr->aspect_type == e_ASPECTTYPE_OSET)
90        return ptr->is_object;
91    return 0;
92}
93
94/* A.role->B.role.role or A.oset->B.role.oset */
95bool abac_aspect_is_linking(abac_aspect_t *ptr) 
96{
97    assert(ptr);
98    return ptr->linked_role_name != NULL;
99}
100
101/**
102 * True if an aspect is an intersection.
103 */
104bool abac_aspect_is_intersection(abac_aspect_t *ptr)
105{
106    assert(ptr);
107    return ptr->prereqs != NULL;
108}
109
110/**
111 * return the type that is common to all the intersecting aspects
112 */ 
113int abac_aspect_intersecting_aspect_type(abac_aspect_t *ptr)
114{
115    assert(ptr);
116    assert(ptr->prereqs);
117    abac_aspect_t *cur;
118    int first=1;
119    int type;
120    abac_list_foreach(ptr->prereqs, cur,
121            if(first) {
122               type=abac_aspect_aspect_type(cur);
123               first=0;
124               } else {
125                   if(abac_aspect_aspect_type(cur) != type)
126                       errx(1,"Intersecting tails are not of the same type");
127            }
128        );
129    return type;
130}
131
132/**
133 * Returns the prereqs of an intersection.
134 */
135abac_list_t *abac_aspect_prereqs(abac_aspect_t *ptr)
136{
137    assert(ptr);
138    return ptr->prereqs;
139}
140
141char* abac_aspect_type_string(abac_aspect_t *ptr)
142{
143    assert(ptr);
144    return ptr->type_string;
145}
146
147int abac_aspect_aspect_type(abac_aspect_t *ptr)
148{
149    assert(ptr);
150    return ptr->aspect_type;
151}
152
153/**
154 * Returns the linked part of a linking aspect.
155 * For instance, if the aspect is A.r1.r2, this returns r1.
156 */
157char *abac_aspect_linked_role_name(abac_aspect_t *ptr)
158{
159    assert(ptr);
160    return ptr->linked_role_name;
161}
162
163/* A.r1 without params or constraints, callee needs to free it*/
164char *abac_aspect_linked_role_bare(abac_aspect_t *ptr)
165{
166    assert(ptr);
167    assert(ptr->principal_name);
168    assert(ptr->linked_role_name);
169    char *tmp=NULL;
170    asprintf(&tmp,"%s.%s",ptr->principal_name,ptr->linked_role_name);
171    return tmp;
172}
173
174abac_param_list_t *abac_aspect_linked_role_params(abac_aspect_t *ptr)
175{
176    assert(ptr);
177    return ptr->linked_role_params;
178}
179
180/**
181 * Returns the name of an aspect. If the aspect is A.r1 then return r1.
182 * If the aspect is A.r1.r2 then return r2.
183 */
184char *abac_aspect_aspect_name(abac_aspect_t *ptr) {
185    assert(ptr);
186    return ptr->aspect_name;
187}
188
189abac_param_list_t *abac_aspect_aspect_params(abac_aspect_t *ptr)
190{
191    assert(ptr);
192    return ptr->aspect_params;
193}
194
195/**
196 * Returns the principal part of an aspect. The stuff before the first dot.
197 */
198char *abac_aspect_principal_name(abac_aspect_t *ptr)
199{
200    assert(ptr);
201    if(USE("ABAC_CN"))
202        return abac_cn_with_sha(ptr->principal_name);
203    if(ABAC_IN_PROLOG) {
204        return ptr->principal_name_p;
205    } else return ptr->principal_name;
206}
207
208char *abac_aspect_principal_principalname(abac_aspect_t *ptr)
209{
210    assert(ptr);
211    if(ABAC_IN_PROLOG)
212        return ptr->principal_name_p;
213    return ptr->principal_name;
214}
215
216char *abac_aspect_principal_type(abac_aspect_t *ptr)
217{
218    assert(ptr);
219    return abac_idtype_with_sha(ptr->principal_name);
220}
221
222char *abac_aspect_principal_cn(abac_aspect_t *ptr)
223{
224    assert(ptr);
225    return abac_cn_with_sha(ptr->principal_name);
226}
227
228char *abac_aspect_object_name(abac_aspect_t *ptr)
229{
230    assert(ptr);
231    assert(ptr->is_object);
232    abac_term_t *obj=ptr->principal_object;
233    char *tmp=abac_term_name(obj);
234    return abac_term_name(obj);
235}
236
237char *abac_aspect_object_type(abac_aspect_t *ptr)
238{
239    assert(ptr);
240    assert(ptr->is_object);
241    abac_term_t *obj=ptr->principal_object;
242    char *tmp=abac_term_type_name(obj);
243    return tmp;
244}
245
246abac_condition_t *abac_aspect_object_constraint(abac_aspect_t *ptr) 
247{
248    assert(ptr != NULL);
249    assert(ptr->is_object);
250    abac_term_t *obj=ptr->principal_object;
251    return abac_term_constraint(obj);
252}
253
254abac_id_t *abac_aspect_get_issuer_id(abac_aspect_t *ptr)
255{
256    abac_id_t *issuer_id;
257    char *principalname=abac_aspect_principal_principalname(ptr);
258    if(principalname) {
259        abac_id_credential_t *id_cred=abac_id_credential_lookup(principalname);
260        if(id_cred)
261            issuer_id=abac_id_credential_id(id_cred);
262    }
263    return issuer_id;
264}
265/**
266 * Increase a aspect's reference count.
267 */
268abac_aspect_t *abac_aspect_dup(abac_aspect_t *ptr)
269{
270    assert(ptr != NULL);
271    ++ptr->refcount;
272    return ptr;
273}
274
275/**
276 * Decrease an aspect's reference count, freeing it when it reaches 0.
277 */
278void abac_aspect_free(abac_aspect_t *ptr) {
279
280    if(debug)
281        printf("DEBUG:trying to freeing an aspect %d\n",(int)ptr);
282
283    assert(ptr);
284
285    --ptr->refcount;
286    if (ptr->refcount > 0)
287        return;
288
289    if(ptr->principal_name) free(ptr->principal_name);
290    if(ptr->principal_name_p) free(ptr->principal_name_p);
291    if(ptr->linked_role_name) free(ptr->linked_role_name);
292    if(ptr->aspect_name) free(ptr->aspect_name);
293
294    if(ptr->type_string) free(ptr->type_string);
295
296    if (ptr->aspect_params != NULL) {
297        abac_param_list_free(ptr->aspect_params);
298    }
299
300    if (ptr->linked_role_params != NULL) {
301        abac_param_list_free(ptr->linked_role_params);
302    }
303
304    if (ptr->prereqs != NULL) {
305        abac_aspect_t *cur;
306        abac_list_foreach(ptr->prereqs, cur,
307            abac_aspect_free(cur);
308        );
309        abac_list_free(ptr->prereqs);
310    }
311
312    free(ptr);
313}
314
315/**********************************************************************/
316
317abac_aspect_t *abac_aspect_add_param(abac_aspect_t *ptr, abac_term_t *param)
318{
319     if(ptr->aspect_params == NULL) {
320        ptr->aspect_params=abac_param_list_new(param);
321        } else {
322            abac_param_list_add_term(ptr->aspect_params, param);
323     }
324     return ptr;
325}
326
327abac_aspect_t *abac_aspect_add_intersecting_aspect(abac_aspect_t *ptr, abac_aspect_t *aspect)
328{
329     abac_list_add(ptr->prereqs, abac_aspect_dup(aspect));
330     return ptr;
331}
332
333abac_aspect_t *abac_aspect_add_linked_param(abac_aspect_t *ptr, abac_term_t *param)
334{
335     if(ptr->linked_role_params == NULL) {
336        ptr->linked_role_params=abac_param_list_new(param);
337        } else {
338            abac_param_list_add_term(ptr->linked_role_params, param);
339     }
340     return ptr;
341}
342
343
344/**********************************************************************/
345
346/**
347 * Create a new principal and initialize it.
348 * always with the proper SHA string
349 */
350static abac_aspect_t *_abac_aspect_init()
351{
352    abac_aspect_t *ptr = (abac_aspect_t *) abac_xmalloc(sizeof(abac_aspect_t));
353
354    ptr->is_object=0;
355    ptr->principal_name = NULL;
356    ptr->principal_name_p = NULL;
357    ptr->aspect_type=e_ASPECTTYPE_NULL;
358
359    ptr->aspect_name = NULL;
360    ptr->aspect_params = NULL;
361
362    ptr->linked_role_name = NULL;
363    ptr->linked_role_params = NULL;
364
365    ptr->type_string=NULL;
366
367    ptr->prereqs = NULL;
368    ptr->refcount = 1;
369}
370
371abac_aspect_t *abac_aspect_principal_new(int type, char *principal_name)
372{
373    assert(principal_name != NULL);
374
375    if (strlen(principal_name) == 0)
376        return NULL;
377
378    abac_aspect_t *ptr = _abac_aspect_init();
379    ptr->aspect_type=type;
380    ptr->principal_name = strdup(principal_name);
381    ptr->principal_name_p =  prologIt(principal_name);
382    if(type==e_ASPECTTYPE_ROLE)
383       ptr->type_string=abac_xstrdup("role");
384    if(type==e_ASPECTTYPE_OSET)
385       ptr->type_string=abac_xstrdup("oset");
386    return ptr;
387}
388
389abac_aspect_t *abac_aspect_role_principal_new(char *principal_name)
390{
391    return abac_aspect_principal_new(e_ASPECTTYPE_ROLE,principal_name);
392}
393
394abac_aspect_t *abac_aspect_role_principal_create(char *principal_name)
395{
396    return abac_aspect_principal_new(e_ASPECTTYPE_ROLE,principal_name);
397}
398
399abac_aspect_t *abac_aspect_oset_principal_new(char *principal_name)
400{
401    return abac_aspect_principal_new(e_ASPECTTYPE_OSET,principal_name);
402}
403
404abac_aspect_t *abac_aspect_oset_principal_create(char *principal_name)
405{
406    return abac_aspect_principal_new(e_ASPECTTYPE_OSET,principal_name);
407}
408
409
410abac_aspect_t *abac_aspect_object_new(int type, abac_term_t *object)
411{
412    assert(object != NULL);
413    abac_aspect_t *ptr = _abac_aspect_init();
414    ptr->aspect_type=type;
415    ptr->is_object =1;
416    ptr->principal_object = object;
417    /* can not be role type */
418    if(type==e_ASPECTTYPE_OSET)
419        ptr->type_string=abac_xstrdup("oset");
420
421    return ptr;
422}
423
424abac_aspect_t *abac_aspect_oset_object_new(abac_term_t *object)
425{
426    return abac_aspect_object_new(e_ASPECTTYPE_OSET, object);
427}
428
429abac_aspect_t *abac_aspect_new(int type, char *principal_name, char *aspect_name)
430{
431    assert(principal_name != NULL);
432    assert(aspect_name != NULL);
433
434    if (strlen(principal_name) == 0 || strlen(aspect_name) == 0)
435        return NULL;
436
437    abac_aspect_t *ptr = _abac_aspect_init();
438
439    ptr->principal_name = strdup(principal_name);
440    ptr->principal_name_p = prologIt(principal_name);
441    ptr->aspect_type=type;
442
443    ptr->aspect_name = abac_xstrdup(aspect_name);
444
445    if(type==e_ASPECTTYPE_OSET)
446        ptr->type_string= abac_xstrdup("oset");
447        else
448            ptr->type_string= abac_xstrdup("role");
449 
450    if(debug)
451        printf("DEBUG:adding a new aspect (%s) to principal (%s)\n",
452                  ptr->aspect_name, ptr->principal_name);
453    return ptr;
454}
455
456abac_aspect_t *abac_aspect_oset_new(char *principal_name, char *oset_name)
457{
458    return abac_aspect_new(e_ASPECTTYPE_OSET,principal_name, oset_name);
459}
460
461abac_aspect_t *abac_aspect_oset_create(char *principal_name, char *oset_name)
462{
463    return abac_aspect_new(e_ASPECTTYPE_OSET,principal_name,oset_name);
464}
465
466abac_aspect_t *abac_aspect_role_new(char *principal_name, char *oset_name)
467{
468    return abac_aspect_new(e_ASPECTTYPE_ROLE,principal_name, oset_name);
469}
470
471abac_aspect_t *abac_aspect_role_create(char *principal_name, char *role_name)
472{
473    return abac_aspect_new(e_ASPECTTYPE_ROLE,principal_name,role_name);
474}
475
476/**
477 * Created a new linking role/oset and initialize it.
478 */
479abac_aspect_t *abac_aspect_linking_new(int type, char *principal_name, 
480char *linked_role_name, char *aspect_name)
481{
482    assert(principal_name != NULL);
483    assert(linked_role_name != NULL);
484    assert(aspect_name != NULL);
485
486    if (strlen(principal_name) == 0 || 
487         strlen(linked_role_name) == 0 || strlen(aspect_name) == 0)
488        return NULL;
489
490    abac_aspect_t *ptr = _abac_aspect_init();
491
492    ptr->is_object=0;
493    ptr->aspect_type=type;
494    ptr->principal_name = strdup(principal_name);
495    ptr->principal_name_p = prologIt(principal_name);
496
497    ptr->linked_role_name = strdup(linked_role_name);
498    ptr->aspect_name = strdup(aspect_name);
499    ptr->aspect_params = NULL;
500
501    if(debug)
502        printf("DEBUG:adding a new linked (%s) to oset(%s) for principal(%s)\n",
503                  ptr->linked_role_name, ptr->aspect_name, ptr->principal_name);
504
505    if(type==e_ASPECTTYPE_OSET)
506        ptr->type_string=abac_xstrdup("oset");
507        else ptr->type_string=abac_xstrdup("role");
508
509    return ptr;
510}
511
512abac_aspect_t *abac_aspect_role_linking_new(char *principal_name, char *linked_role_name, 
513char *role_name)
514{
515    return abac_aspect_linking_new(e_ASPECTTYPE_ROLE,
516                      principal_name, linked_role_name, role_name);
517}
518
519abac_aspect_t *abac_aspect_oset_linking_new(char *principal_name, char *linked_role_name, 
520char *oset_name)
521{
522    return abac_aspect_linking_new(e_ASPECTTYPE_OSET,
523                      principal_name, linked_role_name, oset_name);
524}
525
526/**
527 * Create an intersection oset/role.
528 */
529abac_aspect_t *abac_aspect_intersection_new(abac_aspect_t *aspect)
530{
531    abac_aspect_t *ptr = _abac_aspect_init();
532    ptr->aspect_type=e_ASPECTTYPE_INTERSECTING;
533    abac_list_t *prereqs = abac_list_new();
534    abac_list_add(prereqs, abac_aspect_dup(aspect));
535    ptr->prereqs = prereqs;
536    ptr->refcount = 1;
537    return ptr;
538}
539
540/*******************************************************************/
541char *abac_aspect_intersecting_string_with_condition(int typed,abac_aspect_t *ptr)
542{
543    assert(ptr != NULL);
544    char *tmp=NULL;
545    if (ptr->prereqs != NULL) {
546        abac_aspect_t *cur;
547        abac_list_foreach(ptr->prereqs, cur,
548            char *ntmp=NULL;
549            if(typed)
550                ntmp=abac_aspect_typed_string_with_condition(cur);
551                else
552                    ntmp=abac_aspect_string_with_condition(cur);
553            if(tmp==NULL) {
554                asprintf(&tmp,"%s",ntmp);
555                } else {
556                    asprintf(&tmp,"%s & %s",tmp, ntmp);
557            }
558        );
559    }
560    return tmp;
561}
562
563/**
564 * Returns the string representation of the role/oset.
565 * principal.osetname(params..)
566 * principal.rolename(params..)
567 */
568char *abac_aspect_string_with_condition(abac_aspect_t *ptr)
569{
570    assert(ptr != NULL);
571
572    if(abac_aspect_is_intersection(ptr)) {
573        return abac_aspect_intersecting_string_with_condition(0,ptr);
574    }
575
576    char *tmp=NULL;
577    char *principal_name;
578    if(abac_aspect_is_object(ptr))
579        principal_name = abac_aspect_object_name(ptr);
580        else principal_name = abac_aspect_principal_name(ptr);
581    char *aspect_name= ptr->aspect_name;
582    char *linked_role_name = ptr->linked_role_name;
583    char *params_string=NULL;
584    char *linked_params_string=NULL;
585
586    int len = 0;
587    if(principal_name)
588        len=len+strlen(principal_name)+1;
589    if(aspect_name)
590        len = len+strlen(aspect_name)+1;
591    if(linked_role_name)
592        len = len+strlen(linked_role_name)+1;
593
594    if(ptr->aspect_params) {
595         params_string=abac_param_list_string_with_condition(ptr->aspect_params);
596         len = len+strlen(params_string)+3;
597    } 
598    if(ptr->linked_role_params) {
599         linked_params_string=abac_param_list_string_with_condition(ptr->linked_role_params);
600         len = len+strlen(linked_params_string)+3;
601    }
602
603    /* principal */
604    /* principal.oset/role */
605    /* principal.oset/role(params_string) */
606    /* principal.linked_role(linked_params_string).oset/role(params_string) */
607    tmp = abac_xmalloc(len);
608
609    if(principal_name)
610        sprintf(tmp,"%s",principal_name);
611
612    if(linked_role_name) {
613        strcat(tmp,".");
614        strcat(tmp,linked_role_name);
615    }
616    if(linked_params_string) {
617        strcat(tmp,"(");
618        strcat(tmp,linked_params_string);
619        strcat(tmp,")");
620    }
621    if(aspect_name) {
622        strcat(tmp,".");
623        strcat(tmp,aspect_name);
624    }
625    if(params_string) {
626        strcat(tmp,"(");
627        strcat(tmp,params_string);
628        strcat(tmp,")");
629    }
630
631    if(linked_params_string) free(linked_params_string);
632    if(params_string) free(params_string);
633    return tmp;
634}
635
636char *abac_aspect_string(abac_aspect_t *ptr) { 
637    return abac_aspect_string_with_condition(ptr);
638}
639char *abac_aspect_typed_string(abac_aspect_t *ptr) { 
640    return abac_aspect_typed_string_with_condition(ptr);
641}
642
643void abac_print_aspect_string_with_condition(abac_aspect_t *ptr,FILE *fp)
644{
645    char *string=abac_aspect_string_with_condition(ptr);
646    if(fp==NULL)
647        printf("%s ",string);
648        else fprintf(fp,"%s ",string);
649}
650
651char *abac_aspect_aspect_param_string(abac_aspect_t *ptr) {
652    assert(ptr != NULL);
653    if(ptr->aspect_params) {
654         return abac_param_list_string_with_condition(ptr->aspect_params);
655    }
656    return NULL;
657}
658
659char *abac_aspect_aspect_param_typed_string(abac_aspect_t *ptr) {
660    assert(ptr != NULL);
661    if(ptr->aspect_params) {
662         return abac_param_list_typed_string_with_condition(ptr->aspect_params);
663    }
664    return NULL;
665}
666
667/**
668 * Returns the typed string representation of the role/oset.
669 */
670char *abac_aspect_typed_string_with_condition(abac_aspect_t *ptr)
671{
672    assert(ptr != NULL);
673
674    if(abac_aspect_is_intersection(ptr)) {
675        return abac_aspect_intersecting_string_with_condition(1,ptr);
676    }
677
678    char *tmp=NULL, *final=NULL;
679    char *principal_name;
680    char *principal_name_type=NULL;
681    if(abac_aspect_is_object(ptr)) {
682        principal_name = abac_aspect_object_name(ptr);
683        principal_name_type = abac_aspect_object_type(ptr);
684        } else {
685            principal_name = abac_aspect_principal_name(ptr);
686            principal_name_type=abac_aspect_principal_type(ptr);
687            if(debug) {
688                printf("aspect's typed_string (%s)(%s)\n", principal_name_type, principal_name);
689            }
690    }
691    char *aspect_name=abac_aspect_aspect_name(ptr);
692    char *aspect_name_type=abac_aspect_type_string(ptr);
693    char *linked_role_name=abac_aspect_linked_role_name(ptr);
694    char *params_string=NULL;
695    char *linked_params_string=NULL;
696
697    if(ptr->aspect_params) {
698         params_string=abac_param_list_typed_string_with_condition(ptr->aspect_params);
699    } 
700    if(ptr->linked_role_params) {
701         linked_params_string=abac_param_list_typed_string_with_condition(ptr->linked_role_params);
702    }
703
704    asprintf(&final,"[%s:%s]",principal_name_type,principal_name);
705    if(linked_role_name) {
706        tmp=final;
707        final=NULL;
708        asprintf(&final,".role:%s",tmp,linked_role_name);
709        free(tmp);
710    }
711    if(linked_params_string) {
712        tmp=final;
713        final=NULL;
714        asprintf(&final,"%s(%s)",tmp,linked_params_string);
715        free(tmp);
716    }
717    if(aspect_name) {
718        tmp=final;
719        final=NULL;
720        asprintf(&final,"%s.%s:%s",tmp,aspect_name_type,aspect_name);
721        free(tmp);
722    }
723    if(params_string) {
724        tmp=final;
725        final=NULL;
726        asprintf(&final,"%s(%s)",tmp,params_string);
727        free(tmp);
728    }
729
730    if(linked_params_string) free(linked_params_string);
731    if(params_string) free(params_string);
732    return final;
733}
734
735/**
736 * Build an attribute key from head and tail osets/role. Static.
737 */
738#define ROLE_SEPARATOR " <- "
739char *abac_aspect_attr_key(abac_aspect_t *head_ptr, abac_aspect_t *tail_ptr) {
740    char *head = abac_aspect_string_with_condition(head_ptr);
741    int head_len = strlen(head);
742
743    char *tail = abac_aspect_string_with_condition(tail_ptr);
744    int tail_len = strlen(tail);
745
746    int sep_len = sizeof(ROLE_SEPARATOR) - 1;
747
748    // "head <- tail"
749    char *ret = abac_xmalloc(head_len + tail_len + sep_len + 1);
750    memcpy(ret, head, head_len);
751    memcpy(ret + head_len, ROLE_SEPARATOR, sep_len);
752    memcpy(ret + head_len + sep_len, tail, tail_len);
753    ret[head_len + sep_len + tail_len] = 0;
754
755    return ret;
756}
Note: See TracBrowser for help on using the repository browser.