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