U
    ӇgX                     @   sl  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ee d d!d"Z ee d d#d$Z!d%d& Z"ee#d'd(d)Z$d*d+ Z%efd,d-Z&d.d/ Z'efeeeef  d0d1d2Z(d3d4 Z)d5d6 Z*dS )8    N)suppress)ListSequenceTuple)	lifecyclesubputilz/etc/ssh/sshd_config)ZrsaZecdsaZ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   sd   g }| j r|| j  | jr(|| j | jr:|| j | jrL|| j |sV| jS d|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 r|s$|| dkr|| }|d t |krF|d }q||d  }|dkrl|dkrl|d }n|dkrz| }|d }q|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   entZquotediZcurcZ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 nb tk
r   | |\}	}
|d krt|	}z||
\}}}W n  tk
r   t| Y  Y S X Y nX t|||||dS )Nz
# c                 S   s^   |  d d}t|dk r(tdt| |d tkrDtd|d  t|dkrZ|d |S )N   zTo few fields: %sr   zInvalid keytype %sr,   )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   r/   r*   )r   Zsrc_liner   liner1   r'   r   r   r   Zkeyoptsr)   r   r   r   parse   s,    
zAuthKeyLineParser.parse)N)r   r   r    __doc__r*   r6   r   r   r   r   r!   a   s   r!   c              
   C   s|   g }t  }g }| D ]d}z8tj|rLt| }|D ]}||| q6W q t	t
fk
rt   ttd| Y qX q|S )NzError reading lines from %s)r!   ospathisfiler   load_text_file
splitlinesr   r6   IOErrorOSErrorlogexcLOG)fnameslinesparsercontentsfnamer5   r   r   r   parse_authorized_keys   s    rF   c                 C   s   t dd |D }tt| D ]J}| | }| s4q|D ]&}|j|jkr8|}||kr8|| q8|| |< q|D ]}| | qndd | D }|d d|S )Nc                 S   s   g | ]}|  r|qS r   )r   .0kr   r   r   
<listcomp>   s      z*update_authorized_keys.<locals>.<listcomp>c                 S   s   g | ]}t |qS r   str)rH   br   r   r   rJ      s     r,   
)listranger%   r   r   remover   r   )Zold_entrieskeysZto_addr(   r'   rI   keyrB   r   r   r   update_authorized_keys   s     

rT   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RuntimeErrorr8   r9   r   )usernamepw_entr   r   r   users_ssh_info   s    

r[   c           	      C   sp   d|fd|fdf}| sd} |   }g }|D ]@}|D ]\}}|||}q2|ds`tj||}|| q*|S )N%h%u)z%%%%h/.ssh/authorized_keys/)r.   replacer3   r8   r9   r   r   )	valueZhomedirrY   ZmacrospathsZrenderedr9   ZmacroZfieldr   r   r   render_authorizedkeysfile_paths   s    
rd   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 | }	||	kr|dM }n|dM }||@ d	krtd
|||  dS |r|d@ d	krt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   Z	get_ownerr@   debugZget_permissionsZ	get_groupZget_user_groups)
rY   Zcurrent_path	full_pathis_filestrictmodesZminimal_permissionsownerZparent_permissionZgroup_ownerZuser_groupsr   r   r   check_permissions  sJ    





ro   c              
   C   s  t | d }t dd }z|ddd }d}tj|j}|D ]}|d| 7 }tj|rttd|  W dS tj	|rtd|  W dS |
|sD||jkrqDtj|st|P d	}	|j}
|j}|
|jrd
}	|j}
|j}tj||	dd t||
| W 5 Q R X t| ||d|}|sD W dS qDtj|sRtj|rdtd| W dS tj|stj|dddd t||j|j t| ||d|}|sW dS W n> ttfk
r } zttt| W Y dS d }~X Y nX dS )Nr#   re   r`   r,   z-Invalid directory. Symlink exists in path: %sFz*Invalid directory. File exists in path: %s  rf   T)modeexist_okz%s is not a file!  )rr   Zensure_dir_exists)r[   r.   r8   r9   dirnamerW   islinkr@   rj   r:   r3   existsr   SeLinuxGuardZpw_uidZpw_gidmakedirsZ	chownbyidro   isdir
write_filer=   r>   r?   rL   )rY   filenamerm   Z
user_pwentZ
root_pwentZdirectoriesZparent_folderZhome_folderZ	directoryrr   ZuidgidZpermissionser   r   r   check_create_pathG  s         
    
