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