1# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
2#
3# Copyright (C) 2006-2007 Red Hat
4# see file 'COPYING' for use and warranty information
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2 only
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20# OVERVIEW
21#
22#
23# This is a parser for the refpolicy policy "language" - i.e., the
24# normal SELinux policy language plus the refpolicy style M4 macro
25# constructs on top of that base language. This parser is primarily
26# aimed at parsing the policy headers in order to create an abstract
27# policy representation suitable for generating policy.
28#
29# Both the lexer and parser are included in this file. The are implemented
30# using the Ply library (included with sepolgen).
31
32import sys
33import os
34import re
35import traceback
36
37import refpolicy
38import access
39import defaults
40
41import lex
42import yacc
43
44# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
45#
46# lexer
47#
48# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
49
50tokens = (
51    # basic tokens, punctuation
52    'TICK',
53    'SQUOTE',
54    'OBRACE',
55    'CBRACE',
56    'SEMI',
57    'COLON',
58    'OPAREN',
59    'CPAREN',
60    'COMMA',
61    'MINUS',
62    'TILDE',
63    'ASTERISK',
64    'AMP',
65    'BAR',
66    'EXPL',
67    'EQUAL',
68    'FILENAME',
69    'IDENTIFIER',
70    'NUMBER',
71    'PATH',
72    'IPV6_ADDR',
73    # reserved words
74    #   module
75    'MODULE',
76    'POLICY_MODULE',
77    'REQUIRE',
78    #   flask
79    'SID',
80    'GENFSCON',
81    'FS_USE_XATTR',
82    'FS_USE_TRANS',
83    'FS_USE_TASK',
84    'PORTCON',
85    'NODECON',
86    'NETIFCON',
87    'PIRQCON',
88    'IOMEMCON',
89    'IOPORTCON',
90    'PCIDEVICECON',
91    'DEVICETREECON',
92    #   object classes
93    'CLASS',
94    #   types and attributes
95    'TYPEATTRIBUTE',
96    'ROLEATTRIBUTE',
97    'TYPE',
98    'ATTRIBUTE',
99    'ATTRIBUTE_ROLE',
100    'ALIAS',
101    'TYPEALIAS',
102    #   conditional policy
103    'BOOL',
104    'TRUE',
105    'FALSE',
106    'IF',
107    'ELSE',
108    #   users and roles
109    'ROLE',
110    'TYPES',
111    #   rules
112    'ALLOW',
113    'DONTAUDIT',
114    'AUDITALLOW',
115    'NEVERALLOW',
116    'PERMISSIVE',
117    'TYPE_TRANSITION',
118    'TYPE_CHANGE',
119    'TYPE_MEMBER',
120    'RANGE_TRANSITION',
121    'ROLE_TRANSITION',
122    #   refpolicy keywords
123    'OPT_POLICY',
124    'INTERFACE',
125    'TUNABLE_POLICY',
126    'GEN_REQ',
127    'TEMPLATE',
128    'GEN_CONTEXT',
129    #   m4
130    'IFELSE',
131    'IFDEF',
132    'IFNDEF',
133    'DEFINE'
134    )
135
136# All reserved keywords - see t_IDENTIFIER for how these are matched in
137# the lexer.
138reserved = {
139    # module
140    'module' : 'MODULE',
141    'policy_module' : 'POLICY_MODULE',
142    'require' : 'REQUIRE',
143    # flask
144    'sid' : 'SID',
145    'genfscon' : 'GENFSCON',
146    'fs_use_xattr' : 'FS_USE_XATTR',
147    'fs_use_trans' : 'FS_USE_TRANS',
148    'fs_use_task' : 'FS_USE_TASK',
149    'portcon' : 'PORTCON',
150    'nodecon' : 'NODECON',
151    'netifcon' : 'NETIFCON',
152    'pirqcon' : 'PIRQCON',
153    'iomemcon' : 'IOMEMCON',
154    'ioportcon' : 'IOPORTCON',
155    'pcidevicecon' : 'PCIDEVICECON',
156    'devicetreecon' : 'DEVICETREECON',
157    # object classes
158    'class' : 'CLASS',
159    # types and attributes
160    'typeattribute' : 'TYPEATTRIBUTE',
161    'roleattribute' : 'ROLEATTRIBUTE',
162    'type' : 'TYPE',
163    'attribute' : 'ATTRIBUTE',
164    'attribute_role' : 'ATTRIBUTE_ROLE',
165    'alias' : 'ALIAS',
166    'typealias' : 'TYPEALIAS',
167    # conditional policy
168    'bool' : 'BOOL',
169    'true' : 'TRUE',
170    'false' : 'FALSE',
171    'if' : 'IF',
172    'else' : 'ELSE',
173    # users and roles
174    'role' : 'ROLE',
175    'types' : 'TYPES',
176    # rules
177    'allow' : 'ALLOW',
178    'dontaudit' : 'DONTAUDIT',
179    'auditallow' : 'AUDITALLOW',
180    'neverallow' : 'NEVERALLOW',
181    'permissive' : 'PERMISSIVE',
182    'type_transition' : 'TYPE_TRANSITION',
183    'type_change' : 'TYPE_CHANGE',
184    'type_member' : 'TYPE_MEMBER',
185    'range_transition' : 'RANGE_TRANSITION',
186    'role_transition' : 'ROLE_TRANSITION',
187    # refpolicy keywords
188    'optional_policy' : 'OPT_POLICY',
189    'interface' : 'INTERFACE',
190    'tunable_policy' : 'TUNABLE_POLICY',
191    'gen_require' : 'GEN_REQ',
192    'template' : 'TEMPLATE',
193    'gen_context' : 'GEN_CONTEXT',
194    # M4
195    'ifelse' : 'IFELSE',
196    'ifndef' : 'IFNDEF',
197    'ifdef' : 'IFDEF',
198    'define' : 'DEFINE'
199    }
200
201# The ply lexer allows definition of tokens in 2 ways: regular expressions
202# or functions.
203
204# Simple regex tokens
205t_TICK      = r'\`'
206t_SQUOTE    = r'\''
207t_OBRACE    = r'\{'
208t_CBRACE    = r'\}'
209# This will handle spurios extra ';' via the +
210t_SEMI      = r'\;+'
211t_COLON     = r'\:'
212t_OPAREN    = r'\('
213t_CPAREN    = r'\)'
214t_COMMA     = r'\,'
215t_MINUS     = r'\-'
216t_TILDE     = r'\~'
217t_ASTERISK  = r'\*'
218t_AMP       = r'\&'
219t_BAR       = r'\|'
220t_EXPL      = r'\!'
221t_EQUAL     = r'\='
222t_NUMBER    = r'[0-9\.]+'
223t_PATH      = r'/[a-zA-Z0-9)_\.\*/]*'
224#t_IPV6_ADDR = r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]{0,4}:)*'
225
226# Ignore whitespace - this is a special token for ply that more efficiently
227# ignores uninteresting tokens.
228t_ignore    = " \t"
229
230# More complex tokens
231def t_IPV6_ADDR(t):
232    r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]|:)*'
233    # This is a function simply to force it sooner into
234    # the regex list
235    return t
236
237def t_m4comment(t):
238    r'dnl.*\n'
239    # Ignore all comments
240    t.lexer.lineno += 1
241
242def t_refpolicywarn1(t):
243    r'define.*refpolicywarn\(.*\n'
244    # Ignore refpolicywarn statements - they sometimes
245    # contain text that we can't parse.
246    t.skip(1)
247
248def t_refpolicywarn(t):
249    r'refpolicywarn\(.*\n'
250    # Ignore refpolicywarn statements - they sometimes
251    # contain text that we can't parse.
252    t.lexer.lineno += 1
253
254def t_IDENTIFIER(t):
255    r'[a-zA-Z_\$][a-zA-Z0-9_\-\+\.\$\*~]*'
256    # Handle any keywords
257    t.type = reserved.get(t.value,'IDENTIFIER')
258    return t
259
260def t_FILENAME(t):
261    r'\"[a-zA-Z0-9_\-\+\.\$\*~ :]+\"'
262    # Handle any keywords
263    t.type = reserved.get(t.value,'FILENAME')
264    return t
265
266def t_comment(t):
267    r'\#.*\n'
268    # Ignore all comments
269    t.lexer.lineno += 1
270
271def t_error(t):
272    print "Illegal character '%s'" % t.value[0]
273    t.skip(1)
274
275def t_newline(t):
276    r'\n+'
277    t.lexer.lineno += len(t.value)
278
279# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
280#
281# Parser
282#
283# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
284
285# Global data used during parsing - making it global is easier than
286# passing the state through the parsing functions.
287
288#   m is the top-level data structure (stands for modules).
289m = None
290#   error is either None (indicating no error) or a string error message.
291error = None
292parse_file = ""
293#   spt is the support macros (e.g., obj/perm sets) - it is an instance of
294#     refpolicy.SupportMacros and should always be present during parsing
295#     though it may not contain any macros.
296spt = None
297success = True
298
299# utilities
300def collect(stmts, parent, val=None):
301    if stmts is None:
302        return
303    for s in stmts:
304        if s is None:
305            continue
306        s.parent = parent
307        if val is not None:
308            parent.children.insert(0, (val, s))
309        else:
310            parent.children.insert(0, s)
311
312def expand(ids, s):
313    for id in ids:
314        if spt.has_key(id):
315            s.update(spt.by_name(id))
316        else:
317            s.add(id)
318
319# Top-level non-terminal
320def p_statements(p):
321    '''statements : statement
322                  | statements statement
323                  | empty
324    '''
325    if len(p) == 2 and p[1]:
326        m.children.append(p[1])
327    elif len(p) > 2 and p[2]:
328        m.children.append(p[2])
329
330def p_statement(p):
331    '''statement : interface
332                 | template
333                 | obj_perm_set
334                 | policy
335                 | policy_module_stmt
336                 | module_stmt
337    '''
338    p[0] = p[1]
339
340def p_empty(p):
341    'empty :'
342    pass
343
344#
345# Reference policy language constructs
346#
347
348# This is for the policy module statement (e.g., policy_module(foo,1.2.0)).
349# We have a separate terminal for either the basic language module statement
350# and interface calls to make it easier to identifier.
351def p_policy_module_stmt(p):
352    'policy_module_stmt : POLICY_MODULE OPAREN IDENTIFIER COMMA NUMBER CPAREN'
353    m = refpolicy.ModuleDeclaration()
354    m.name = p[3]
355    m.version = p[5]
356    m.refpolicy = True
357    p[0] = m
358
359def p_interface(p):
360    '''interface : INTERFACE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
361    '''
362    x = refpolicy.Interface(p[4])
363    collect(p[8], x)
364    p[0] = x
365
366def p_template(p):
367    '''template : TEMPLATE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
368                | DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
369    '''
370    x = refpolicy.Template(p[4])
371    collect(p[8], x)
372    p[0] = x
373
374def p_define(p):
375    '''define : DEFINE OPAREN TICK IDENTIFIER SQUOTE CPAREN'''
376    # This is for defining single M4 values (to be used later in ifdef statements).
377    # Example: define(`sulogin_no_pam'). We don't currently do anything with these
378    # but we should in the future when we correctly resolve ifdef statements.
379    p[0] = None
380
381def p_interface_stmts(p):
382    '''interface_stmts : policy
383                       | interface_stmts policy
384                       | empty
385    '''
386    if len(p) == 2 and p[1]:
387        p[0] = p[1]
388    elif len(p) > 2:
389        if not p[1]:
390            if p[2]:
391                p[0] = p[2]
392        elif not p[2]:
393            p[0] = p[1]
394        else:
395            p[0] = p[1] + p[2]
396
397def p_optional_policy(p):
398    '''optional_policy : OPT_POLICY OPAREN TICK interface_stmts SQUOTE CPAREN
399                       | OPT_POLICY OPAREN TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
400    '''
401    o = refpolicy.OptionalPolicy()
402    collect(p[4], o, val=True)
403    if len(p) > 7:
404        collect(p[8], o, val=False)
405    p[0] = [o]
406
407def p_tunable_policy(p):
408    '''tunable_policy : TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
409                      | TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
410    '''
411    x = refpolicy.TunablePolicy()
412    x.cond_expr = p[4]
413    collect(p[8], x, val=True)
414    if len(p) > 11:
415        collect(p[12], x, val=False)
416    p[0] = [x]
417
418def p_ifelse(p):
419    '''ifelse : IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
420              | IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
421    '''
422#    x = refpolicy.IfDef(p[4])
423#    v = True
424#    collect(p[8], x, val=v)
425#    if len(p) > 12:
426#        collect(p[12], x, val=False)
427#    p[0] = [x]
428    pass
429
430
431def p_ifdef(p):
432    '''ifdef : IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
433             | IFNDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
434             | IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
435    '''
436    x = refpolicy.IfDef(p[4])
437    if p[1] == 'ifdef':
438        v = True
439    else:
440        v = False
441    collect(p[8], x, val=v)
442    if len(p) > 12:
443        collect(p[12], x, val=False)
444    p[0] = [x]
445
446def p_interface_call(p):
447    '''interface_call : IDENTIFIER OPAREN interface_call_param_list CPAREN
448                      | IDENTIFIER OPAREN CPAREN
449                      | IDENTIFIER OPAREN interface_call_param_list CPAREN SEMI'''
450    # Allow spurious semi-colons at the end of interface calls
451    i = refpolicy.InterfaceCall(ifname=p[1])
452    if len(p) > 4:
453        i.args.extend(p[3])
454    p[0] = i
455
456def p_interface_call_param(p):
457    '''interface_call_param : IDENTIFIER
458                            | IDENTIFIER MINUS IDENTIFIER
459                            | nested_id_set
460                            | TRUE
461                            | FALSE
462                            | FILENAME
463    '''
464    # Intentionally let single identifiers pass through
465    # List means set, non-list identifier
466    if len(p) == 2:
467        p[0] = p[1]
468    else:
469        p[0] = [p[1], "-" + p[3]]
470
471def p_interface_call_param_list(p):
472    '''interface_call_param_list : interface_call_param
473                                 | interface_call_param_list COMMA interface_call_param
474    '''
475    if len(p) == 2:
476        p[0] = [p[1]]
477    else:
478        p[0] = p[1] + [p[3]]
479
480
481def p_obj_perm_set(p):
482    'obj_perm_set : DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK names SQUOTE CPAREN'
483    s = refpolicy.ObjPermSet(p[4])
484    s.perms = p[8]
485    p[0] = s
486
487#
488# Basic SELinux policy language
489#
490
491def p_policy(p):
492    '''policy : policy_stmt
493              | optional_policy
494              | tunable_policy
495              | ifdef
496              | ifelse
497              | conditional
498    '''
499    p[0] = p[1]
500
501def p_policy_stmt(p):
502    '''policy_stmt : gen_require
503                   | avrule_def
504                   | typerule_def
505                   | typeattribute_def
506                   | roleattribute_def
507                   | interface_call
508                   | role_def
509                   | role_allow
510                   | permissive
511                   | type_def
512                   | typealias_def
513                   | attribute_def
514                   | attribute_role_def
515                   | range_transition_def
516                   | role_transition_def
517                   | bool
518                   | define
519                   | initial_sid
520                   | genfscon
521                   | fs_use
522                   | portcon
523                   | nodecon
524                   | netifcon
525                   | pirqcon
526                   | iomemcon
527                   | ioportcon
528                   | pcidevicecon
529                   | devicetreecon
530    '''
531    if p[1]:
532        p[0] = [p[1]]
533
534def p_module_stmt(p):
535    'module_stmt : MODULE IDENTIFIER NUMBER SEMI'
536    m = refpolicy.ModuleDeclaration()
537    m.name = p[2]
538    m.version = p[3]
539    m.refpolicy = False
540    p[0] = m
541
542def p_gen_require(p):
543    '''gen_require : GEN_REQ OPAREN TICK requires SQUOTE CPAREN
544                   | REQUIRE OBRACE requires CBRACE'''
545    # We ignore the require statements - they are redundant data from our point-of-view.
546    # Checkmodule will verify them later anyway so we just assume that they match what
547    # is in the rest of the interface.
548    pass
549
550def p_requires(p):
551    '''requires : require
552                | requires require
553                | ifdef
554                | requires ifdef
555    '''
556    pass
557
558def p_require(p):
559    '''require : TYPE comma_list SEMI
560               | ROLE comma_list SEMI
561               | ATTRIBUTE comma_list SEMI
562               | ATTRIBUTE_ROLE comma_list SEMI
563               | CLASS comma_list SEMI
564               | BOOL comma_list SEMI
565    '''
566    pass
567
568def p_security_context(p):
569    '''security_context : IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER
570                        | IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER COLON mls_range_def'''
571    # This will likely need some updates to handle complex levels
572    s = refpolicy.SecurityContext()
573    s.user = p[1]
574    s.role = p[3]
575    s.type = p[5]
576    if len(p) > 6:
577        s.level = p[7]
578
579    p[0] = s
580
581def p_gen_context(p):
582    '''gen_context : GEN_CONTEXT OPAREN security_context COMMA mls_range_def CPAREN
583    '''
584    # We actually store gen_context statements in a SecurityContext
585    # object - it knows how to output either a bare context or a
586    # gen_context statement.
587    s = p[3]
588    s.level = p[5]
589
590    p[0] = s
591
592def p_context(p):
593    '''context : security_context
594               | gen_context
595    '''
596    p[0] = p[1]
597
598def p_initial_sid(p):
599    '''initial_sid : SID IDENTIFIER context'''
600    s = refpolicy.InitialSid()
601    s.name = p[2]
602    s.context = p[3]
603    p[0] = s
604
605def p_genfscon(p):
606    '''genfscon : GENFSCON IDENTIFIER PATH context'''
607
608    g = refpolicy.GenfsCon()
609    g.filesystem = p[2]
610    g.path = p[3]
611    g.context = p[4]
612
613    p[0] = g
614
615def p_fs_use(p):
616    '''fs_use : FS_USE_XATTR IDENTIFIER context SEMI
617              | FS_USE_TASK IDENTIFIER context SEMI
618              | FS_USE_TRANS IDENTIFIER context SEMI
619    '''
620    f = refpolicy.FilesystemUse()
621    if p[1] == "fs_use_xattr":
622        f.type = refpolicy.FilesystemUse.XATTR
623    elif p[1] == "fs_use_task":
624        f.type = refpolicy.FilesystemUse.TASK
625    elif p[1] == "fs_use_trans":
626        f.type = refpolicy.FilesystemUse.TRANS
627
628    f.filesystem = p[2]
629    f.context = p[3]
630
631    p[0] = f
632
633def p_portcon(p):
634    '''portcon : PORTCON IDENTIFIER NUMBER context
635               | PORTCON IDENTIFIER NUMBER MINUS NUMBER context'''
636    c = refpolicy.PortCon()
637    c.port_type = p[2]
638    if len(p) == 5:
639        c.port_number = p[3]
640        c.context = p[4]
641    else:
642        c.port_number = p[3] + "-" + p[4]
643        c.context = p[5]
644
645    p[0] = c
646
647def p_nodecon(p):
648    '''nodecon : NODECON NUMBER NUMBER context
649               | NODECON IPV6_ADDR IPV6_ADDR context
650    '''
651    n = refpolicy.NodeCon()
652    n.start = p[2]
653    n.end = p[3]
654    n.context = p[4]
655
656    p[0] = n
657
658def p_netifcon(p):
659    'netifcon : NETIFCON IDENTIFIER context context'
660    n = refpolicy.NetifCon()
661    n.interface = p[2]
662    n.interface_context = p[3]
663    n.packet_context = p[4]
664
665    p[0] = n
666
667def p_pirqcon(p):
668    'pirqcon : PIRQCON NUMBER context'
669    c = refpolicy.PirqCon()
670    c.pirq_number = p[2]
671    c.context = p[3]
672
673    p[0] = c
674
675def p_iomemcon(p):
676    '''iomemcon : IOMEMCON NUMBER context
677                | IOMEMCON NUMBER MINUS NUMBER context'''
678    c = refpolicy.IomemCon()
679    if len(p) == 4:
680        c.device_mem = p[2]
681        c.context = p[3]
682    else:
683        c.device_mem = p[2] + "-" + p[3]
684        c.context = p[4]
685
686    p[0] = c
687
688def p_ioportcon(p):
689    '''ioportcon : IOPORTCON NUMBER context
690                | IOPORTCON NUMBER MINUS NUMBER context'''
691    c = refpolicy.IoportCon()
692    if len(p) == 4:
693        c.ioport = p[2]
694        c.context = p[3]
695    else:
696        c.ioport = p[2] + "-" + p[3]
697        c.context = p[4]
698
699    p[0] = c
700
701def p_pcidevicecon(p):
702    'pcidevicecon : PCIDEVICECON NUMBER context'
703    c = refpolicy.PciDeviceCon()
704    c.device = p[2]
705    c.context = p[3]
706
707    p[0] = c
708
709def p_devicetreecon(p):
710    'devicetreecon : DEVICETREECON NUMBER context'
711    c = refpolicy.DevicetTeeCon()
712    c.path = p[2]
713    c.context = p[3]
714
715    p[0] = c
716
717def p_mls_range_def(p):
718    '''mls_range_def : mls_level_def MINUS mls_level_def
719                     | mls_level_def
720    '''
721    p[0] = p[1]
722    if len(p) > 2:
723        p[0] = p[0] + "-" + p[3]
724
725def p_mls_level_def(p):
726    '''mls_level_def : IDENTIFIER COLON comma_list
727                     | IDENTIFIER
728    '''
729    p[0] = p[1]
730    if len(p) > 2:
731        p[0] = p[0] + ":" + ",".join(p[3])
732
733def p_type_def(p):
734    '''type_def : TYPE IDENTIFIER COMMA comma_list SEMI
735                | TYPE IDENTIFIER SEMI
736                | TYPE IDENTIFIER ALIAS names SEMI
737                | TYPE IDENTIFIER ALIAS names COMMA comma_list SEMI
738    '''
739    t = refpolicy.Type(p[2])
740    if len(p) == 6:
741        if p[3] == ',':
742            t.attributes.update(p[4])
743        else:
744            t.aliases = p[4]
745    elif len(p) > 4:
746        t.aliases = p[4]
747        if len(p) == 8:
748            t.attributes.update(p[6])
749    p[0] = t
750
751def p_attribute_def(p):
752    'attribute_def : ATTRIBUTE IDENTIFIER SEMI'
753    a = refpolicy.Attribute(p[2])
754    p[0] = a
755
756def p_attribute_role_def(p):
757	'attribute_role_def : ATTRIBUTE_ROLE IDENTIFIER SEMI'
758	a = refpolicy.Attribute_Role(p[2])
759	p[0] = a
760
761def p_typealias_def(p):
762    'typealias_def : TYPEALIAS IDENTIFIER ALIAS names SEMI'
763    t = refpolicy.TypeAlias()
764    t.type = p[2]
765    t.aliases = p[4]
766    p[0] = t
767
768def p_role_def(p):
769    '''role_def : ROLE IDENTIFIER TYPES comma_list SEMI
770                | ROLE IDENTIFIER SEMI'''
771    r = refpolicy.Role()
772    r.role = p[2]
773    if len(p) > 4:
774        r.types.update(p[4])
775    p[0] = r
776
777def p_role_allow(p):
778    'role_allow : ALLOW names names SEMI'
779    r = refpolicy.RoleAllow()
780    r.src_roles = p[2]
781    r.tgt_roles = p[3]
782    p[0] = r
783
784def p_permissive(p):
785    'permissive : PERMISSIVE names SEMI'
786    t.skip(1)
787
788def p_avrule_def(p):
789    '''avrule_def : ALLOW names names COLON names names SEMI
790                  | DONTAUDIT names names COLON names names SEMI
791                  | AUDITALLOW names names COLON names names SEMI
792                  | NEVERALLOW names names COLON names names SEMI
793    '''
794    a = refpolicy.AVRule()
795    if p[1] == 'dontaudit':
796        a.rule_type = refpolicy.AVRule.DONTAUDIT
797    elif p[1] == 'auditallow':
798        a.rule_type = refpolicy.AVRule.AUDITALLOW
799    elif p[1] == 'neverallow':
800        a.rule_type = refpolicy.AVRule.NEVERALLOW
801    a.src_types = p[2]
802    a.tgt_types = p[3]
803    a.obj_classes = p[5]
804    a.perms = p[6]
805    p[0] = a
806
807def p_typerule_def(p):
808    '''typerule_def : TYPE_TRANSITION names names COLON names IDENTIFIER SEMI
809                    | TYPE_TRANSITION names names COLON names IDENTIFIER FILENAME SEMI
810                    | TYPE_TRANSITION names names COLON names IDENTIFIER IDENTIFIER SEMI
811                    | TYPE_CHANGE names names COLON names IDENTIFIER SEMI
812                    | TYPE_MEMBER names names COLON names IDENTIFIER SEMI
813    '''
814    t = refpolicy.TypeRule()
815    if p[1] == 'type_change':
816        t.rule_type = refpolicy.TypeRule.TYPE_CHANGE
817    elif p[1] == 'type_member':
818        t.rule_type = refpolicy.TypeRule.TYPE_MEMBER
819    t.src_types = p[2]
820    t.tgt_types = p[3]
821    t.obj_classes = p[5]
822    t.dest_type = p[6]
823    t.file_name = p[7]
824    p[0] = t
825
826def p_bool(p):
827    '''bool : BOOL IDENTIFIER TRUE SEMI
828            | BOOL IDENTIFIER FALSE SEMI'''
829    b = refpolicy.Bool()
830    b.name = p[2]
831    if p[3] == "true":
832        b.state = True
833    else:
834        b.state = False
835    p[0] = b
836
837def p_conditional(p):
838    ''' conditional : IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE
839                    | IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE ELSE OBRACE interface_stmts CBRACE
840    '''
841    c = refpolicy.Conditional()
842    c.cond_expr = p[3]
843    collect(p[6], c, val=True)
844    if len(p) > 8:
845        collect(p[10], c, val=False)
846    p[0] = [c]
847
848def p_typeattribute_def(p):
849    '''typeattribute_def : TYPEATTRIBUTE IDENTIFIER comma_list SEMI'''
850    t = refpolicy.TypeAttribute()
851    t.type = p[2]
852    t.attributes.update(p[3])
853    p[0] = t
854
855def p_roleattribute_def(p):
856    '''roleattribute_def : ROLEATTRIBUTE IDENTIFIER comma_list SEMI'''
857    t = refpolicy.RoleAttribute()
858    t.role = p[2]
859    t.roleattributes.update(p[3])
860    p[0] = t
861
862def p_range_transition_def(p):
863    '''range_transition_def : RANGE_TRANSITION names names COLON names mls_range_def SEMI
864                            | RANGE_TRANSITION names names names SEMI'''
865    pass
866
867def p_role_transition_def(p):
868    '''role_transition_def : ROLE_TRANSITION names names names SEMI'''
869    pass
870
871def p_cond_expr(p):
872    '''cond_expr : IDENTIFIER
873                 | EXPL cond_expr
874                 | cond_expr AMP AMP cond_expr
875                 | cond_expr BAR BAR cond_expr
876                 | cond_expr EQUAL EQUAL cond_expr
877                 | cond_expr EXPL EQUAL cond_expr
878    '''
879    l = len(p)
880    if l == 2:
881        p[0] = [p[1]]
882    elif l == 3:
883        p[0] = [p[1]] + p[2]
884    else:
885        p[0] = p[1] + [p[2] + p[3]] + p[4]
886
887
888#
889# Basic terminals
890#
891
892# Identifiers and lists of identifiers. These must
893# be handled somewhat gracefully. Names returns an IdSet and care must
894# be taken that this is _assigned_ to an object to correctly update
895# all of the flags (as opposed to using update). The other terminals
896# return list - this is to preserve ordering if it is important for
897# parsing (for example, interface_call must retain the ordering). Other
898# times the list should be used to update an IdSet.
899
900def p_names(p):
901    '''names : identifier
902             | nested_id_set
903             | asterisk
904             | TILDE identifier
905             | TILDE nested_id_set
906             | IDENTIFIER MINUS IDENTIFIER
907    '''
908    s = refpolicy.IdSet()
909    if len(p) < 3:
910        expand(p[1], s)
911    elif len(p) == 3:
912        expand(p[2], s)
913        s.compliment = True
914    else:
915        expand([p[1]])
916        s.add("-" + p[3])
917    p[0] = s
918
919def p_identifier(p):
920    'identifier : IDENTIFIER'
921    p[0] = [p[1]]
922
923def p_asterisk(p):
924    'asterisk : ASTERISK'
925    p[0] = [p[1]]
926
927def p_nested_id_set(p):
928    '''nested_id_set : OBRACE nested_id_list CBRACE
929    '''
930    p[0] = p[2]
931
932def p_nested_id_list(p):
933    '''nested_id_list : nested_id_element
934                      | nested_id_list nested_id_element
935    '''
936    if len(p) == 2:
937        p[0] = p[1]
938    else:
939        p[0] = p[1] + p[2]
940
941def p_nested_id_element(p):
942    '''nested_id_element : identifier
943                         | MINUS IDENTIFIER
944                         | nested_id_set
945    '''
946    if len(p) == 2:
947        p[0] = p[1]
948    else:
949        # For now just leave the '-'
950        str = "-" + p[2]
951        p[0] = [str]
952
953def p_comma_list(p):
954    '''comma_list : nested_id_list
955                  | comma_list COMMA nested_id_list
956    '''
957    if len(p) > 2:
958        p[1] = p[1] + p[3]
959    p[0] = p[1]
960
961def p_optional_semi(p):
962    '''optional_semi : SEMI
963                   | empty'''
964    pass
965
966
967#
968# Interface to the parser
969#
970
971def p_error(tok):
972    global error, parse_file, success, parser
973    error = "%s: Syntax error on line %d %s [type=%s]" % (parse_file, tok.lineno, tok.value, tok.type)
974    print error
975    success = False
976
977def prep_spt(spt):
978    if not spt:
979        return { }
980    map = {}
981    for x in spt:
982        map[x.name] = x
983
984parser = None
985lexer = None
986def create_globals(module, support, debug):
987    global parser, lexer, m, spt
988
989    if not parser:
990        lexer = lex.lex()
991        parser = yacc.yacc(method="LALR", debug=debug, write_tables=0)
992
993    if module is not None:
994        m = module
995    else:
996        m = refpolicy.Module()
997
998    if not support:
999        spt = refpolicy.SupportMacros()
1000    else:
1001        spt = support
1002
1003def parse(text, module=None, support=None, debug=False):
1004    create_globals(module, support, debug)
1005    global error, parser, lexer, success
1006
1007    success = True
1008
1009    try:
1010        parser.parse(text, debug=debug, lexer=lexer)
1011    except Exception, e:
1012        parser = None
1013        lexer = None
1014        error = "internal parser error: %s" % str(e) + "\n" + traceback.format_exc()
1015
1016    if not success:
1017        # force the parser and lexer to be rebuilt - we have some problems otherwise
1018        parser = None
1019        msg = 'could not parse text: "%s"' % error
1020        raise ValueError(msg)
1021    return m
1022
1023def list_headers(root):
1024    modules = []
1025    support_macros = None
1026
1027    for dirpath, dirnames, filenames in os.walk(root):
1028        for name in filenames:
1029            modname = os.path.splitext(name)
1030            filename = os.path.join(dirpath, name)
1031
1032            if modname[1] == '.spt':
1033                if name == "obj_perm_sets.spt":
1034                    support_macros = filename
1035                elif len(re.findall("patterns", modname[0])):
1036                         modules.append((modname[0], filename))
1037            elif modname[1] == '.if':
1038                modules.append((modname[0], filename))
1039
1040    return (modules, support_macros)
1041
1042
1043def parse_headers(root, output=None, expand=True, debug=False):
1044    import util
1045
1046    headers = refpolicy.Headers()
1047
1048    modules = []
1049    support_macros = None
1050
1051    if os.path.isfile(root):
1052        name = os.path.split(root)[1]
1053        if name == '':
1054            raise ValueError("Invalid file name %s" % root)
1055        modname = os.path.splitext(name)
1056        modules.append((modname[0], root))
1057        all_modules, support_macros = list_headers(defaults.headers())
1058    else:
1059        modules, support_macros = list_headers(root)
1060
1061    if expand and not support_macros:
1062        raise ValueError("could not find support macros (obj_perm_sets.spt)")
1063
1064    def o(msg):
1065        if output:
1066            output.write(msg)
1067
1068    def parse_file(f, module, spt=None):
1069        global parse_file
1070        if debug:
1071            o("parsing file %s\n" % f)
1072        try:
1073            fd = open(f)
1074            txt = fd.read()
1075            fd.close()
1076            parse_file = f
1077            parse(txt, module, spt, debug)
1078        except IOError, e:
1079            return
1080        except ValueError, e:
1081            raise ValueError("error parsing file %s: %s" % (f, str(e)))
1082
1083    spt = None
1084    if support_macros:
1085        o("Parsing support macros (%s): " % support_macros)
1086        spt = refpolicy.SupportMacros()
1087        parse_file(support_macros, spt)
1088
1089        headers.children.append(spt)
1090
1091        # FIXME: Total hack - add in can_exec rather than parse the insanity
1092        # of misc_macros. We are just going to pretend that this is an interface
1093        # to make the expansion work correctly.
1094        can_exec = refpolicy.Interface("can_exec")
1095        av = access.AccessVector(["$1","$2","file","execute_no_trans","open", "read",
1096                                  "getattr","lock","execute","ioctl"])
1097
1098        can_exec.children.append(refpolicy.AVRule(av))
1099        headers.children.append(can_exec)
1100
1101        o("done.\n")
1102
1103    if output and not debug:
1104        status = util.ConsoleProgressBar(sys.stdout, steps=len(modules))
1105        status.start("Parsing interface files")
1106
1107    failures = []
1108    for x in modules:
1109        m = refpolicy.Module()
1110        m.name = x[0]
1111        try:
1112            if expand:
1113                parse_file(x[1], m, spt)
1114            else:
1115                parse_file(x[1], m)
1116        except ValueError, e:
1117            o(str(e) + "\n")
1118            failures.append(x[1])
1119            continue
1120
1121        headers.children.append(m)
1122        if output and not debug:
1123            status.step()
1124
1125    if len(failures):
1126        o("failed to parse some headers: %s" % ", ".join(failures))
1127
1128    return headers
1129