U
    Ó‡gú  ã                   @   st   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mZ ddlm	Z	m
Z
 ddlmZ e e¡ZdZG dd„ dƒZdS )	z0gpg.py - Collection of gpg key related functionsé    N)ÚTemporaryDirectory)ÚDictÚOptional)ÚsubpZ	GNUPGHOMEc                   @   sÌ   e Zd Zdd„ Zdd„ Zeeeef dœdd„ƒZdd	„ Z	d
dœdd„Z
eee dœdd„Zeedœdd„Zd"eedœdd„Zd#eed
dœdd„Zed
dœdd„Zd$eeee dœdd„Zd
dœd d!„Zd
S )%ÚGPGc                 C   s   d| _ i | _tƒ | _d S )NF)Úgpg_startedÚ_envr   Útemp_dir©Úself© r   ú//usr/lib/python3/dist-packages/cloudinit/gpg.pyÚ__init__   s    zGPG.__init__c                 C   s   | S ©Nr   r
   r   r   r   Ú	__enter__   s    zGPG.__enter__)Úreturnc                 C   s&   | j r| j S d| _t| jji| _ | j S )a  when this env property gets invoked, set up our temporary
        directory, and also set gpg_started to tell the cleanup()
        method whether or not

        why put this here and not in __init__? pytest seems unhappy
        and it's not obvious how to work around it
        T)r   r   ÚHOMEr	   Únamer
   r   r   r   Úenv"   s
    	zGPG.envc                 C   s   |   ¡  d S r   )Úcleanup)r   Zexc_typÚ	exc_valueÚ	tracebackr   r   r   Ú__exit__1   s    zGPG.__exit__Nc                 C   s,   |   ¡  | jr(tj | jj¡r(| j ¡  dS )z0cleanup the gpg temporary directory and kill gpgN)Úkill_gpgr	   ÚosÚpathÚisdirr   r   r
   r   r   r   r   4   s    zGPG.cleanup)Úkeyr   c              
   C   sV   zt j ddd|gd| jd�jW S  t jk
rP } zt d||¡ W 5 d}~X Y nX dS )z*Export gpg key, armoured key gets returnedÚgpgz--exportz--armourT©ÚcaptureÚ
update_envú&Failed to export armoured key "%s": %sN)r   r   ÚstdoutÚProcessExecutionErrorÚLOGÚdebug©r   r   Úerrorr   r   r   Úexport_armour:   s    
ý
 zGPG.export_armourc                 C   s   t j ddg|d| jd�jS )z†Dearmor gpg key, dearmored key gets returned

        note: man gpg(1) makes no mention of an --armour spelling, only --armor
        r   z	--dearmorF)ÚdataÚdecoder!   )r   r   r#   )r   r   r   r   r   ÚdearmorG   s       ÿzGPG.dearmorF)Úkey_filer   c                 C   sT   ddddddg}|s|  d¡ |  |¡ tj|| jdd	�\}}|rPt d
||¡ |S )zòList keys from a keyring with fingerprints. Default to a
        stable machine parseable format.

        @param key_file: a string containing a filepath to a key
        @param human_output: return output intended for human parsing
        r   z--no-optionsz--with-fingerprintz--no-default-keyringz--list-keysz	--keyringz--with-colonsT)r!   r    r"   )Úappendr   r   r%   Úwarning)r   r-   Zhuman_outputÚcmdr#   Ústderrr   r   r   Ú	list_keysP   s$    ú

  ÿzGPG.list_keys©é   r4   )r   Ú	keyserverr   c           	   
   C   sð   t  d||¡ d}d}t|pg ƒ}|d7 }z6tjddd| d|gd	| jd
