U
    ]s                     @   sz  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m	Z	 ddl
mZ ddlmZ ddlmZ ddlmZmZmZmZmZmZ eeZG dd	 d	eZd
d Z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Z%e&dd e%d!d"gZ'd#Z(d$d%e(gZ)d&d' Z*d(d) Z+d=d*d+Z,d,d- Z-d.d/ Z.d0d1 Z/d2d3 Z0d4d5 Z1d6d7 Z2d8d9 Z3d:d; Z4dS )>z>NginxParser is a member object of the NginxConfigurator class.    N)errors)os)obj)nginxparser)UnionDictSetAnyListTuplec                   @   s   e Zd Z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 Zd/ddZdd Zdd  Zd0d!d"Zd1d#d$Zd2d&d'Zd(d) Zd*d+ Zd3d,d-Zd%S )4NginxParserzClass handles the fine details of parsing the Nginx Configuration.

    :ivar str root: Normalized absolute path to the server root
        directory. Without trailing slash.
    :ivar dict parsed: Mapping of file paths to parsed trees

    c                 C   s*   i | _ tj|| _|  | _|   d S N)parsedr   pathabspathroot_find_config_rootconfig_rootload)selfr    r   6/usr/lib/python3/dist-packages/certbot_nginx/parser.py__init__   s    
zNginxParser.__init__c                 C   s   i | _ | | j dS )z/Loads Nginx files into a parsed tree.

        N)r   _parse_recursivelyr   )r   r   r   r   r   (   s    zNginxParser.loadc                 C   s   |  |}| |}|D ]}|D ]}t|r<| |d  q |d dgksX|d dgkr |d D ]\}t|r|| |d  q`|d dgkr`|d dgkr`|d D ]}t|r| |d  qq`q qdS )a  Parses nginx config files recursively by looking at 'include'
        directives inside 'http' and 'server' blocks. Note that this only
        reads Nginx files that potentially declare a virtual host.

        :param str filepath: The path to the files to parse, as a glob

           r   ZhttpserverN)abs_path_parse_files_is_include_directiver   )r   filepathtreestreeentryZsubentryZserver_entryr   r   r   r   /   s    	

zNginxParser._parse_recursivelyc                 C   s0   t j|s$t jt j| j|S t j|S )zConverts a relative path to an absolute path relative to the root.
        Does nothing for paths that are already absolute.

        :param str path: The path
        :returns: The absolute path
        :rtype: str

        )r   r   isabsnormpathjoinr   )r   r   r   r   r   r   K   s    	zNginxParser.abs_pathc           	      C   sn   |   }i }|D ]X}|| D ]J\}}t|}|d D ]0}| }||krR|j||< |jp^|| ||< q4qq|S )zSBuilds a map from address to whether it listens on ssl in any server block
        addrs)_get_raw_servers_parse_server_rawnormalized_tuplessl)	r   serversaddr_to_sslfilenamer   _parsed_serveraddrZ
addr_tupler   r   r   _build_addr_to_sslX   s    
zNginxParser._build_addr_to_sslc                    sz   i }| j D ]j}| j | }g ||< ||  t|dd  fdd t|| D ]&\}\}}| |}||f|| |< qLq
|S )z0Get a map of unparsed all server blocks
        c                 S   s   t | dko| d dgkS )N   r   r   len)xr   r   r   <lambda>u       z.NginxParser._get_raw_servers.<locals>.<lambda>c                    s     | d |fS )Nr   )append)r5   yZsrvr   r   r6   v   r7   )r   _do_for_subarray	enumerate_get_included_directives)r   r+   r-   r!   ir   r   
new_serverr   r:   r   r'   i   s    




zNginxParser._get_raw_serversc           	      C   sn   d}|   }g }|D ]J}|| D ]<\}}t|}t||d |d ||d ||}|| q q| | |S )a=  Gets list of all 'virtual hosts' found in Nginx configuration.
        Technically this is a misnomer because Nginx does not have virtual
        hosts, it has 'server blocks'.

        :returns: List of :class:`~certbot_nginx.obj.VirtualHost`
            objects found in configuration
        :rtype: list

        Tr&   r*   names)r'   r(   r   ZVirtualHostr8   _update_vhosts_addrs_ssl)	r   Zenabledr+   vhostsr-   r   r   r/   vhostr   r   r   