r   c                 C   s   t | \}}tj|d}|}g }tj|ddn z2t|}|dd}|dd}	t||j	| }W n4 t
tfk
r   ||d< ttd	t|d  Y nX W 5 Q R X t| |D ]H\}
}td
|
kd|
k|d|j	grt| ||	dk}|r|} qq||krtd| |t|gfS )NZauthorized_keysT	recursiveZauthorizedkeysfiler_   rm   Zyesr   zhFailed extracting 'AuthorizedKeysFile' in SSH config from %r, using 'AuthorizedKeysFile' file %r insteadr]   r\   z{}/zAAuthorizedKeysFile has an user-specific authorized_keys, using %s)r[   r8   r9   r   r   rx   parse_ssh_config_mapgetrd   rW   r=   r>   r?   r@   DEF_SSHD_CFGzipr.   anyr3   formatr   rj   rF   )rY   Zsshd_cfg_filessh_dirrZ   Zdefault_authorizedkeys_fileZuser_authorizedkeys_fileZauth_key_fnsZssh_cfgZ	key_pathsrm   Zkey_pathauth_key_fnZ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 5 Q R X d S )N)r   Tr   preserve_mode)r!   r   r6   rL   r   r8   r9   ru   r   rx   rT   r{   )
rR   rY   r   rC   Zkey_entriesrI   r   Z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   )r5   _keyrb   )r   r5   rI   vr   r   r   r     s    zSshdConfigLine.__init__c                 C   s   | j d krd S | j  S r   )r   lowerr   r   r   r   rS     s    
zSshdConfigLine.keyc                 C   s>   | j d krt| jS t| j }| jr6|dt| j 7 }|S d S r   )r   rL   r5   rb   )r   r   r   r   r   r     s    


zSshdConfigLine.__str__)NN)r   r   r    r   propertyrS   r   r   r   r   r   r     s   

r   )returnc                 C   s"   t j| sg S tt|  S r   )r8   r9   r:   parse_ssh_config_linesr   r;   r<   rE   r   r   r   parse_ssh_config  s    r   c                 C   s   g }| D ]}|  }|r"|dr2|t| qz|d d\}}W nP tk
r   z|dd\}}W n& tk
r   td| Y Y qY nX Y nX |t||| q|S )Nr+   r#   =z;sshd_config: option "%s" has no key/value pair, skipping it)r4   r3   r   r   r.   
ValueErrorr@   rj   )rB   retr5   rS   valr   r   r   r     s&    r   c                 C   s6   t | }|si S i }|D ]}|js$q|j||j< q|S r   )r   rS   rb   )rE   rB   r   r5   r   r   r   r     s    r   )rE   r   c                 C   s@   t j| sdS t|  D ]}|d|  dr dS qdS )NFzInclude z	.d/*.confT)r8   r9   r:   r   r;   r<   r3   )rE   r5   r   r   r   _includes_dconf"  s    r   c                 C   s^   t | rZtj|  ds.tj|  ddd tj|  dd} tj| sZt| d | S )Nz.drq   )rr   z50-cloud-init.confrt   )	r   r8   r9   rz   r   Z
ensure_dirr   r:   Zensure_filer   r   r   r   "_ensure_cloud_init_ssh_config_file+  s    r   c                 C   sP   t |}t|}t|| d}|rDt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.)rB   updatesrN   c                 S   s   g | ]}t |qS r   rK   )rH   r5   r   r   r   rJ   A  s     z%update_ssh_config.<locals>.<listcomp>Tr   r   )r   r   update_ssh_config_linesr   r{   r   r%   )r   rE   rB   changedr   r   r   update_ssh_config6  s    r   c           	      C   s  t  }g }tdd | D }t| ddD ]v\}}|js<q,|j|kr,||j }|| }|| |j|kr~td||| q,|	| td|||j| ||_q,t
|t
|kr| D ]B\}}||krq|	| | 	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   rG   r   r   r   rJ   Q  s     z+update_ssh_config_lines.<locals>.<listcomp>r#   )startz$line %d: option %s already set to %sz#line %d: option %s updated %s -> %sr,   z line %d: option %s added with %s)setdictrR   	enumeraterS   addrb   r@   rj   r   r%   itemsr   )	rB   r   foundr   Zcasemapr(   r5   rS   rb   r   r   r   r   G  sN    



   

   r   )rB   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   )rH   rI   r   r   r   r   	<genexpr>y  s     z$append_ssh_config.<locals>.<genexpr>rN   ZabT)Zomoder   )r   r   r{   r   )rB   rE   r   r   r   r   append_ssh_configu  s    r   c               	   C   sp   d} t tj  tjddgddgd\}} W 5 Q R X d}| dD ](}||rB|t||d	   S qBd
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.
    r,   Zsshdz-Vr   r#   )ZrcsZOpenSSH_rN   ,N)r   r   ZProcessExecutionErrorr.   r3   r%   find)err_prefixr5   r   r   r   get_opensshd_version  s    
$
r   c               	   C   s   d} t  }|dkrtj| S d|kr:|d|d } n d|krV|d|d } n|} ztj| } | W S  ttfk
r   td|  Y nX dS )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   ZVersionZfrom_strr   r   r/   r@   Zwarning)Zupstream_versionZfull_versionr   r   r   get_opensshd_upstream_version  s    r   )N)+Zloggingr8   rU   
contextlibr   typingr   r   r   Z	cloudinitr   r   r   Z	getLoggerr   r@   r   r0   Z_DISABLE_USER_SSH_EXITrL   ZDISABLE_USER_OPTSr   r!   rF   rT   r[   rd   ro   r   r   r   r   r   r   r   boolr   r   r   r   r   r   r   r   r   r   r   <module>	   sH   
YEO9
	.