o
    fX                     @   sn  d dl Z d dlZd dlZd dlmZ d dlmZmZmZ d dl	m
Z
mZmZ e eZdZdZdZdee d	 ZG d
d dZG dd dZdd Zdd Zdd Zdd Zdd Zdd ZefddZd7ddZG dd dZd ee fd!d"Z d ee fd#d$Z!d%d& Z"d'ed e#fd(d)Z$d*d+ Z%efd,d-Z&d.d/ Z'efd0eeeef  fd1d2Z(d3d4 Z)d5d6 Z*dS )8    N)suppress)ListSequenceTuple)	lifecyclesubputilz/etc/ssh/sshd_config)rsaecdsaed25519z(ecdsa-sha2-nistp256-cert-v01@openssh.comzecdsa-sha2-nistp256z(ecdsa-sha2-nistp384-cert-v01@openssh.comzecdsa-sha2-nistp384z(ecdsa-sha2-nistp521-cert-v01@openssh.comzecdsa-sha2-nistp521z+sk-ecdsa-sha2-nistp256-cert-v01@openssh.comz"sk-ecdsa-sha2-nistp256@openssh.comz#sk-ssh-ed25519-cert-v01@openssh.comzsk-ssh-ed25519@openssh.comz ssh-ed25519-cert-v01@openssh.comzssh-ed25519zssh-rsa-cert-v01@openssh.comzssh-rsazssh-xmss-cert-v01@openssh.comzssh-xmss@openssh.com   zno-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"$USER\" rather than the user \"$DISABLE_USER\".';echo;sleep 10;exit "c                   @   s(   e Zd Z	dddZdd Zdd ZdS )	AuthKeyLineNc                 C   s"   || _ || _|| _|| _|| _d S N)base64commentoptionskeytypesource)selfr   r   r   r   r    r   4/usr/lib/python3/dist-packages/cloudinit/ssh_util.py__init__E   s
   
zAuthKeyLine.__init__c                 C   s   | j o| jS r   )r   r   r   r   r   r   validN   s   zAuthKeyLine.validc                 C   s`   g }| j r|| j  | jr|| j | jr|| j | jr&|| j |s+| jS d|S N )r   appendr   r   r   r   join)r   toksr   r   r   __str__Q   s   
zAuthKeyLine.__str__)NNNN)__name__
__module____qualname__r   r   r    r   r   r   r   r   D   s
    
	r   c                   @   s"   e Zd ZdZdd ZdddZdS )AuthKeyLineParserau  
    AUTHORIZED_KEYS FILE FORMAT
     AuthorizedKeysFile specifies the file containing public keys for public
     key authentication; if none is specified, the default is
     ~/.ssh/authorized_keys.  Each line of the file contains one key (empty
     (because of the size of the public key encoding) up to a limit of 8 kilo-
     bytes, which permits DSA keys up to 8 kilobits and RSA keys up to 16
     kilobits.  You don't want to type them in; instead, copy the
     identity.pub or the id_rsa.pub file and edit it.

     sshd enforces a minimum RSA key modulus size for protocol 1 and protocol
     2 keys of 768 bits.

     The options (if present) consist of comma-separated option specifica-
     tions.  No spaces are permitted, except within double quotes.  The fol-
     lowing option specifications are supported (note that option keywords are
     case-insensitive):
    c                 C   s   d}d}|t |k rO|s|| dvrO|| }|d t |kr#|d }n,||d  }|dkr6|dkr6|d }n|dkr=| }|d }|t |k rO|s|| dvs|d| }||d  }||fS )z
        The options (if present) consist of comma-separated option specifica-
         tions.  No spaces are permitted, except within double quotes.
         Note that option keywords are case-insensitive.
        Fr   )r   	   \r   N)lenlstrip)r   entquotedicurcnextcr   remainr   r   r   _extract_optionsu   s"   
z"AuthKeyLineParser._extract_optionsNc                 C   s   | d}|ds| dkrt|S dd }| }z	||\}}}W n/ tyT   | |\}	}
|d u r9|	}z	||
\}}}W n tyQ   t| Y  Y S w Y nw t|||||dS )Nz
# c                 S   s^   |  d d}t|dk rtdt| |d tvr"td|d  t|dkr-|d |S )N   zTo few fields: %sr   zInvalid keytype %sr2   )splitr(   	TypeErrorVALID_KEY_TYPESr   )r*   r   r   r   r   parse_ssh_key   s   
z.AuthKeyLineParser.parse.<locals>.parse_ssh_key)r   r   r   r   )rstrip
startswithstripr   r5   r0   )r   src_liner   liner7   r*   r   r   r   keyoptsr/   r   r   r   parse   s2   

zAuthKeyLineParser.parser   )r!   r"   r#   __doc__r0   r>   r   r   r   r   r$   a   s    r$   c              
   C   sx   g }t  }g }| D ]0}ztj|r&t| }|D ]
}||| qW q	 t	t
fy9   ttd| Y q	w |S )NzError reading lines from %s)r$   ospathisfiler   load_text_file
splitlinesr   r>   IOErrorOSErrorlogexcLOG)fnameslinesparsercontentsfnamer<   r   r   r   parse_authorized_keys   s   rN   c                 C   s   t dd |D }tt| D ]%}| | }| sq|D ]}|j|jkr/|}||v r/|| q|| |< q|D ]}| | q7dd | D }|d d|S )Nc                 S   s   g | ]}|  r|qS r   )r   .0kr   r   r   
<listcomp>       z*update_authorized_keys.<locals>.<listcomp>c                 S      g | ]}t |qS r   str)rP   br   r   r   rR          r2   
)listranger(   r   r   remover   r   )old_entrieskeysto_addr,   r*   rQ   keyrJ   r   r   r   update_authorized_keys   s"   



ra   c                 C   s4   t | }|r
|jstd|  tj|jd|fS )Nz"Unable to get SSH info for user %rz.ssh)pwdgetpwnampw_dirRuntimeErrorr@   rA   r   )usernamepw_entr   r   r   users_ssh_info   s   

rh   c           	      C   sp   d|fd|fdf}| sd} |   }g }|D ] }|D ]
\}}|||}q|ds0tj||}|| q|S )N%h%u)z%%%%h/.ssh/authorized_keys/)r4   replacer9   r@   rA   r   r   )	valuehomedirrf   macrospathsrenderedrA   macrofieldr   r   r   render_authorizedkeysfile_paths   s   
rv   c           
      C   s   d}|rd}t |}|r || kr |dkr td||| | dS t |}|| kr.|dM }nt |}t | }	||	v rA|dM }n|dM }||@ d	krUtd
|||  dS |rf|d@ d	krftd|| dS dS )aV  Check if the file/folder in @current_path has the right permissions.

    We need to check that:
    1. If StrictMode is enabled, the owner is either root or the user
    2. the user can access the file/folder, otherwise ssh won't use it
    3. If StrictMode is enabled, no write permission is given to group
       and world users (022)
    i  i  rootzXPath %s in %s must be own by user %s or by root, but instead is own by %s. Ignoring key.F  8      r   zBPath %s in %s must be accessible by user %s, check its permissions   zRPath %s in %s must not give writepermission to group or world users. Ignoring key.T)r   	get_ownerrH   debugget_permissions	get_groupget_user_groups)
rf   current_path	full_pathis_filestrictmodesminimal_permissionsownerparent_permissiongroup_owneruser_groupsr   r   r   check_permissions  sJ   





r   c              
   C   s  t | d }t dd }z|ddd }d}tj|j}|D ]}|d| 7 }tj|r9td|  W dS tj	|rItd|  W dS |
|sS||jkrTq!tj|st|- d	}	|j}
|j}|
|jrvd
}	|j}
|j}tj||	dd t||
| W d    n1 sw   Y  t| ||d|}|s W dS q!tj|stj|rtd| W dS tj|stj|dddd t||j|j t| ||d|}|sW dS W dS  ttfy } zttt| W Y d }~dS d }~ww )Nr&   rw   rm   r2   z-Invalid directory. Symlink exists in path: %sFz*Invalid directory. File exists in path: %s  rx   T)modeexist_okz%s is not a file!  )r   ensure_dir_exists)rh   r4   r@   rA   dirnamerd   islinkrH   r}   rB   r9   existsr   SeLinuxGuardpw_uidpw_gidmakedirs	chownbyidr   isdir
write_filerE   rF   rG   rV   )rf   filenamer   
user_pwent
root_pwentdirectoriesparent_folderhome_folder	directoryr   uidgidpermissionser   r   r   check_create_pathG  sv   


r   c                 C   s0  t | \}}tj|d}|}g }tj|dd; zt|}|dd}|dd}	t||j	| }W n t
tfyK   ||d< ttd	t|d  Y nw W d    n1 sVw   Y  t| |D ]$\}
}td
|
v d|
v |d|j	grt| ||	dk}|r|} nqb||krtd| |t|gfS )Nauthorized_keysT	recursiveauthorizedkeysfilerl   r   yesr   zhFailed extracting 'AuthorizedKeysFile' in SSH config from %r, using 'AuthorizedKeysFile' file %r insteadrj   ri   z{}/zAAuthorizedKeysFile has an user-specific authorized_keys, using %s)rh   r@   rA   r   r   r   parse_ssh_config_mapgetrv   rd   rE   rF   rG   rH   DEF_SSHD_CFGzipr4   anyr9   formatr   r}   rN   )rf   sshd_cfg_filessh_dirrg   default_authorizedkeys_fileuser_authorizedkeys_fileauth_key_fnsssh_cfg	key_pathsr   key_pathauth_key_fnpermissions_okr   r   r   extract_authorized_keys  s^   
r   c           
      C   s   t  }g }| D ]}||jt||d qt|\}}tj|}tj	|dd t
||}	tj||	dd W d    d S 1 sBw   Y  d S )N)r   Tr   preserve_mode)r$   r   r>   rV   r   r@   rA   r   r   r   ra   r   )
r^   rf   r   rK   key_entriesrQ   r   auth_key_entriesr   contentr   r   r   setup_user_keys  s   
"r   c                   @   s*   e Zd ZdddZedd Zdd ZdS )	SshdConfigLineNc                 C   s   || _ || _|| _d S r   )r<   _keyro   )r   r<   rQ   vr   r   r   r     s   
zSshdConfigLine.__init__c                 C   s   | j d u rd S | j  S r   )r   lowerr   r   r   r   r`     s   

zSshdConfigLine.keyc                 C   s:   | j d u r
t| jS t| j }| jr|dt| j 7 }|S r   )r   rV   r<   ro   )r   r   r   r   r   r      s   