get_vhosts~   s"    
zNginxParser.get_vhostsc                 C   s<   |   }|D ]*}|jD ]}||  |_|jrd|_qqdS )zPUpdate a list of raw parsed vhosts to include global address sslishness
        TN)r1   r&   r)   r*   )r   rB   r,   rC   r0   r   r   r   rA      s    
z$NginxParser._update_vhosts_addrs_sslc              
   C   sh   t |}|D ]T}t|rt| |d }|D ].}z|| j|  W q2 tk
r^   Y q2X q2q|S )zReturns array with the "include" directives expanded out by
        concatenating the contents of the included file to the block.

        :param list block:
        :rtype: list

        r   )copydeepcopyr   globr   extendr   KeyError)r   blockresult	directiveZincluded_filesZinclr   r   r   r=      s    

z$NginxParser._get_included_directivesFc           	      C   s   t  |}g }|D ]}|| jkr&|s&qz6t|$}t|}|| j|< || W 5 Q R X W q tk
r|   td| Y q t	j
k
r } ztd|| W 5 d}~X Y qX q|S )zParse files from a glob

        :param str filepath: Nginx config file path
        :param bool override: Whether to parse a file that has been parsed
        :returns: list of parsed tree structures
        :rtype: list

        zCould not open file: %s"Could not parse file: %s due to %sN)rG   r   openr   r   r8   IOErrorloggerwarning	pyparsingZParseExceptiondebug)	r   r   overridefilesr    item_filer   errr   r   r   r      s    	



"zNginxParser._parse_filesc                 C   sJ   dg}|D ]0}t jt j| j|r
t j| j|  S q
tddS )z)Return the Nginx Configuration Root file.z
nginx.confz9Could not find Nginx root configuration file (nginx.conf)N)r   r   isfiler%   r   r   ZNoInstallationError)r   locationnamer   r   r   r      s    zNginxParser._find_config_roottmpTc              
   C   s   | j D ]}| j | }|r(|tjj | }zL|r:| s:W qt|}td|| t	|d}|
| W 5 Q R X W q tk
r   td| Y qX qdS )zDumps parsed configurations into files.

        :param str ext: The file extension to use for the dumped files. If
            empty, this overrides the existing conf files.
        :param bool lazy: Only write files that have been modified

        z!Writing nginx conf tree to %s:
%swz#Could not open file for writing: %sN)r   r   r   extsepZis_dirtyr   dumpsrP   rS   rN   writerO   error)r   ZextZlazyr-   r!   outrW   r   r   r   filedump   s    	


zNginxParser.filedumpc                 C   s   |   }t|}t|| |S )zParses a list of server directives, accounting for global address sslishness.

        :param list server: list of directives in a server block
        :rtype: dict
        )r1   r(   _apply_global_addr_ssl)r   r   r,   r/   r   r   r   parse_server   s    
zNginxParser.parse_serverc                 C   s*   |j }|D ]}|sq
q
t|r
 dS q
dS )zDoes vhost have ssl on for all ports?

        :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost in question

        :returns: True if 'ssl on' directive is included
        :rtype: bool

        TF)raw_is_ssl_on_directive)r   rC   r   rL   r   r   r   has_ssl_on_directive  s    	z NginxParser.has_ssl_on_directivec                 C   s   |  |tt|| dS )a  Add directives to the server block identified by vhost.

        This method modifies vhost to be fully consistent with the new directives.

        ..note :: It's an error to try and add a nonrepeatable directive that already
            exists in the config block with a conflicting value.

        ..todo :: Doesn't match server blocks whose server_name directives are
            split across multiple conf files.

        :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
            whose information we use to match on
        :param list directives: The directives to add
        :param bool insert_at_top: True if the directives need to be inserted at the top
            of the server block instead of the bottom

        N)_modify_server_directives	functoolspartial_add_directivesr   rC   
