
\\y                 @   s   d  Z  d d l Z d d l Z d d l Z d d l Z d d l Z d d l Z d d l Z d d l Z d d l	 m
 Z
 m Z m Z d d l m Z e j e  Z Gd d   d e  Z d d   Z d	 d
   Z d S)z@ApacheParser is a member object of the ApacheConfigurator class.    N)DictListSet)errorsc               @   s  e  Z d  Z d Z e j d  Z e d d d d d g  Z d dP d d d  Z	 d d   Z
 d d   Z d d   Z d d   Z d d   Z d d   Z d d   Z d d   Z d d   Z d d    Z d! d"   Z d# d$   Z d% d&   Z d' d(   Z d) d*   Z d+ d,   Z d d- d.  Z d d d/ d0 d1  Z d2 d3   Z d4 d5   Z d6 d7   Z d8 d9   Z d: d;   Z  d< d=   Z! d> d?   Z" d@ dA   Z# dB dC   Z$ dD dE   Z% dF dG   Z& dH dI   Z' dJ dK   Z( dL dM   Z) dN dO   Z* d S)QApacheParsera  Class handles the fine details of parsing the Apache Configuration.

    .. todo:: Make parsing general... remove sites-available etc...

    :ivar str root: Normalized absolute path to the server root
        directory. Without trailing slash.
    :ivar set modules: All module names that are currently enabled.
    :ivar dict loc: Location to place directives, root - configuration origin,
        default - user config file, name - NameVirtualHost,

    z\$\{[^ \}]*}*?\[]N      c             C   s*  | |  _  t   |  _ i  |  _ i  |  _ | |  _ t j j |  |  _	 d |  j
   i |  _ |  j |  j d  | d
 k r |  j   |  j   |  j   |  j j |  j    t j |  j  |  _ | r |  j t j j |  d |  j  j d   | d k  r&|  j d d d r&t j d	   d  S)Nrootr   r   /Zvhost_filesZDefineexcludeFzError parsing runtime variables)r   r   )r   r   )configuratorsetmodulesparser_paths	variablesaugospathabspathr   _find_config_rootloc
parse_fileupdate_runtime_variablesstandardize_exclparse_modulesupdate_set_locationscopydeepcopyexisting_pathsoptionfind_dirr   PluginError)selfr   r   Z	vhostrootversionr    r*   7/usr/lib/python3/dist-packages/certbot_apache/parser.py__init__"   s(    				


zApacheParser.__init__c             C   s   t  |  j t d  |   d k r t j d | t |   |  j t |  d |  t j j	 |  } t j j
 |  } | |  j j   k r |  j | j |  n | g |  j | <d S)zAdd Include for a new configuration file if one does not exist

        :param str main_config: file path to main Apache config file
        :param str inc_path: path of file to include

        Includer   zAdding Include %s to %sN)lenr&   case_iloggerdebugget_aug_pathadd_dirr   r   dirnamebasenamer$   keysappend)r(   Zmain_configZinc_pathZnew_dirZnew_filer*   r*   r+   add_includeO   s    $		
zApacheParser.add_includec             C   sZ   | d |  j  k r' |  j  j | d  d | d |  j  k rV |  j  j d | d  d S)z%Shortcut for updating parser modules._moduleZmod_z.cN)r   add)r(   mod_namer*   r*   r+   add_modg   s    zApacheParser.add_modc             C   s$   t    |  _ |  j   |  j   d S)zgReset the loaded modules list. This is called from cleanup to clear
        temporarily loaded modules.N)r   r   update_modulesr   )r(   r*   r*   r+   reset_modulesn   s    
zApacheParser.reset_modulesc       	      C   s  t    } |  j d  } t |  } d	 } x t |  | k r t |  } x t j j | |  D] \ } } |  j |  } |  j |  } | r | r | j |  | j t	 j
 j |  d d
  d  qa t j d d j | d d    qa Wq- W|  j j |  d S)zIterates on the configuration until no new modules are loaded.

        ..todo:: This should be attempted to be done with a binary to avoid
            the iteration issue.  Else... parse and enable mods at same time.

        Z
