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