U
    ÝÁ]9_  ã                   @   s`  d Z ddlmZ ddlZddlZddlZ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W n ek
r|   dZY nX dZddlmZmZmZ dd„ Zd	d
„ Zdd„ Zdd„ Zdd„ Zd8dd„Zd9d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(d)„ Z#d*d+„ Z$d,d-„ Z%d.d/„ Z&d0d1„ Z'd2d3„ Z(d4d5„ Z)d6d7„ Z*dS );z;Compat module to handle files security on Windows and Linuxé    )Úabsolute_importNTF)ÚListÚUnionÚTuplec                 C   s    t rt | |¡ n
t| |ƒ dS )a^  
    Apply a POSIX mode on given file_path:
        * for Linux, the POSIX mode will be directly applied using chmod,
        * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for
          Certbot context, and applied to the file using kernel calls.

    The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot,
    is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the
    method _generate_windows_flags().

    :param str file_path: Path of the file
    :param int mode: POSIX mode to apply
    N)Ú
POSIX_MODEÚosÚchmodÚ_apply_win_mode©Ú	file_pathÚmode© r   ú;/usr/lib/python3/dist-packages/certbot/compat/filesystem.pyr      s    r   c                 C   sV   t r:t | ¡}|r|jnd}|r&|jnd}t |||¡ n|rHt| |ƒ t||ƒ dS )aó  
    Copy ownership (user and optionally group on Linux) from the source to the
    destination, then apply given mode in compatible way for Linux and Windows.
    This replaces the os.chown command.
    :param str src: Path of the source file
    :param str dst: Path of the destination file
    :param int mode: Permission mode to apply on the destination file
    :param bool copy_user: Copy user if `True`
    :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows)
    éÿÿÿÿN)r   r   ÚstatÚst_uidÚst_gidÚchownÚ_copy_win_ownershipr   )ÚsrcÚdstr   Z	copy_userZ
copy_groupZstatsZuser_idZgroup_idr   r   r   Úcopy_ownership_and_apply_mode8   s    

r   c                 C   s$   t rt t | ¡j¡|kS t| |ƒS )a`  
    Check if the given mode matches the permissions of the given file.
    On Linux, will make a direct comparison, on Windows, mode will be compared against
    the security model.
    :param str file_path: Path of the file
    :param int mode: POSIX mode to test
    :rtype: bool
    :return: True if the POSIX mode matches the file permissions
    )r   r   ÚS_IMODEr   Úst_modeÚ_check_win_moder
   r   r   r   Ú
check_modeP   s    r   c                 C   s8   t rt | ¡jt ¡ kS t | tj¡}| ¡ }t	ƒ |kS )zÀ
    Check if given file is owned by current user.
    :param str file_path: File path to check
    :rtype: bool
    :return: True if given file is owned by current user, False otherwise.
    )
r   r   r   r   ÚgetuidÚwin32securityÚGetFileSecurityÚOWNER_SECURITY_INFORMATIONÚGetSecurityDescriptorOwnerÚ_get_current_user)r   ÚsecurityÚuserr   r   r   Úcheck_ownera   s
    r$   c                 C   s   t | ƒot| |ƒS )zü
    Check if given file has the given mode and is owned by current user.
    :param str file_path: File path to check
    :param int mode: POSIX mode to check
    :rtype: bool
    :return: True if file has correct mode and owner, False otherwise.
    )r$   r   r
   r   r   r   Úcheck_permissionst   s    	r%   éÿ  c           
   
   C   s:  t rt | ||¡S |tj@ r |tj@ r.tjntj}t 	¡ }|j
}tƒ }t||ƒ}| |d¡ | d|d¡ d}z†z$t | tjtjtj@ ||dd¡}W n\ tjk
rò }	 z<|	jtjkrÄttj|	jƒ‚|	jtjkrÞttj|	jƒ‚|	‚W 5 d}	~	X Y nX W 5 |r| ¡  X t | |tjA tjA ¡S t | |¡}t| |ƒ |S )az  
    Wrapper of original os.open function, that will ensure on Windows that given mode
    is correctly applied.
    :param str file_path: The file path to open
    :param int flags: Flags to apply on file while opened
    :param int mode: POSIX mode to apply on file when opened,
        Python defaults will be applied if ``None``
    :returns: the file descriptor to the opened file
    :rtype: int
    :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set,
            OSError(errno.EACCES) on Windows if the file already exists and is a directory, and
                os.O_CREAT is set.
    r   é   N) r   r   ÚopenÚO_CREATÚO_EXCLÚwin32conZ
CREATE_NEWZCREATE_ALWAYSr   ÚSECURITY_ATTRIBUTESÚSECURITY_DESCRIPTORr!   Ú_generate_daclÚSetSecurityDescriptorOwnerÚSetSecurityDescriptorDaclZCloseÚ	win32fileZ
CreateFileZGENERIC_READZFILE_SHARE_READZFILE_SHARE_WRITEÚ
pywintypesÚerrorÚwinerrorZERROR_FILE_EXISTSÚOSErrorÚerrnoÚEEXISTÚstrerrorZERROR_SHARING_VIOLATIONZEACCESr   )
r   Úflagsr   ZdispositionÚ
attributesr"   r#   ÚdaclZhandleÚerrr   r   r   r(   €   s>    


   þ

