source: libabac/abac_graph.c @ 8f53997

abac0-leakabac0-meimei-idmei-rt0-ntvf-new-xml
Last change on this file since 8f53997 was d4b3b52, checked in by Ted Faber <faber@…>, 12 years ago

Better partial proofs

  • Property mode set to 100644
File size: 17.9 KB
RevLine 
[d4cbf71]1#include <assert.h>
[379a53f]2#include <stdlib.h>
[d4cbf71]3
[06293d1]4#include "abac_graph.h"
[cb0f2b2]5
[2fd24c7]6#include "abac_set.h"
[3c251d0]7#include "abac_util.h"
[d4cbf71]8
[f46412c]9#include "uthash.h"
10
11// vertex
[06293d1]12struct _abac_vertex_t {
[1743825]13    abac_role_t *role;
[f46412c]14    char *name;
15
[6d5623e]16    abac_list_t *edges;
[d4b3b52]17    abac_list_t *reverse_edges;
[f46412c]18
[0eb4a8e]19    // only relevant to intersection edges
20    abac_list_t *prereqs;
21
[f46412c]22    UT_hash_handle hh;
23};
24
[ae72c5d]25// edge
[06293d1]26typedef struct _abac_edge_t {
27    abac_vertex_t *vertex;
[d4b3b52]28    abac_vertex_t *reverse_vertex;
[401a054]29    abac_credential_t *credential;
[06293d1]30} abac_edge_t;
[ae72c5d]31
[f46412c]32// derived edge
[06293d1]33typedef struct _abac_derived_key_t {
34    abac_vertex_t *head;
35    abac_edge_t *tail;
36} abac_derived_key_t;
[f46412c]37
[06293d1]38typedef struct _abac_derived_t {
39    abac_derived_key_t key;
[f46412c]40    UT_hash_handle hh;
[06293d1]41} abac_derived_t;
[f46412c]42
43// graph
[06293d1]44struct _abac_graph_t {
45    abac_vertex_t *vertices;
46    abac_derived_t *derived;
[f46412c]47    int dirty;
48};
[0ec9e25]49
[0eb4a8e]50// ugghhhghhhghh need this for intersections
[9a411d7]51abac_list_t *abac_role_prereqs(abac_role_t *);
[0eb4a8e]52
[0ec9e25]53/**
54 * Create a new graph.
55 */
[06293d1]56abac_graph_t *abac_graph_new(void) {
[3c251d0]57    abac_graph_t *graph = abac_xmalloc(sizeof(abac_graph_t));
[82aeefa]58
59    graph->vertices = NULL;
[6188f4e]60    graph->derived = NULL;
[82aeefa]61    graph->dirty = 0;
62
63    return graph;
64}
[d4cbf71]65
[b26942d]66/**
67 * Deep copy a graph.
68 */
[06293d1]69abac_graph_t *abac_graph_dup(abac_graph_t *graph) {
70    abac_vertex_t *vertex;
71    abac_edge_t *edge;
[b26942d]72
[06293d1]73    abac_graph_t *clone = abac_graph_new();
[b26942d]74
75    // copy the vertices edge by edge
76    for (vertex = graph->vertices; vertex != NULL; vertex = vertex->hh.next)
[6d5623e]77        abac_list_foreach(vertex->edges, edge,
[b26942d]78            // only copy non-derived edges
[401a054]79            if (edge->credential != NULL)
80                abac_graph_add_credential(clone, edge->credential);
[b26942d]81        );
82
83    return clone;
84}
85
[d4cbf71]86/**
[401a054]87 * Add a vertex to the graph. Should only be called by abac_graph_add_credential.
[d4cbf71]88 */
[06293d1]89static abac_vertex_t *_get_vertex(abac_graph_t *graph, abac_role_t *role) {
90    abac_vertex_t *vertex;
[c28bd8b]91    char *string;
92   
[dcc1a8e]93    string = abac_role_string(role);
[82aeefa]94    HASH_FIND_STR(graph->vertices, string, vertex);
[c28bd8b]95
96    // add the vertex if it doesn't exist
97    if (vertex == NULL) {
[3c251d0]98        vertex = abac_xmalloc(sizeof(abac_vertex_t));
[dcc1a8e]99        vertex->role = abac_role_dup(role);
100        vertex->name = abac_role_string(vertex->role);
[d4cbf71]101
[379a53f]102        // create the list of edges
[6d5623e]103        vertex->edges = abac_list_new();
[d4b3b52]104        vertex->reverse_edges = abac_list_new();
[d4cbf71]105
[0eb4a8e]106        // for intersections, always NULL on normal vertices
[9a411d7]107        if (abac_role_is_intersection(role)) {
108            abac_role_t *prereq;
109            vertex->prereqs = abac_list_new();
110
111            // add each prereq to the vertex
112            abac_list_foreach(abac_role_prereqs(role), prereq,
113                abac_vertex_t *tail_vertex = _get_vertex(graph, prereq);
114                abac_list_add(vertex->prereqs, tail_vertex);
115            );
116        }
[0eb4a8e]117
[9a411d7]118        // normal edges have no prereqs
119        else
120            vertex->prereqs = NULL;
[0eb4a8e]121
[c28bd8b]122        // add it to the vertices
[82aeefa]123        HASH_ADD_KEYPTR(hh, graph->vertices, vertex->name, strlen(vertex->name), vertex);
[c28bd8b]124    }
[d4cbf71]125
126    return vertex;
127}
128
129/**
[401a054]130 * Add a credential to the credential graph.
[d4cbf71]131 */
[401a054]132int abac_graph_add_credential(abac_graph_t *graph, abac_credential_t *cred) {
[06293d1]133    abac_vertex_t *head_vertex, *tail_vertex;
[314869f]134    abac_edge_t *edge;
[d4cbf71]135
[401a054]136    assert(cred != NULL);
[ae72c5d]137
[401a054]138    abac_role_t *head = abac_credential_head(cred);
139    abac_role_t *tail = abac_credential_tail(cred);
[d4cbf71]140
[401a054]141    // a valid credential must have a role for the head
[dcc1a8e]142    if (!abac_role_is_role(head)) return 0;
[d4cbf71]143
[82aeefa]144    head_vertex = _get_vertex(graph, head);
[9a411d7]145    tail_vertex = _get_vertex(graph, tail);
[d4cbf71]146
[314869f]147    // make sure we don't insert the same edge twice (ugh)
148    abac_list_foreach(head_vertex->edges, edge,
149        if (edge->vertex == tail_vertex)
150            return 0;
151    );
152
[ae72c5d]153    // create the edge and add it
[314869f]154    edge = abac_xmalloc(sizeof(abac_edge_t));
[ae72c5d]155    edge->vertex = tail_vertex;
[d4b3b52]156    edge->reverse_vertex = head_vertex;
[401a054]157    edge->credential = abac_credential_dup(cred);
[ae72c5d]158
[6d5623e]159    abac_list_add(head_vertex->edges, edge);
[d4b3b52]160    abac_list_add(tail_vertex->reverse_edges, edge);
[d4cbf71]161
[82aeefa]162    // must re-derive edges
163    graph->dirty = 1;
164
[d4cbf71]165    return 1;
166}
167
[ebde9dd]168// find the principals that have a role
[06293d1]169static abac_set_t *_find_principals(abac_graph_t *graph, abac_vertex_t *start_vertex) {
[2fd24c7]170    abac_set_t *principals = abac_set_new();
[d4cbf71]171
[06293d1]172    abac_list_t *traversal = abac_graph_postorder(graph, start_vertex->role);
173    abac_vertex_t *vertex;
[d4cbf71]174
[6d5623e]175    abac_list_foreach(traversal, vertex,
[9a411d7]176        if (abac_role_is_principal(vertex->role))
177            abac_set_add(principals, abac_role_string(vertex->role));
[ebde9dd]178    );
[d4cbf71]179
[6d5623e]180    abac_list_free(traversal);
[ebde9dd]181    return principals;
182}
[d4cbf71]183
[6188f4e]184// remove any derived edges from the graph
[06293d1]185void _clear_derived(abac_graph_t *graph) {
186    abac_derived_t *current;
[6188f4e]187
188    while (graph->derived) {
189        current = graph->derived;
190
191        HASH_DEL(graph->derived, current);
192
[06293d1]193        abac_vertex_t *head = current->key.head;
194        abac_edge_t *tail = current->key.tail;
[401a054]195        assert(tail->credential == NULL);
[6188f4e]196
197        // this can fail, but we assume the data structures are consistent
[6d5623e]198        abac_list_remove(head->edges, tail);
[d4b3b52]199        abac_list_remove(tail->reverse_vertex->edges, tail);
[6188f4e]200
201        free(current);
[186cb75]202        free(tail);
[6188f4e]203    }
204}
205
[704fde7]206// add a derived edge, returns 1 if added 0 if dup
207static int _derived_edge(abac_graph_t *graph, abac_vertex_t *head, abac_vertex_t *tail) {
208    abac_edge_t *edge;
209
210    // don't add duplicate edges
211    abac_list_foreach(head->edges, edge,
212        if (edge->vertex == tail)
213            return 0;
214    );
215
216    debug_printf("derived edge %s <- %s\n", head->name, tail->name);
217
218    edge = abac_xmalloc(sizeof(abac_edge_t));
[e2a0f26]219    edge->vertex = tail;
[d4b3b52]220    edge->reverse_vertex = head;
[e2a0f26]221    edge->credential = NULL;
222    abac_list_add(head->edges, edge);
[d4b3b52]223    abac_list_add(tail->reverse_edges, edge);
[e2a0f26]224
225    // add to list of derived edges
226    abac_derived_t *derived = abac_xmalloc(sizeof(abac_derived_t));
227    derived->key.head = head;
228    derived->key.tail = edge;
229    HASH_ADD(hh, graph->derived, key, sizeof(abac_derived_key_t), derived);
[704fde7]230
231    return 1;
[e2a0f26]232}
233
234// find a vertex by name
235abac_vertex_t *_find_vertex(abac_graph_t *graph, char *name) {
236    abac_vertex_t *ret = NULL;
237    HASH_FIND_STR(graph->vertices, name, ret);
238    return ret;
239}
240
[704fde7]241/**
242 * Single iteration of deriving new edges. Returns the number of new edges
243 * added.
244 */
245static int _derive_links_iter(abac_graph_t *graph) {
246    int count = 0;
[06293d1]247    abac_vertex_t *vertex;
[d4cbf71]248
[ebde9dd]249    for (vertex = graph->vertices; vertex != NULL; vertex = vertex->hh.next) {
[0eb4a8e]250        // intersection
[9a411d7]251        if (abac_role_is_intersection(vertex->role)) {
[0eb4a8e]252            // for each prereq edge:
253            //     find principals that have the edge
254            // find intersection of all sets
255            // for each principal B in intersection:
256            //     add link
[e2a0f26]257
258            char *name;
259            abac_vertex_t *prereq;
260            abac_set_t *principals = NULL;
261
262            abac_list_foreach(vertex->prereqs, prereq,
263                abac_set_t *cur = _find_principals(graph, prereq);
264
265                if (principals == NULL)
266                    principals = cur;
267                else {
268                    abac_set_intersect(principals, cur);
269                    abac_set_free(cur);
270                }
271
272                if (abac_set_size(principals) == 0)
273                    goto isect_done;
274            );
275
276            abac_list_t *prin_names = abac_set_elements(principals);
277            abac_list_foreach(prin_names, name,
278                abac_vertex_t *principal = _find_vertex(graph, name);
[704fde7]279                count += _derived_edge(graph, vertex, principal);
[e2a0f26]280            );
281
282            abac_list_free(prin_names);
283isect_done:
284            abac_set_free(principals);
[0eb4a8e]285        }
286
287        // linking role
288        else if (abac_role_is_linking(vertex->role)) {
289            // linking roles take the form A.r1.r2
290            char *A_r1 = abac_role_linked_role(vertex->role);
291            char *r2 = abac_role_role_name(vertex->role);
292
293            // find the linked role in the graph
294            abac_vertex_t *A_r1_vertex;
295            HASH_FIND_STR(graph->vertices, A_r1, A_r1_vertex);
296            if (A_r1_vertex == NULL)
297                continue;
298
299            // find the principals that have A.r1
300            abac_set_t *principals = _find_principals(graph, A_r1_vertex);
301            char *B;
302
303            abac_list_t *elts = abac_set_elements(principals);
304
305            // and add a link for each B.r2 to A.r1.r2
306            abac_list_foreach(elts, B,
307                int B_len = strlen(B);
308                int r2_len = strlen(r2);
309
310                // create the string B.r2, thx C
311                char *B_r2 = malloc(B_len + r2_len + 2);
312                memcpy(B_r2, B, B_len);
313                B_r2[B_len] = '.';
314                memcpy(B_r2 + B_len + 1, r2, r2_len);
315                B_r2[B_len + r2_len + 1] = 0;
316
317                // add an edge if the principal's granted it to someone
[e2a0f26]318                abac_vertex_t *B_r2_vertex = _find_vertex(graph, B_r2);
[0eb4a8e]319                if (B_r2_vertex) {
320                    debug_printf("adding edge from %s to %s\n", B_r2, abac_role_string(vertex->role));
[704fde7]321                    count += _derived_edge(graph, vertex, B_r2_vertex);
[0eb4a8e]322                }
[ebde9dd]323
[dbbf777]324#ifdef DEBUG
[0eb4a8e]325                debug_printf("    incoming edges for %s\n", abac_role_string(vertex->role));
326                abac_edge_t *cur;
327                abac_list_foreach(vertex->edges, cur,
328                    debug_printf("        %s (%s)\n", abac_role_string(cur->vertex->role), cur->vertex->name);
329                );
[dbbf777]330#endif
[d4cbf71]331
[0eb4a8e]332                free(B_r2);
333            );
[ebde9dd]334
[0eb4a8e]335            abac_list_free(elts);
336            abac_set_free(principals);
337        }
[ebde9dd]338    }
[6188f4e]339
[704fde7]340    return count;
341}
342
343/**
344 * Derive all implied edges in the graph. These can come from linking roles
345 * and intersections.
346 *
347 * We have to do it iteratively because derived edges can imply new edges.
348 */
349void abac_graph_derive_links(abac_graph_t *graph) {
350    if (!graph->dirty)
351        return;
352
353    // iterate as long as new links are derived
354    while (_derive_links_iter(graph) > 0)
355        ;
356
[6188f4e]357    graph->dirty = 0;
[d4cbf71]358}
[cb0f2b2]359
[d4b3b52]360static void _reverse_order_recurse(abac_vertex_t *vertex, abac_set_t *seen, int preorder, abac_list_t *stack) {
361    abac_edge_t *outgoing;
362
363    // don't revisit nodes
364    if (!abac_set_add(seen, abac_role_string(vertex->role)))
365        return;
366
367    if (preorder)
368        abac_list_add(stack, vertex);
369
370    // recurse along the incoming vertices
371    abac_list_foreach(vertex->reverse_edges, outgoing,
372        _reverse_order_recurse(outgoing->reverse_vertex, seen, preorder, stack);
373    );
374
375    if (!preorder)
376        abac_list_add(stack, vertex);
377}
378
379static abac_list_t *_reverse_order(abac_graph_t *graph, abac_role_t *start, int preorder) {
380    debug_printf("%sorder at %s\n", preorder ? "pre" : "post", abac_role_string(start));
381
382    abac_vertex_t *start_vertex = _get_vertex(graph, start);
383    abac_set_t *seen = abac_set_new();
384
385    // create the return list
386    abac_list_t *stack = abac_list_new();
387
388    _reverse_order_recurse(start_vertex, seen, preorder, stack);
389
390    abac_set_free(seen);
391
392    return stack;
393}
394
[06293d1]395static void _order_recurse(abac_vertex_t *vertex, abac_set_t *seen, int preorder, abac_list_t *stack) {
396    abac_edge_t *incoming;
[cb0f2b2]397
398    // don't revisit nodes
[9a411d7]399    if (!abac_set_add(seen, abac_role_string(vertex->role)))
[cb0f2b2]400        return;
401
402    if (preorder)
[6d5623e]403        abac_list_add(stack, vertex);
[cb0f2b2]404
[9536712]405    // recurse along the incoming vertices
[6d5623e]406    abac_list_foreach(vertex->edges, incoming,
[ae72c5d]407        _order_recurse(incoming->vertex, seen, preorder, stack);
[379a53f]408    );
[cb0f2b2]409
410    if (!preorder)
[6d5623e]411        abac_list_add(stack, vertex);
[cb0f2b2]412}
413
[06293d1]414static abac_list_t *_order(abac_graph_t *graph, abac_role_t *start, int preorder) {
[dcc1a8e]415    debug_printf("%sorder at %s\n", preorder ? "pre" : "post", abac_role_string(start));
[ebde9dd]416
[06293d1]417    abac_vertex_t *start_vertex = _get_vertex(graph, start);
[2fd24c7]418    abac_set_t *seen = abac_set_new();
[cb0f2b2]419
[379a53f]420    // create the return list
[6d5623e]421    abac_list_t *stack = abac_list_new();
[cb0f2b2]422
423    _order_recurse(start_vertex, seen, preorder, stack);
424
[2fd24c7]425    abac_set_free(seen);
[be963dc]426
[cb0f2b2]427    return stack;
428}
429
[06293d1]430abac_list_t *abac_graph_postorder(abac_graph_t *graph, abac_role_t *start) {
[82aeefa]431    return _order(graph, start, 0);
[cb0f2b2]432}
[97a5e13]433
[4e426c9]434/**
435 * Postorder traverse the graph and return all the credentials within.
436 */
437abac_list_t *abac_graph_postorder_credentials(abac_graph_t *graph, char *start) {
438    abac_vertex_t *vertex;
439    abac_edge_t *incoming;
440
441    // get the postorder of vertices
442    abac_role_t *role = abac_role_from_string(start);
443    abac_list_t *order = abac_graph_postorder(graph, role);
444
445    // go through the list and dup all the credentials
446    abac_list_t *credentials = abac_list_new();
447    abac_list_foreach(order, vertex,
448        abac_list_foreach(vertex->edges, incoming,
449            if (incoming->credential != NULL)
450                abac_list_add(credentials, abac_credential_dup(incoming->credential));
451        );
452    );
453
454    abac_role_free(role);
455    abac_list_free(order);
456
457    return credentials;
458}
459
[d4b3b52]460
461abac_list_t *abac_graph_postorder_reverse(abac_graph_t *graph, abac_role_t *start) {
462    return _reverse_order(graph, start, 0);
463}
464
465/**
466 * Postorder traverse the graph and return all the credentials within.
467 */
468abac_list_t *abac_graph_postorder_reverse_credentials(abac_graph_t *graph, char *start) {
469    abac_vertex_t *vertex;
470    abac_edge_t *outgoing;
471
472    // get the postorder of vertices
473    abac_role_t *role = abac_role_from_string(start);
474    abac_list_t *order = abac_graph_postorder_reverse(graph, role);
475
476    // go through the list and dup all the credentials
477    abac_list_t *credentials = abac_list_new();
478    abac_list_foreach(order, vertex,
479        abac_list_foreach(vertex->reverse_edges, outgoing,
480            if (outgoing->credential != NULL)
481                abac_list_add(credentials, abac_credential_dup(outgoing->credential));
482        );
483    );
484
485    abac_role_free(role);
486    abac_list_free(order);
487
488    return credentials;
489}
490
[401a054]491static void _query(abac_graph_t *graph, char *role_name, char *principal, abac_graph_t *return_graph) {
[06293d1]492    abac_vertex_t *vertex;
493    abac_edge_t *incoming;
[97a5e13]494
[401a054]495    abac_role_t *role = abac_role_from_string(role_name);
[dcc1a8e]496    abac_role_t *prin_role = abac_role_from_string(principal);
[97a5e13]497
[675cbea]498    // give up on bogus roles
499    if (role == NULL || prin_role == NULL) {
500        free(role);
501        free(prin_role);
502        return;
503    }
504
[2fd24c7]505    abac_set_t *on_path = abac_set_new();
[dcc1a8e]506    abac_set_add(on_path, abac_role_string(prin_role));
[97a5e13]507
[401a054]508    abac_list_t *traversal = abac_graph_postorder(graph, role);
[6d5623e]509    abac_list_foreach(traversal, vertex,
[1743825]510        abac_role_t *role = vertex->role;
[97a5e13]511
[6d5623e]512        abac_list_foreach(vertex->edges, incoming,
[1743825]513            abac_role_t *incoming_role = incoming->vertex->role;
[97a5e13]514
[9a411d7]515            if (!abac_set_contains(on_path, abac_role_string(incoming_role)))
[ae72c5d]516                continue;
[97a5e13]517
[9a411d7]518            abac_set_add(on_path, abac_role_string(role));
[97a5e13]519
[ebe824c]520            // get implying edges for intersection vertices
[9a411d7]521            if (abac_role_is_intersection(role)) {
[ebe824c]522                abac_vertex_t *prereq;
523                abac_list_foreach(vertex->prereqs, prereq,
524                    _query(graph, prereq->name, principal, return_graph);
525                );
[e2a0f26]526            }
[97a5e13]527
528            // recursively find linked roles
[e2a0f26]529            else if (abac_role_is_linking(role)) {
[401a054]530                char *linked_role = abac_role_linked_role(role);
[dcc1a8e]531                char *principal = abac_role_principal(incoming_role);
[97a5e13]532
[401a054]533                _query(graph, linked_role, principal, return_graph);
[97a5e13]534            }
[e2a0f26]535
536            // add non-derived edges to the proof graph
537            else
538                abac_graph_add_credential(return_graph, incoming->credential);
[97a5e13]539        );
540    );
541
[6d5623e]542    abac_list_free(traversal);
[2fd24c7]543    abac_set_free(on_path);
[401a054]544    abac_role_free(role);
[dcc1a8e]545    abac_role_free(prin_role);
[97a5e13]546}
547
[401a054]548abac_graph_t *abac_graph_query(abac_graph_t *graph, char *role, char *principal) {
[06293d1]549    abac_graph_derive_links(graph);
[97a5e13]550
[06293d1]551    abac_graph_t *return_graph = abac_graph_new();
[401a054]552    _query(graph, role, principal, return_graph);
[06293d1]553    abac_graph_derive_links(return_graph);
[97a5e13]554    return return_graph;
555}
[be963dc]556
[d4b3b52]557abac_graph_t *abac_graph_principal_creds(abac_graph_t *graph, char *principal) {
558    abac_graph_derive_links(graph);
559    abac_graph_t *result_graph = abac_graph_new();
560    abac_list_t *result = abac_graph_postorder_reverse_credentials(graph, 
561            principal);
562    abac_credential_t *cur = NULL;
563    abac_list_foreach(result, cur,
564        abac_graph_add_credential(result_graph, cur);
565    );
566    abac_list_free(result);
567    /* For each terminal role that the principal can reach, roll a proof into
568       the result_graph. */
569    abac_vertex_t *vertex = NULL;
570    for (vertex = result_graph->vertices; vertex != NULL; 
571            vertex = vertex->hh.next) {
572        if ( abac_list_size(vertex->reverse_edges) == 0) 
573            _query(graph, vertex->name, principal, result_graph);
574    }
575    abac_graph_derive_links(result_graph);
576    return result_graph;
577}
578
579
[902d079]580/**
[401a054]581 * Get all the credentials (attribute/issuer cert pairs) from the graph.
[902d079]582 */
[401a054]583abac_list_t *abac_graph_credentials(abac_graph_t *graph) {
584    abac_list_t *credentials = abac_list_new();
[902d079]585
[06293d1]586    abac_vertex_t *vertex;
[902d079]587
588    for (vertex = graph->vertices; vertex != NULL; vertex = vertex->hh.next) {
[06293d1]589        abac_edge_t *edge;
[6d5623e]590        abac_list_foreach(vertex->edges, edge,
[401a054]591            if (edge->credential != NULL)
592                abac_list_add(credentials, abac_credential_dup(edge->credential));
[902d079]593        );
594    }
595
[401a054]596    return credentials;
[902d079]597}
598
[06293d1]599void abac_graph_free(abac_graph_t *graph) {
600    abac_vertex_t *vertex;
601    abac_edge_t *edge;
[be963dc]602
603    // kill derived edges
604    _clear_derived(graph);
605
606    // delete vertices
607    while ((vertex = graph->vertices) != NULL) {
608        HASH_DEL(graph->vertices, vertex);
609
[dcc1a8e]610        abac_role_free(vertex->role);
[186cb75]611
[6d5623e]612        abac_list_foreach(vertex->edges, edge,
[401a054]613            if (edge->credential != NULL)
614                abac_credential_free(edge->credential);
[186cb75]615            free(edge);
616        );
[6d5623e]617        abac_list_free(vertex->edges);
[aba6e07]618
619        // the prereq vertices will be freed by the outer while loop
620        if (vertex->prereqs != NULL)
621            abac_list_free(vertex->prereqs);
622
[be963dc]623        free(vertex);
624    }
625
626    free(graph);
627}
[f46412c]628
[06293d1]629abac_role_t *abac_vertex_role(abac_vertex_t *vertex) {
[f46412c]630    return vertex->role;
631}
Note: See TracBrowser for help on using the repository browser.