source: libabac/abac_aspect.c @ 8bd77b5

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

1) convert parser and libabac to use id cred and attr cred like

creddy (move those 2 files to libabac).

2) fix up abac.hh to work with expanded libabac. can now build

structure from python script

3) redid the credential dump using the internal credential table

instead of depending on a search in db.

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