zSshdConfigLine.__str__)NN)r!   r"   r#   r   propertyr`   r    r   r   r   r   r     s
    

r   returnc                 C   s"   t j| sg S tt|  S r   )r@   rA   rB   parse_ssh_config_linesr   rC   rD   rM   r   r   r   parse_ssh_config  s   r   c                 C   s   g }| D ]M}|  }|r|dr|t| qz
|d d\}}W n$ tyG   z
|dd\}}W n tyD   td| Y Y qw Y nw |t||| q|S )Nr1   r&   =z;sshd_config: option "%s" has no key/value pair, skipping it)r:   r9   r   r   r4   
ValueErrorrH   r}   )rJ   retr<   r`   valr   r   r   r     s,   
r   c                 C   s6   t | }|si S i }|D ]}|jsq|j||j< q|S r   )r   r`   ro   )rM   rJ   r   r<   r   r   r   r     s   r   rM   c                 C   sn   t j| sdS t| d }|D ]}|d|  dr$ W d    dS qW d    dS 1 s0w   Y  dS )NFrzInclude z	.d/*.confT)r@   rA   rB   openr9   )rM   fr<   r   r   r   _includes_dconf"  s   
r   c                 C   s^   t | r-tj|  dstj|  ddd tj|  dd} tj| s-t| d | S )Nz.dr   )r   z50-cloud-init.confr   )	r   r@   rA   r   r   
ensure_dirr   rB   ensure_filer   r   r   r   "_ensure_cloud_init_ssh_config_file,  s   r   c                 C   sP   t |}t|}t|| d}|r"tj|ddd |D d dd t|dkS )zRead fname, and update if changes are necessary.

    @param updates: dictionary of desired values {Option: value}
    @return: boolean indicating if an update was done.)rJ   updatesrY   c                 S   rT   r   rU   )rP   r<   r   r   r   rR   B  rX   z%update_ssh_config.<locals>.<listcomp>Tr   r   )r   r   update_ssh_config_linesr   r   r   r(   )r   rM   rJ   changedr   r   r   update_ssh_config7  s   r   c           	      C   s  t  }g }tdd | D }t| ddD ];\}}|jsq|j|v rQ||j }|| }|| |j|kr?td||| q|	| td|||j| ||_qt
|t
|kr| D ]!\}}||v rgq^|	| | 	td|| tdt
| || q^|S )	zUpdate the SSH config lines per updates.

    @param lines: array of SshdConfigLine.  This array is updated in place.
    @param updates: dictionary of desired values {Option: value}
    @return: A list of keys in updates that were changed.c                 S   s   g | ]}|  |fqS r   )r   rO   r   r   r   rR   R  rS   z+update_ssh_config_lines.<locals>.<listcomp>r&   )startz$line %d: option %s already set to %sz#line %d: option %s updated %s -> %sr2   z line %d: option %s added with %s)setdictr^   	enumerater`   addro   rH   r}   r   r(   itemsr   )	rJ   r   foundr   casemapr,   r<   r`   ro   r   r   r   r   H  sD   





r   rJ   c                 C   s>   | sd S t |}dd | D }tj|d|d ddd d S )Nc                 s   s"    | ]\}}| d | V  qdS )r   Nr   )rP   rQ   r   r   r   r   	<genexpr>z  s     z$append_ssh_config.<locals>.<genexpr>rY   abT)omoder   )r   r   r   r   )rJ   rM   r   r   r   r   append_ssh_configv  s   
r   c                  C   s   d} t tj tjddgddgd\}} W d   n1 sw   Y  d}| d	D ]}||r?|t||d
   S q+dS )zGet the full version of the OpenSSH sshd daemon on the system.

    On an ubuntu system, this would look something like:
    1.2p1 Ubuntu-1ubuntu0.1

    If we can't find `sshd` or parse the version number, return None.
    r2   sshdz-Vr   r&   )rcsNOpenSSH_rY   ,)r   r   ProcessExecutionErrorr4   r9   r(   find)err_prefixr<   r   r   r   get_opensshd_version  s   

r   c               	   C   s   d} t  }|du rtj| S d|v r|d|d } nd|v r+|d|d } n|} z	tj| } | W S  ttfyH   td|  Y dS w )zGet the upstream version of the OpenSSH sshd daemon on the system.

    This will NOT include the portable number, so if the Ubuntu version looks
    like `1.2p1 Ubuntu-1ubuntu0.1`, then this function would return
    `1.2`
    z9.0Npr   z Could not parse sshd version: %s)	r   r   Versionfrom_strr   r   r5   rH   warning)upstream_versionfull_versionr   r   r   get_opensshd_upstream_version  s   r   r   )+loggingr@   rb   
contextlibr   typingr   r   r   	cloudinitr   r   r   	getLoggerr!   rH   r   r6   _DISABLE_USER_SSH_EXITrV   DISABLE_USER_OPTSr   r$   rN   ra   rh   rv   r   r   r   r   r   r   r   r   boolr   r   r   r   r   r   r   r   r   r   r   <module>   sJ   
YEO
9
.