directivesinsert_at_topr   r   r   add_server_directives  s    z!NginxParser.add_server_directivesc                 C   s   |  |tt|| dS )aR  Add or replace directives in the server block identified by vhost.

        This method modifies vhost to be fully consistent with the new directives.

        ..note :: When a directive with the same name already exists in the
        config block, the first instance will be replaced. Otherwise, the directive
        will be appended/prepended to the config block as in add_server_directives.

        ..todo :: Doesn't match server blocks whose server_name directives are
            split across multiple conf files.

        :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
            whose information we use to match on
        :param list directives: The directives to add
        :param bool insert_at_top: True if the directives need to be inserted at the top
            of the server block instead of the bottom

        N)ri   rj   rk   _update_or_add_directivesrm   r   r   r   update_or_add_server_directives-  s    z+NginxParser.update_or_add_server_directivesNc                 C   s   |  |tt|| dS )ab  Remove all directives of type directive_name.

        :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
            to remove directives from
        :param string directive_name: The directive type to remove
        :param callable match_func: Function of the directive that returns true for directives
            to be deleted.
        N)ri   rj   rk   _remove_directives)r   rC   directive_name
match_funcr   r   r   remove_server_directivesC  s    	z$NginxParser.remove_server_directivesc                 C   s<   |  |}| |}|d |_|d |_|d |_||_d S )Nr&   r*   r@   )r=   re   r&   r*   r@   rf   )r   rC   Zdirectives_listr?   r/   r   r   r   %_update_vhost_based_on_new_directivesO  s    




z1NginxParser._update_vhost_based_on_new_directivesc              
   C   s   |j }z^| j| }|jD ]}|| }qt|tr<t|dkrFtd|d }|| | || W n: tjk
r } ztd|t	|f W 5 d }~X Y nX d S )Nr2   zNot a server block.r   zProblem in %s: %s)
filepr   r   
isinstancelistr4   r   MisconfigurationErrorrw   str)r   rC   Z
block_funcr-   rK   indexrX   r   r   r   ri   W  s    