LoadModule   Nr   cz)Could not read LoadModule directive from zAugeas path: {0}   )r   r&   iterr.   sixZmoveszipget_argr:   r   r   r5   r0   r1   formatr   r    )	r(   ZmodsmatchesiteratorZ	prev_sizeZ
match_nameZmatch_filenamer;   Zmod_filenamer*   r*   r+   r   u   s     	*	#zApacheParser.parse_modulesc             C   s"   |  j    |  j   |  j   d S)zAUpdate Includes, Defines and Includes from httpd config dump dataN)update_definesupdate_includesr=   )r(   r*   r*   r+   r      s    

z%ApacheParser.update_runtime_variablesc             C   s   t    } |  j j d  d d d g } |  j | d  } y | j d  Wn t k
 rb d SYn Xx` | D]X } | j d  d k r t j d	  t	 j
 d
   | j d  } | d | | d <qj W| |  _ d S)zGet Defines from httpd processctlz-tz-DZDUMP_RUN_CFGzDefine: ([^ \n]*)N=r?   z8Unexpected number of equal signs in runtime config dump.z&Error parsing Apache runtime variablesr   r   )dictr   r%   parse_from_subprocessremove
ValueErrorcountr0   errorr   r'   	partitionr   )r(   r   Z
define_cmdrI   matchpartsr*   r*   r+   rK      s     				zApacheParser.update_definesc             C   sv   |  j  d  } |  j j d  d d d g } |  j | d  } | rr x* | D]" } |  j |  sL |  j |  qL Wd S)z>Get includes from httpd process, and add them to DOM if neededr-   rM   z-tz-DZDUMP_INCLUDESz\(.*\) (.*)N)r&   r   r%   rP   parsed_in_currentr   )r(   _Zinc_cmdrI   ir*   r*   r+   rL      s    	zApacheParser.update_includesc             C   sX   |  j  j d  d d d g } |  j | d  } x! | D] } |  j | j    q7 Wd S)z:Get loaded modules from httpd process, and add them to DOMrM   z-tz-DZDUMP_MODULESz(.*)_moduleN)r   r%   rP   r<   strip)r(   Zmod_cmdrI   modr*   r*   r+   r=      s
    	zApacheParser.update_modulesc             C   s%   |  j  |  } t j |  j |  S)zGet values from stdout of subprocess command

        :param list command: Command to run
        :param str regexp: Regexp for parsing

        :returns: list parsed from command output
        :rtype: list

        )_get_runtime_cfgrecompilefindall)r(   commandZregexpstdoutr*   r*   r+   rP      s    
z"ApacheParser.parse_from_subprocessc             C   s   y= t  j | d t  j d t  j d d } | j   \ } } Wn@ t t f k
 r t j d | t j	  t
 j d |   Yn X| j d k r t j d |  t
 j d	   | S)
zwGet runtime configuration info.
        :param command: Command to run

        :returns: stdout from command

        rb   stderrZuniversal_newlinesTz2Error running command %s for runtime parameters!%sz,Error accessing loaded Apache parameters: %sr   z$Error in checking parameter list: %sz^Apache is unable to check whether or not the module is loaded because Apache is misconfigured.)
subprocessPopenPIPEZcommunicateOSErrorrR   r0   rT   r   linesepr   ZMisconfigurationError
returncodeZwarning)r(   ra   procrb   rc   r*   r*   r+   r]      s&    				zApacheParser._get_runtime_cfgc             C   s   g  } | d k r` x t  t |   D]4 } | | j d  r% | j | | d d   q% Wn x t  t |   D]y } | | j d |  rs | t |  d k s | | d j d | d  rs | j | | d t d |    qs W| S)a  Filter out directives with specific number of arguments.

        This function makes the assumption that all related arguments are given
        in order.  Thus /files/apache/directive[5]/arg[2] must come immediately
        after /files/apache/directive[5]/arg[1]. Runs in 1 linear pass.

        :param string matches: Matches of all directives with arg nodes
        :param int args: Number of args you would like to filter

        :returns: List of directives that contain # of arguments.
            (arg is stripped off)

        r?   z/argNr   z/arg[%d])ranger.   endswithr7   )r(   rI   argsZfilteredrZ   r*   r*   r+   filter_args_num   s    "*zApacheParser.filter_args_numc             C   s   |  j  | d  } |  j j | d d d  | d } |  j j | |  t |  d k ry |  j j | d | d  n> x; t |  D]- \ } } |  j j d	 | | d f |  q Wd
 S)a  Adds directive and value to IfMod ssl block.

        Adds given directive and value along configuration path within
        an IfMod mod_ssl.c block.  If the IfMod block does not exist in
        the file, it is created.

        :param str aug_conf_path: Desired Augeas config path to add directive
        :param str directive: Directive you would like to add, e.g. Listen
        :param args: Values of the directive; str "443" or list of str
        :type args: list

        z	mod_ssl.carg	directiveFzdirective[1]r?   z/argr   z