r(   c                 C   s:   t rt | |¡S tj}ztt_t | |¡W ¢S |t_X dS )a3  
    Rewrite of original os.makedirs function, that will ensure on Windows that given mode
    is correctly applied.
    :param str file_path: The file path to open
    :param int mode: POSIX mode to apply on leaf directory when created, Python defaults
                     will be applied if ``None``
    N)r   r   ÚmakedirsÚmkdir)r   r   Zorig_mkdir_fnr   r   r   r=   Å   s    	r=   c              
   C   s¦   t rt | |¡S t ¡ }|j}tƒ }t||ƒ}| |d¡ | 	d|d¡ zt
 | |¡ W nH tjk
r  } z(|jtjkrŒttj|j| |jƒ‚|‚W 5 d}~X Y nX dS )a+  
    Rewrite of original os.mkdir function, that will ensure on Windows that given mode
    is correctly applied.
    :param str file_path: The file path to open
    :param int mode: POSIX mode to apply on directory when created, Python defaults
                     will be applied if ``None``
    Fr'   r   N)r   r   r>   r   r,   r-   r!   r.   r/   r0   r1   ZCreateDirectoryr2   r3   r4   ZERROR_ALREADY_EXISTSr5   r6   r7   r8   )r   r   r:   r"   r#   r;   r<   r   r   r   r>   Û   s    	
r>   c                 C   s,   t tdƒrttdƒ| |ƒ nt | |¡ dS )z´
    Rename a file to a destination path and handles situations where the destination exists.
    :param str src: The current file path.
    :param str dst: The new file path.
    ÚreplaceN)Úhasattrr   ÚgetattrÚrename)r   r   r   r   r   r?   ú   s    
r?   c                 C   s    | }t r2tj | ¡}tj |¡r.td |¡ƒ‚|S g }tj | ¡r”| }t | ¡} tj | ¡srtj 	tj 
|¡| ¡} | |krˆtd |¡ƒ‚| | ¡ q6tj | ¡S )z²
    Find the real path for the given path. This method resolves symlinks, including
    recursive symlinks, and is protected against symlinks that creates an infinite loop.
    zError, link {0} is a loop!)r   r   ÚpathÚrealpathÚislinkÚRuntimeErrorÚformatÚreadlinkÚisabsÚjoinÚdirnameÚappendÚabspath)r   Zoriginal_pathrC   Zinspected_pathsZ	link_pathr   r   r   rD   
  s     
rD   c                 C   s&   t rtj | ¡ot | tj¡S t| ƒS )zˆ
    Is path an executable file?
    :param str path: path to test
    :return: True if path is an executable file
    :rtype: bool
    )r   r   rC   ÚisfileÚaccessÚX_OKÚ_win_is_executable)rC   r   r   r   Úis_executable/  s    rR   c                 C   sV   t r tt t | ¡j¡tj@ ƒS t | tj	¡}| 
¡ }t| tjtjt d¡dœ¡ƒS )zÐ
    Check if everybody/world has any right (read/write/execute) on a file given its path
    :param str path: path to test
    :return: True if everybody/world has any right to the file
    :rtype: bool
    úS-1-1-0©ZTrusteeFormZTrusteeTypeZ
Identifier)r   Úboolr   r   r   r   ÚS_IRWXOr   r   ÚDACL_SECURITY_INFORMATIONÚGetSecurityDescriptorDaclÚGetEffectiveRightsFromAclÚTRUSTEE_IS_SIDÚTRUSTEE_IS_USERÚConvertStringSidToSid)rC   r"   r;   r   r   r   Úhas_world_permissions=  s    ýr]   c                 C   s:   t r6t t | ¡j¡tjtjB tjB tjB @ }||B S |S )a  
    Calculate the POSIX mode to apply to a private key given the previous private key
    :param str old_key: path to the previous private key
    :param int base_mode: the minimum modes to apply to a private key
    :return: the POSIX mode to apply
    :rtype: int
    )	r   r   r   r   r   ÚS_IRGRPÚS_IWGRPÚS_IXGRPÚS_IROTH)Zold_keyZ	base_modeZold_moder   r   r   Úcompute_private_key_modeR  s    	ÿrb   c                 C   sd   t r0t | ¡}t |¡}|j|jf|j|jfkS t | tj¡}| ¡ }t |tj¡}| ¡ }||kS )ar  
    Return True if the ownership of two files given their respective path is the same.
    On Windows, ownership is checked against owner only, since files do not have a group owner.
    :param str path1: path to the first file
    :param str path2: path to the second file
    :return: True if both files have the same ownership, False otherwise
    :rtype: bool

    )	r   r   r   r   r   r   r   r   r    )Zpath1Zpath2Zstats1Zstats2Z	security1Zuser1Z	security2Zuser2r   r   r   Úhas_same_ownershipg  s    