z%NginxParser._modify_server_directivesc                 C   s2  t |}| j|j }|jdd D ]}|| }q$t ||jd  }|dk	rtg }|d D ]}	|	r`|	d |kr`||	 q`||d< | || || t	|d |jd< |r.|j
D ]}
d|
_d|
_q||jd  d D ]L}	|	r|	d dkrtd}|D ]*}dd	 |	D }||kr |	||= q q|S )
ar  Duplicate the vhost in the configuration files.

        :param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost
            whose information we copy
        :param bool remove_singleton_listen_params: If we should remove parameters
            from listen directives in the block that can only be used once per address
        :param list only_directives: If it exists, only duplicate the named directives. Only
            looks at first level of depth; does not expand includes.

        :returns: A vhost object for the newly created vhost
        :rtype: :class:`~certbot_nginx.obj.VirtualHost`
        Nr   r   Flisten)Zdefault_serverdefaultZsetfibZfastopenZbacklogZrcvbufZsndbufZaccept_filterZdeferredZbindipv6onlyZ	reuseportZso_keepalivec                 S   s   g | ]}| d d qS )=r   )split.0r5   r   r   r   
<listcomp>  s     z/NginxParser.duplicate_vhost.<locals>.<listcomp>)rE   rF   r   rx   r   r   UnspacedListr8   rw   r4   r&   r   r   setr}   )r   Zvhost_templateZremove_singleton_listen_paramsZonly_directivesZ	new_vhostZenclosing_blockr}   Zraw_in_parsedZnew_directivesrL   r0   ZexcludeZparamkeysr   r   r   duplicate_vhostf  s4    





zNginxParser.duplicate_vhost)F)r\   T)F)F)N)FN)__name__
__module____qualname____doc__r   r   r   r   r1   r'   rD   rA   r=   r   r   rc   re   rh   rp   rr   rv   rw   ri   r   r   r   r   r   r      s.   
!





  r   c              
   C   s   | d k	rz,t | }t|W  5 Q R  W S Q R X W nP tk
rT   td|  Y n2 tjk
r } ztd| | W 5 d }~X Y nX g S )Nz"Missing NGINX TLS options file: %srM   )	rN   r   r   rO   rP   rQ   rR   ZParseBaseExceptionrS   )Zssl_optionsrW   rX   r   r   r   _parse_ssl_options  s    
" r   c                 C   sT   |dkrg }t | trP|| r*|| | n&t| D ]\}}t|||||g  q2dS )a(  Executes a function for a subarray of a nested array if it matches
    the given condition.

    :param list entry: The list to iterate over
    :param function condition: Returns true iff func should be executed on item
    :param function func: The function to call for each matching item

    N)ry   rz   r<   r;   )r"   Z	conditionfuncr   r}   rV   r   r   r   r;     s    	
r;   c                 C   s   g }g }g }g }|D ]^}t | |r.|| qt| |drF|| qt| |dr^|| qt| |r|| q|rt|td}d|fS |rt|td}d|fS |rt|td}d|fS |r|d }d|fS d	S )
ah  Finds the best match for target_name out of names using the Nginx
    name-matching rules (exact > longest wildcard starting with * >
    longest wildcard ending with * > regex).

    :param str target_name: The name to match
    :param set names: The candidate server names
    :returns: Tuple of (type of match, the name that matched)
    :rtype: tuple

    TF)keyexactwildcard_startwildcard_endr   regex)NN)_exact_matchr8   _wildcard_match_regex_matchminr4   max)target_namer@   r   r   r   r   r[   matchr   r   r   get_best_match  s4    

r   c                 C   s   | |kpd|  |kS )N.r   )r   r[   r   r   r   r     s    r   c                 C   st   |dkrdS |  d}| d}|s4|  |  |d}|dkrR|dkrRdS d|} d|}| d| S )N*Tr   r    F)r   reversepopr%   endswith)r   r[   startpartsZmatch_partsfirstr   r   r   r     s    




r   c                 C   sZ   t |dk s|d dkrdS z t|dd  }t|| W S  tjk
rT   Y dS X d S )Nr2   r   ~Fr   )r4   recompiler   ra   )r   r[   r   r   r   r   r     s    r   c                 C   s2   t | to0t| dko0| d dko0t | d tjS )zChecks if an nginx parsed entry is an 'include' directive.

    :param list entry: the parsed entry
    :returns: Whether it's an 'include' directive
    :rtype: bool

    r2   r   includer   )ry   rz   r4   sixstring_typesr"   r   r   r   r     s    


r   c                 C   s.   t | to,t| dko,| d dko,| d dkS )zChecks if an nginx parsed entry is an 'ssl on' directive.

    :param list entry: the parsed entry
    :returns: Whether it's an 'ssl on' directive
    :rtype: bool

    r2   r   r*   r   Zon)ry   rz   r4   r   r   r   r   rg     s    



rg   c                 C   s:   | D ]}t ||| q|r6d|d kr6|td dS )z"Adds directives to a config block.
r~   N)_add_directiver8   r   r   rn   ro   rJ   rL   r   r   r   rl   +  s    rl   c                 C   s:   | D ]}t ||| q|r6d|d kr6|td dS )z.Adds or replaces directives in a config block.r   r~   N)_update_or_add_directiver8   r   r   r   r   r   r   rq   2  s    rq   r   server_namer   ZrewriteZ
add_headerz managed by Certbot #c                 C   s   |d t | k r| |d  nd}t|trv|rvt |dkrV|d dkrVt|d krVdS t|tjrn|jd }n|d }| |d tdd  |dk	rd|kr| |d d dS )	zAdd a ``#managed by Certbot`` comment to the end of the line at location.

    :param list block: The block containing the directive to be commented
    :param int location: The location within ``block`` of the directive to be commented
    r   Nr2   r   r~   r   r   )	r4   ry   rz   COMMENTr   r   spacedinsertCOMMENT_BLOCK)rJ   rZ   Z
next_entryr   r   r   comment_directive?  s     $r   c           
      C   s   d |}| | }tg }|| t|}|d | }t|}d}	|d jd |d d krhd}	|d j|	d |d jd t|}t|}|d | |< dS )z=Comment out the line at location, with a note of explanation.z duplicated in {0}z #r   r   z# ;N)formatr   r   r8   r_   loadsr   r   )
rJ   rZ   Zinclude_locationZcomment_messagerL   Znew_dir_blockZdumpedZ	commentedZnew_dirZinsert_locationr   r   r   _comment_out_directiveR  s    






r   c                    s   t  fddt| D dS )zeFinds the index of the first instance of directive_name in block.
       If no line exists, use None.c                 3   s6   | ].\}}|r|d   krdks*|r|V  qdS )r   Nr   )r   r}   linert   ru   r   r   	<genexpr>l  s       z!_find_location.<locals>.<genexpr>N)nextr<   )rJ   rt   ru   r   r   r   _find_locationi  s    r   c                 C   s   t | dkp| d dkS )z;Is this directive either a whitespace or comment directive?r   r   r3   rL   r   r   r   _is_whitespace_or_commento  s    r   c                 C   sJ  t |tjst|}t|r,| | d S t| |d }|d }dd }d}|tkrt|d }|D ]`}t| |d }	|d }