%s/arg[%d]N)
_get_ifmodr   insertr   r.   	enumerate)r(   aug_conf_pathrq   rn   Zif_mod_pathZnvh_pathrZ   rp   r*   r*   r+   add_dir_to_ifmodssl  s    
z ApacheParser.add_dir_to_ifmodsslc             C   s   |  j  j d | | f  } t |  d k rx |  j  j d | d  |  j  j d | |  |  j  j d | | f  } | d d t | d  d  S)zReturns the path to <IfMod mod> and creates one if it doesn't exist.

        :param str aug_conf_path: Augeas configuration path
        :param str mod: module ie. mod_ssl.c

        z%s/IfModule/*[self::arg='%s']r   z%s/IfModule[last() + 1] z%s/IfModule[last()]/argN   )r   rV   r.   r   )r(   ru   r\   Zif_modsr*   r*   r+   rr   ,  s    zApacheParser._get_ifmodc             C   s   |  j  j | d |  t | t  rf xT t | d  D]) \ } } |  j  j d | | f |  q6 Wn |  j  j | d |  d S)a  Appends directive to the end fo the file given by aug_conf_path.

        .. note:: Not added to AugeasConfigurator because it may depend
            on the lens

        :param str aug_conf_path: Augeas configuration path to add directive
        :param str directive: Directive to add
        :param args: Value of the directive. ie. Listen 443, 443 is arg
        :type args: list or str

        z/directive[last() + 1]r?   z%s/directive[last()]/arg[%d]z/directive[last()]/argN)r   r   
isinstancelistrt   )r(   ru   rq   rn   rZ   valuer*   r*   r+   r3   =  s    	zApacheParser.add_dirc             C   s   | d } |  j  j | d d  |  j  j | |  t | t  r xR t | d  D]' \ } } |  j  j | d | |  qR Wn |  j  j | d |  d S)a)  Adds the directive to the beginning of defined aug_conf_path.

        :param str aug_conf_path: Augeas configuration path to add directive
        :param str dirname: Directive to add
        :param args: Value of the directive. ie. Listen 443, 443 is arg
        :type args: list or str
        z/directive[1]rq   Tr?   z/arg[%d]z/argN)r   rs   r   ry   rz   rt   )r(   ru   r4   rn   Z	first_dirrZ   r{   r*   r*   r+   add_dir_beginningQ  s    
"zApacheParser.add_dir_beginningc             C   s   |  j  j | d |  d S)zAdds the comment to the augeas path

        :param str aug_conf_path: Augeas configuration path to add directive
        :param str comment: Comment content

        z/#comment[last() + 1]N)r   r   )r(   ru   commentr*   r*   r+   add_commentb  s    zApacheParser.add_commentc             C   sw   | s t  |  j  } |  j j d |  } g  } x? | D]7 } |  j j |  } | r8 | | k r8 | j |  q8 W| S)a  Finds a comment with specified content from the provided DOM path

        :param str arg: Comment content to search
        :param str start: Beginning Augeas path to begin looking

        :returns: List of augeas paths containing the comment content
        :rtype: list

        z%s//*[label() = '#comment'])r2   r   r   rV   getr7   )r(   rp   startZcommentsZresultsr}   Z	c_contentr*   r*   r+   find_commentsk  s    
zApacheParser.find_commentsTc          	   C   sC  | s t  |  j d  } d t |  t d  t d  f } |  j j d | | f  } | ro |  j |  } | d k r d } n d t |  } g  } x | D] }	 |  j j |	  j   }
 |
 d	 k s |
 d
 k r| j |  j	 | | |  j
 |  j |	 d   |   |
 | j   k r | j |  j j |	 |   q W| S)a	  Finds directive in the configuration.

        Recursively searches through config files to find directives
        Directives should be in the form of a case insensitive regex currently

        .. todo:: arg should probably be a list
        .. todo:: arg search currently only supports direct matching. It does
            not handle the case of variables or quoted arguments. This should
            be adapted to use a generic search for the directive and then do a
            case-insensitive self.get_arg filter

        Note: Augeas is inherently case sensitive while Apache is case
        insensitive.  Augeas 1.0 allows case insensitive regexes like
        regexp(/Listen/, "i"), however the version currently supported
        by Ubuntu 0.10 does not.  Thus I have included my own case insensitive
        transformation by calling case_i() on everything to maintain
        compatibility.

        :param str directive: Directive to look for
        :param arg: Specific value directive must have, None if all should
                    be considered
        :type arg: str or None

        :param str start: Beginning Augeas path to begin looking
        :param bool exclude: Whether or not to exclude directives based on
            variables and enabled modules

        r   z(%s)|(%s)|(%s)r-   ZIncludeOptionalz$%s//*[self::directive=~regexp('%s')]Nz/argz/*[self::arg=~regexp('%s')]includeZincludeoptional)r2   r   r/   r   rV   _exclude_dirsr   lowerextendr&   _get_include_pathrG   )r(   rq   rp   r   r   ZregexrI   Z
arg_suffixZordered_matchesrV   Zdir_r*   r*   r+   r&     s.    			
!zApacheParser.find_dirc             C   s   |  j  j |  } | s d S| j d  } t j j |  } x] | D]U } y' | j | |  j | d d   } WqD t k
 r t	 j
 d |   YqD XqD W| S)zUses augeas.get to get argument value and interprets result.

        This also converts all variables and parameters appropriately.

        Nz'"r   r?   zError Parsing variable: %srB   )r   r   r[   r   arg_var_interpreterr`   replacer   KeyErrorr   r'   )r(   rV   r{   r   varr*   r*   r+   rG     s    'zApacheParser.get_argc             C   sj   d |  j  f d |  j f g } g  } x? | D]7 } x. | D] } |  j | |  s8 Pq8 W| j |  q+ W| S)z>Exclude directives that are not loaded into the configuration.ZifmoduleZifdefine)r   r   _pass_filterr7   )r(   rI   filtersZvalid_matchesrV   filter_r*   r*   r+   r     s    zApacheParser._exclude_dirsc             C   s   | j    } | j | d  } x | d	 k r | j d |  } |  j j | d |  d  } | j d  r | d d  | d k r d Sn | | d k r d S| j | d |  } q" Wd S)
zDetermine if directive passes a filter.

        :param str match: Augeas path
        :param list filter: list of tuples of form
            [("lowercase if directive", set of relevant parameters)]

        r   r?   r   Nz/arg!FTrB   )r   findr   r   
startswith)r(   rV   r   Zmatch_lZlast_match_idxZ	end_of_ifZ
expressionr*   r*   r+   r     s     zApacheParser._pass_filterc             C   s
  | j  d  } | j d  sE t j j t j j |  j |   } n t j j |  } t j j |  r |  j t j j | d   n |  j |  | j	 d  } xJ t
 |  D]< \ } } t d d   | D  r d |  j |  | | <q Wd j |  } t |  S)a@  Converts an Apache Include directive into Augeas path.

        Converts an Apache Include directive argument into an Augeas
        searchable path

        .. todo:: convert to use os.path.join()

        :param str arg: Argument of Include directive

        :returns: Augeas path string
        :rtype: str

        z'"r   r   c             s   s   |  ] } | t  j k Vq d  S)N)r   fnmatch_chars).0charr*   r*   r+   	<genexpr>?  s    z1ApacheParser._get_include_path.<locals>.<genexpr>z* [label()=~regexp('%s')])r[   r   r   r   normpathjoinr   isdirr   splitrt   anyfnmatch_to_rer2   )r(   rp   Z	split_argidxr   r*   r*   r+   r     s    'zApacheParser._get_include_pathc             C   sA   t  j d k  r& t j |  d d  St j |  d d  Sd S)	a  Method converts Apache's basic fnmatch to regular expression.

        Assumption - Configs are assumed to be well-formed and only writable by
        privileged users.

        https://apr.apache.org/docs/apr/2.0/apr__fnmatch_8h_source.html
        http://apache2.sourcearchive.com/documentation/2.2.16-6/apr__fnmatch_8h_source.html

        :param str clean_fn_match: Apache style filename match, like globs

        :returns: regex suitable for augeas
        :rtype: str

        rx   rA   N   r   )rx   rA   i)sysversion_infofnmatch	translate)r(   Zclean_fn_matchr*   r*   r+   r   J  s    zApacheParser.fnmatch_to_rec             C   s~   |  j  |  \ } } |  j r+ |  j j   | rz |  j j d |  } | sz | r` |  j |  |  j |  |  j j   d S)zParse file with Augeas

        Checks to see if file_path is parsed by Augeas
        If filepath isn't parsed, the file is added and Augeas is reloaded

        :param str filepath: Apache config file path

        z&/augeas/load/Httpd['%s' =~ glob(incl)]N)_check_path_actionsr   Zensure_augeas_stater   rV   _remove_httpd_transform_add_httpd_transformload)r(   filepathuse_new
remove_oldZinc_testr*   r*   r+   r   `  s    			zApacheParser.parse_filec             C   s   |  j  | |  j  S)a<  Checks if the file path is parsed by current Augeas parser config
        ie. returns True if the file is found on a path that's found in live
        Augeas configuration.

        :param str filep: Path to match

        :returns: True if file is parsed in existing configuration tree
        :rtype: bool
        )_parsed_by_parser_pathsr   )r(   filepr*   r*   r+   rX   |  s    
zApacheParser.parsed_in_currentc             C   s   |  j  | |  j  S)a[  Checks if the file path is parsed by existing Apache config.
        ie. returns True if the file is found on a path that matches Include or
        IncludeOptional statement in the Apache configuration.

        :param str filep: Path to match

        :returns: True if file is parsed in existing configuration tree
        :rtype: bool
        )r   r$   )r(   r   r*   r*   r+   parsed_in_original  s    
zApacheParser.parsed_in_originalc             C   sU   xN | j    D]@ } x7 | | D]+ } t j | t j j | |   r d Sq Wq Wd S)znHelper function that searches through provided paths and returns
        True if file path is found in the setTF)r6   r   r   r   r   )r(   r   pathsZ	directoryfilenamer*   r*   r+   r     s
    !z$ApacheParser._parsed_by_parser_pathsc             C   s   ye t  j j |  } |  j t  j j |  } d | k rC d } n d } | d k r^ d } n d } Wn t k
 r d } d } Yn X| | f S)aL  Determine actions to take with a new augeas path

        This helper function will return a tuple that defines
        if we should try to append the new filepath to augeas
        parser paths, and / or remove the old one with more
        narrow matching.

        :param str filepath: filepath to check the actions for

        r   FT)r   r   r5   r   r4   r   )r(   r   Znew_file_matchZexisting_matchesr   r   r*   r*   r+   r     s    		
z ApacheParser._check_path_actionsc             C   s   |  j  t j j |  } t j j |  } xF | D]> } | d | } |  j j d |  } |  j j | d  q2 W|  j  j |  d S)z[Remove path from Augeas transform

        :param str filepath: filepath to remove
        r   z!/augeas/load/Httpd/incl [. ='%s']r   N)r   r   r   r4   r   rV   rQ   pop)r(   r   Zremove_basenamesZremove_dirnamenameZremove_pathZ
remove_incr*   r*   r+   r     s    	z$ApacheParser._remove_httpd_transformc             C   s   |  j  j d  } | rH |  j  j | d d d  |  j  j d |  n& |  j  j d d  |  j  j d |  y0 |  j t j j |  j t j j	 |   Wn: t
 k
 r t j j	 |  g |  j t j j |  <Yn Xd	 S)
a"  Add a transform to Augeas.

        This function will correctly add a transform to augeas
        The existing augeas.add_transform in python doesn't seem to work for
        Travis CI as it loads in libaugeas.so.0.10.0

        :param str incl: filepath to include for transform

        z /augeas/load/Httpd/incl [last()]r   inclFz/augeas/load/Httpd/incl[last()]z/augeas/load/Httpd/lensz	Httpd.lnsz/augeas/load/Httpd/inclN)r   rV   rs   r   r   r   r   r4   r7   r5   r   )r(   r   Zlast_includer*   r*   r+   r     s    
z!ApacheParser._add_httpd_transformc             C   s   d d d d d d d d d	 |  j  d
 |  j  d |  j  d |  j  d |  j  d |  j  d g } x4 t | d  D]# \ } } |  j j d | |  qm W|  j j   d S)aa  Standardize the excl arguments for the Httpd lens in Augeas.

        Note: Hack!
        Standardize the excl arguments for the Httpd lens in Augeas
        Servers sometimes give incorrect defaults
        Note: This problem should be fixed in Augeas 1.0.  Unfortunately,
        Augeas 0.10 appears to be the most popular version currently.

        z*.augnewz	*.augsavez*.dpkg-distz
*.dpkg-bakz
*.dpkg-newz
*.dpkg-oldz	*.rpmsavez*.rpmnewz*~z
/*.augsavez/*~z/*/*augsavez/*/*~z/*/*/*.augsavez/*/*/*~r?   z/augeas/load/Httpd/excl[%d]N)r   rt   r   r   r   )r(   ZexclrZ   Zexcludedr*   r*   r+   r     s    




zApacheParser.standardize_exclc             C   sh   |  j  d } t j j |  j d  } t j j |  rF | } | } n | } | } d | d | d | i S)zSet default location for directives.

        Locations are given as file_paths
        .. todo:: Make sure that files are included

        r   z
ports.confdefaultlistenr   )r   r   r   r   r   isfile)r(   r   Ztempr   r   r*   r*   r+   r!     s    	zApacheParser._set_locationsc             C   sm   d d d g } xH | D]@ } t  j j t  j j |  j |   r t  j j |  j |  Sq Wt j d   d S)z(Find the Apache Configuration Root file.zapache2.confz
httpd.confzconf/httpd.confz!Could not find configuration rootN)r   r   r   r   r   r   ZNoInstallationError)r(   locationr   r*   r*   r+   r     s
    $zApacheParser._find_config_root)r   r   )+__name__
__module____qualname____doc__r^   r_   r   r   r   r,   r8   r<   r>   r   r   rK   rL   r=   rP   r]   ro   rv   rr   r3   r|   r~   r   r&   rG   r   r   r   r   r   rX   r   r   r   r   r   r   r!   r   r*   r*   r*   r+   r      sL   ,		L5	 r   c             C   s#   d j  d d   t j |   D  S)a`  Returns case insensitive regex.

    Returns a sloppy, but necessary version of a case insensitive regex.
    Any string should be able to be submitted and the string is
    escaped and then made case insensitive.
    May be replaced by a more proper /i once augeas 1.0 is widely
    supported.

    :param str string: string to make case i regex

    rw   c             S   s@   g  |  ]6 } | j    r6 d  | j   | j   d n |  q S)r
   r   )isalphaupperr   )r   r@   r*   r*   r+   
<listcomp>,  s   	zcase_i.<locals>.<listcomp>)r   r^   escape)stringr*   r*   r+   r/      s    r/   c             C   s   d |  S)zTReturn augeas path for full filepath.

    :param str file_path: Full filepath

    z/files%sr*   )Z	file_pathr*   r*   r+   r2   0  s    r2   )r   r"   r   Zloggingr   r^   rd   r   rE   Zacme.magic_typingr   r   r   Zcertbotr   Z	getLoggerr   r0   objectr   r/   r2   r*   r*   r*   r+   <module>   s"      