rc   c                 C   sª   t rt | ¡j}|||B kS t| ƒ} t | tjtjB ¡}| 	¡ }| 
¡ }t||ƒ}t| ¡ ƒD ]F}| |¡}|d }	|d }| tjtj|dœ¡}
|
|
|	B kr^ dS q^dS )a“  
    Check if a file given its path has at least the permissions defined by the given minimal mode.
    On Windows, group permissions are ignored since files do not have a group owner.
    :param str path: path to the file to check
    :param int min_mode: the minimal permissions expected
    :return: True if the file matches the minimal permissions expectations, False otherwise
    :rtype: bool
    r'   é   rT   FT)r   r   r   r   rD   r   r   r   rW   r    rX   r.   ÚrangeÚGetAceCountÚGetAcerY   rZ   r[   )rC   Zmin_moder   r"   r#   r;   Zmin_daclÚindexZmin_aceÚmaskZeffective_maskr   r   r   Úhas_min_permissions€  s.    
 
ÿ

ýrj   c                 C   sN   t j | ¡sdS t | tj¡}| ¡ }| tjtj	t
ƒ dœ¡}|tj@ tjkS )NFrT   )r   rC   rN   r   r   rW   rX   rY   rZ   r[   r!   ÚntsecurityconÚFILE_GENERIC_EXECUTE)rC   r"   r;   r   r   r   r   rQ   ­  s    ýrQ   c                 C   sJ   t | ƒ} t | tj¡}| ¡ }t||ƒ}| d|d¡ t | tj|¡ dS )zà
    This function converts the given POSIX mode into a Windows ACL list, and applies it to the
    file given its path. If the given path is a symbolic link, it will resolved to apply the
    mode on the targeted file.
    r'   r   N)	rD   r   r   r   r    r.   r0   ÚSetFileSecurityrW   )r   r   r"   r#   r;   r   r   r   r	   ½  s    
r	   c           
      C   s®   t |ƒ}t d¡}t d¡}t d¡}t ¡ }| ||fkrZt|d ƒ}|rZ| tj|| ¡ t|d ƒ}|rz| tj||¡ tddddœƒ}	| tj|	|¡ | tj|	|¡ |S )NzS-1-5-18zS-1-5-32-544rS   r#   ÚallT©ÚreadÚwriteÚexecute)Ú_analyze_moder   r\   ZACLÚ_generate_windows_flagsZAddAccessAllowedAceZACL_REVISION)
Zuser_sidr   ZanalysisÚsystemZadminsZeveryoner;   Z
user_flagsZeverybody_flagsZfull_permissionsr   r   r   r.   Ð  s     


r.   c                 C   s>   | t j@ | t j@ | t j@ dœ| t j@ | t j@ | t j@ dœdœS )Nro   )r#   rn   )r   ÚS_IRUSRÚS_IWUSRÚS_IXUSRra   ÚS_IWOTHÚS_IXOTH)r   r   r   r   rs   ò  s    ýýúrs   c                 C   sD   t  | t j¡}| ¡ }t  |t j¡}| |d¡ t  |t j|¡ d S ©NF)r   r   r   r    r/   rm   )r   r   Zsecurity_srcZuser_srcZsecurity_dstr   r   r   r     s
    r   c                 C   sJ   d}| d r|t jB }| d r4|t jt jA t jA B }| d rF|t jB }|S )Nr   rp   rq   rr   )rk   ZFILE_GENERIC_READZFILE_ALL_ACCESSrl   )Zrights_descÚflagr   r   r   rt     s    
ÿþ
rt   c                 C   sH   t | ƒ} t | tjtjB ¡}| ¡ }| ¡ }|s4dS t||ƒ}t||ƒS r{   )	rD   r   r   r   rW   rX   r    r.   Ú_compare_dacls)r   r   r"   r;   r#   Zref_daclr   r   r   r   ,  s    
ÿ
r   c                    s4   ‡ fdd„t ˆ  ¡ ƒD ƒ‡fdd„t ˆ ¡ ƒD ƒkS )z¥
    This method compare the two given DACLs to check if they are identical.
    Identical means here that they contains the same set of ACEs in the same order.
    c                    s   g | ]}ˆ   |¡‘qS r   ©rg   ©Ú.0rh   )Údacl1r   r   Ú
<listcomp>G  s     z"_compare_dacls.<locals>.<listcomp>c                    s   g | ]}ˆ   |¡‘qS r   r~   r   )Údacl2r   r   r‚   H  s     )re   rf   ©r   rƒ   r   r„   r   r}   B  s    ÿr}   c                  C   s   t  t j¡} t d| ¡d S )z=
    Return the pySID corresponding to the current user.
    Nr   )Úwin32apiZGetUserNameExZNameSamCompatibler   ZLookupAccountName)Zaccount_namer   r   r   r!   K  s    r!   )r&   )r&   )r&   )+Ú__doc__Z
__future__r   r6   r   r   rk   r   r+   r…   r1   r2   r4   ÚImportErrorr   Zacme.magic_typingr   r   r   r   r   r   r$   r%   r(   r=   r>   r?   rD   rR   r]   rb   rc   rj   rQ   r	   r.   rs   r   rt   r   r}   r!   r   r   r   r   Ú<module>   sP   

E

%-"	