t|sf||	|
sf| |	 |krt	|
|| |	 qft| |	|d  qf|||r"|r| dtd | d| t| d n| | t| t| d  n$| | |krFt	|
|| | d S )Nr   c                 S   s   | dkpt |tjo|tkS )z, Can we append this directive to the block? N)ry   r   r   REPEATABLE_DIRECTIVES)ZlocZdir_namer   r   r   
can_append  s    z"_add_directive.<locals>.can_appendz<tried to insert directive "{0}" but found conflicting "{1}".r   r   )ry   r   r   r   r8   r   INCLUDEr   r   r{   r   r   r   r   r4   )rJ   rL   ro   rZ   rt   r   Zerr_fmtZincluded_directivesZincluded_directiveZincluded_dir_locZincluded_dir_namer   r   r   r   s  s>    



r   c                 C   s   || |< t | | d S r   )r   )rJ   rL   rZ   r   r   r   _update_directive  s    r   c                 C   sb   t |tjst|}t|r,| | d S t| |d }|d k	rRt| || d S t| || d S )Nr   )ry   r   r   r   r8   r   r   r   )rJ   rL   ro   rZ   r   r   r   r     s    

r   c                 C   s   d| kot | kS )Nr   )r   r   r   r   r   _is_certbot_comment  s    r   c                 C   sP   t || |d}|dkrdS |d t|k rDt||d  rD||d = ||= q dS )zYRemoves directives of name directive_name from a config block if match_func matches.
    )ru   Nr   )r   r4   r   )rt   ru   rJ   rZ   r   r   r   rs     s     
rs   c                 C   s.   |d D ] }| |   |_|jrd|d< qdS )zCApply global sslishness information to the parsed server block
    r&   Tr*   N)r)   r*   )r,   r/   r0   r   r   r   rd     s    rd   c                 C   s   t  }d}t  }d}| D ]}|s"q|d dkrbtjd|dd }|r|| |jrd}q|d dkr|d	d
 |dd D  qt|rd}d}q|r|D ]
}d|_q|||dS )zxParses a list of server directives.

    :param list server: list of directives in a server block
    :rtype: dict

    Fr   r   r   r   NTr   c                 s   s   | ]}| d V  qdS )z"'N)stripr   r   r   r   r     s     z$_parse_server_raw.<locals>.<genexpr>)r&   r*   r@   )	r   r   ZAddrZ
fromstringr%   addr*   updaterg   )r   r&   r*   r@   Zapply_ssl_to_all_addrsrL   r0   r   r   r   r(     s2    
r(   )N)N)5r   rE   rj   rG   Zloggingr   rR   r   Zcertbotr   Zcertbot.compatr   Zcertbot_nginxr   r   Zacme.magic_typingr   r   r   r	   r
   r   Z	getLoggerr   rP   objectr   r   r;   r   r   r   r   r   rg   rl   rq   r   r   r   r   r   r   r   r   r   r   r   r   r   rs   rd   r(   r   r   r   r   <module>   sT    
   

.

7