� t  d|||¡ W dS  tjk
rˆ } z|}W 5 d}~X Y nX z&t|ƒ}t  d|j|¡ t 	|¡ W q" t
k
rè } ztd||||f ƒ|‚W 5 d}~X Y q"X q"dS )aÙ  Receive gpg key from the specified keyserver.

        Retries are done by default because keyservers can be unreliable.
        Additionally, there is no way to determine the difference between
        a non-existent key and a failure.  In both cases gpg (at least 2.2.4)
        exits with status 2 and stderr: "keyserver receive failed: No data"
        It is assumed that a key provided to cloud-init exists on the keyserver
        so re-trying makes better sense than failing.

        @param key: a string key fingerprint (as passed to gpg --recv-keys).
        @param keyserver: the keyserver to request keys from.
        @param retries: an iterable of sleep lengths for retries.
        Use None to indicate no retries.z&Importing key '%s' from keyserver '%s'r   Nr4   r   z--no-ttyz--keyserver=%sz--recv-keysTr   z/Imported key '%s' from keyserver '%s' on try %dz6Import failed with exit code %d, will try again in %ssz@Failed to import key '%s' from keyserver '%s' after %d tries: %s)r%   r&   Úiterr   r   r$   ÚnextZ	exit_codeÚtimeÚsleepÚStopIterationÚ
ValueError)	r   r   r5   ZretriesZtrynumr(   ZsleepsÚeZnaplenr   r   r   Úrecv_keyj   sR    û÷üý
ÿÿýzGPG.recv_keyc              
   C   sX   z t j dddd|gd| jd� W n2 t jk
rR } zt d||¡ W 5 d}~X Y nX dS )	z0Delete the specified key from the local gpg ringr   z--batchz--yesz--delete-keysTr   zFailed delete key "%s": %sN)r   r   r$   r%   r/   r'   r   r   r   Ú
delete_key¡   s    ý
zGPG.delete_keyúkeyserver.ubuntu.com)Úkeyidr5   r   c              	   C   sd   |   |¡}|s`zDz| j||d� |   |¡}W n" tk
rN   t d|¡ ‚ Y nX W 5 |  |¡ X |S )zget gpg keyid from keyserver)r5   zFailed to obtain gpg key %s)r)   r>   r=   r;   r%   Z	exception)r   r@   r5   Zarmourr   r   r   Ú
getkeybyid¬   s    
zGPG.getkeybyidc              
   C   sÖ   z | j sW dS t d¡r4tjdddgd| jd�j}njtjddd	d
dd
dd
dg	dddgd�j}t d|¡}dd„ |D ƒ}|r†t d|¡ |D ]}t	 
|tj¡ qŠW n0 tjk
rÐ } zt d|¡ W 5 d}~X Y nX dS )a  killing with gpgconf is best practice, but when it isn't available
        failover is possible

        GH: 4344 - stop gpg-agent/dirmgr daemons spawned by gpg
        key imports. Daemons spawned by cloud-config.service on systemd
        v253 report (running)
        NZgpgconfz--killÚallTr   Zpsz-ozppid,pidz-CZkeyboxdZdirmngrz	gpg-agentr   r4   )r    Zrcsz(?P<ppid>\d+)\s+(?P<pid>\d+)c                 S   s$   g | ]}|d  dkrt |d ƒ‘qS )r   Ú1r4   )Úint)Ú.0Úpidr   r   r   Ú
<listcomp>â   s     z GPG.kill_gpg.<locals>.<listcomp>z&Killing gpg-agent and dirmngr pids: %sz"Failed to clean up gpg process: %s)r   r   Zwhichr   r#   ÚreÚfindallr%   r&   r   ÚkillÚsignalÚSIGKILLr$   r/   )r   Zgpg_process_outZgpg_pidsZroot_gpg_pidsZgpg_pidr<   r   r   r   r   ¾   sN    
ý
÷ó ÿÿ ÿzGPG.kill_gpg)F)r3   )r?   )Ú__name__Ú
__module__Ú__qualname__r   r   Úpropertyr   Ústrr   r   r   r   r)   r,   r2   r=   r>   rA   r   r   r   r   r   r      s$   	7 ÿ þr   )Ú__doc__Zloggingr   rH   rK   r8   Ztempfiler   Útypingr   r   Z	cloudinitr   Z	getLoggerrM   r%   r   r   r   r   r   r   Ú<module>   s   
