o
    w7eGY                     @   sn  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
 d dl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d8d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/d0 Z'efd1eeeef  fd2d3Z(d4d5 Z)d6d7 Z*dS )9    N)suppress)ListSequenceTuple)log)subputilz/etc/ssh/sshd_config)dsa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-dss-cert-v01@openssh.comzssh-dssz 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__H   s
   
zAuthKeyLine.__init__c                 C   s   | j o| jS r   )r   r   r   r   r   r   validQ   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__T   s   
zAuthKeyLine.__str__)NNNN)__name__
__module____qualname__r   r   r!   r   r   r   r   r   G   s
    
	r   c                   @   s"   e Zd ZdZdd ZdddZdS )AuthKeyLineParsera  
    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, id_dsa.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_optionsx   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 %sr3   )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   r6   r1   )r   src_liner   liner8   r+   r   r   r   keyoptsr0   r   r   r   parse   s2   

zAuthKeyLineParser.parser   )r"   r#   r$   __doc__r1   r?   r   r   r   r   r%   d   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_file
splitlinesr   r?   IOErrorOSErrorlogexcLOG)fnameslinesparsercontentsfnamer=   r   r   r   parse_authorized_keys   s   rO   c                 C   s   t dd |D }tdt| D ]%}| | }| sq|D ]}|j|jkr0|}||v r0|| q|| |< q|D ]}| | q8d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>r   c                 S      g | ]}t |qS r   str)rQ   br   r   r   rS          r3   
)listranger)   r   r   remover   r   )old_entrieskeysto_addr-   r+   rR   keyrK   r   r   r   update_authorized_keys   s"   



rb   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RuntimeErrorrA   rB   r   )usernamepw_entr   r   r   users_ssh_info   s   

ri   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/)r5   replacer:   rA   rB   r   r   )	valuehomedirrg   macrospathsrenderedrB   macrofieldr   r   r   render_authorizedkeysfile_paths   s   
rw   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_ownerrI   debugget_permissions	get_groupget_user_groups)
rg   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'   rx   rn   r3   z-Invalid directory. Symlink exists in path: %sFz*Invalid directory. File exists in path: %s  ry   T)modeexist_okz%s is not a file!  )r   ensure_dir_exists)ri   r5   rA   rB   dirnamere   islinkrI   r~   rC   r:   existsr   SeLinuxGuardpw_uidpw_gidmakedirs	chownbyidr   isdir
write_filerF   rG   rH   rW   )rg   filenamer   
user_pwent
root_pwentdirectoriesparent_folderhome_folder	directoryr   uidgidpermissionser   r   r   check_create_pathJ  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authorizedkeysfilerm   r   yesr   zhFailed extracting 'AuthorizedKeysFile' in SSH config from %r, using 'AuthorizedKeysFile' file %r insteadrk   rj   z{}/zAAuthorizedKeysFile has an user-specific authorized_keys, using %s)ri   rA   rB   r   r   r   parse_ssh_config_mapgetrw   re   rF   rG   rH   rI   DEF_SSHD_CFGzipr5   anyr:   formatr   r~   rO   )rg   sshd_cfg_filessh_dirrh   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?   rW   r   rA   rB   r   r   r   rb   r   )
r_   rg   r   rL   key_entriesrR   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=   _keyrp   )r   r=   rR   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   ra     s   

zSshdConfigLine.keyc                 C   s:   | j d u r
t| jS t| j }| jr|dt| j 7 }|S r   )r   rW   r=   rp   )r   r   r   r   r   r!     s   


zSshdConfigLine.__str__)NN)r"   r#   r$   r   propertyra   r!   r   r   r   r   r     s
    

r   returnc                 C   s"   t j| sg S tt|  S r   )rA   rB   rC   parse_ssh_config_linesr   rD   rE   rN   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 )Nr2   r'   =z;sshd_config: option "%s" has no key/value pair, skipping it)r;   r:   r   r   r5   
ValueErrorrI   r~   )rK   retr=   ra   valr   r   r   r     s,   
r   c                 C   s6   t | }|si S i }|D ]}|jsq|j||j< q|S r   )r   ra   rp   )rN   rK   r   r=   r   r   r   r     s   r   rN   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)rA   rB   rC   openr:   )rN   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   rA   rB   r   r   
ensure_dirr   rC   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.)rK   updatesrZ   c                 S   rU   r   rV   )rQ   r=   r   r   r   rS   E  rY   z%update_ssh_config.<locals>.<listcomp>Tr   r   )r   r   update_ssh_config_linesr   r   r   r)   )r   rN   rK   changedr   r   r   update_ssh_config:  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   rP   r   r   r   rS   U  rT   z+update_ssh_config_lines.<locals>.<listcomp>r'   )startz$line %d: option %s already set to %sz#line %d: option %s updated %s -> %sr3   z line %d: option %s added with %s)setdictr_   	enumeratera   addrp   rI   r~   r   r)   itemsr   )	rK   r   foundr   casemapr-   r=   ra   rp   r   r   r   r   K  sD   





r   rK   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   )rQ   rR   r   r   r   r   	<genexpr>}  s     z$append_ssh_config.<locals>.<genexpr>rZ   abT)omoder   )r   r   r   r   )rK   rN   r   r   r   r   append_ssh_configy  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.
    r3   sshdz-Vr   r'   )rcsNOpenSSH_rZ   ,)r   r   ProcessExecutionErrorr5   r:   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 dameon 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   r6   rI   warning)upstream_versionfull_versionr   r   r   get_opensshd_upstream_version  s   r   r   )+rA   rc   
contextlibr   typingr   r   r   	cloudinitr   loggingr   r   	getLoggerr"   rI   r   r7   _DISABLE_USER_SSH_EXITrW   DISABLE_USER_OPTSr   r%   rO   rb   ri   rw   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
.