o
    f                     @   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)subp	GNUPGHOMEc                   @   s   e Zd Zdd Zdd Zedeeef fddZdd	 Z	d"ddZ
dedee fddZdedefddZd#dedefddZd$dededd
fddZdedd
fddZ	d%dededee fddZ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   exc_typ	exc_value	tracebackr   r   r   __exit__1   s   zGPG.__exit__Nc                 C   s4   |    | jrtj| jjr| j  dS dS 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keyc              
   C   sV   zt j ddd|gd| jdjW S  t jy* } ztd|| W Y d}~dS d}~ww )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 )zDearmor 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_filec                 C   sL   g d}|s| d | | tj|| jdd\}}|r$td|| |S )zList 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/   human_outputcmdr%   stderrr   r   r   	list_keysP   s   

zGPG.list_keys   r7   	keyserverc           	   
   C   s   t d|| d}d}t|pg }	 |d7 }ztjddd| d	|gd| jd
 t d||| W dS  tjyF } z|}W Y d}~nd}~ww zt|}t d|j| t	| W n t
yq } ztd||||f |d}~ww q)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   NTr7   r    z--no-ttyz--keyserver=%sz--recv-keysr!   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&   next	exit_codetimesleepStopIteration
ValueError)	r   r   r8   retriestrynumr*   sleepsenaplenr   r   r   recv_keyj   s^   
zGPG.recv_keyc              
   C   sZ   zt j dddd|gd| jd W dS  t jy, } ztd|| W Y d}~dS d}~ww )	z0Delete the specified key from the local gpg ringr    z--batchz--yesz--delete-keysTr!   zFailed delete key "%s": %sN)r   r   r&   r'   r1   r)   r   r   r   
delete_key   s   zGPG.delete_keykeyserver.ubuntu.comkeyidc              	   C   sj   |  |}|s3z%z| j||d |  |}W n ty$   td|  w W | | |S | | w |S )zget gpg keyid from keyserver)r8   zFailed to obtain gpg key %s)r+   rE   r?   r'   	exceptionrF   )r   rH   r8   armourr   r   r   
getkeybyid   s   

zGPG.getkeybyidc              
   C   s   zK| j sW dS tdrtjg dd| jdj}W dS tjg ddddgd	j}td
|}dd |D }|r=td| |D ]	}t	
|tj q?W dS  tjye } ztd| W Y d}~dS d}~ww )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)
        Ngpgconf)rL   z--killallTr!   )	psz-ozppid,pid-CkeyboxdrO   dirmngrrO   z	gpg-agentr   r7   )r"   rcsz(?P<ppid>\d+)\s+(?P<pid>\d+)c                 S   s$   g | ]}|d  dkrt |d qS )r   1r7   )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   whichr   r%   refindallr'   r(   r   killsignalSIGKILLr&   r1   )r   gpg_process_outgpg_pidsroot_gpg_pidsgpg_pidrC   r   r   r   r      sF   
zGPG.kill_gpg)r   N)F)r6   )rG   )__name__
__module____qualname__r   r   propertyr   strr   r   r   r   r+   r.   r5   rE   rF   rK   r   r   r   r   r   r      s*    
	7
r   )__doc__loggingr   rY   r\   r<   tempfiler   typingr   r   	cloudinitr   	getLoggerrb   r'   r   r   r   r   r   r   <module>   s   
