
    ~ gp                         S SK r S SKrS SKrS SKJr  S SKJr  S SKJrJ	r	   S SK
r
Sr S SKrSr " S S5      rg! \ a    Sr Nf = f! \ a    Sr N#f = f)	    N)getpass)_sos)is_executableTIMEOUT_DEFAULTTFc                      \ rS rSrSrSrSrSrSrSr	Sr
SrSrSrSrSrSrSrSrS	rSrSrSrSrSrSrSrSrSrSr0 S
S	_SS	_SS_S/ _SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS0ErSBS jrS r S  r!\"S! 5       r#S" r$S# r%S$ r&S% r'S& r(S' r)S( r*S) r+S* r,S+ r-S, r.SCS- jr/S. r0S/ r1S0 r2S1 r3S2 r4S3 r5S4 r6S5 r7S6 r8S7 r9S8 r:SCS9 jr;S: r<SDS; jr=S< r>SDS= jr?S> r@SES? jrA  SFS@ jrBSArCg)GUploadTarget    z
This class is designed to upload files to a distribution
defined location. These files can be either sos reports,
sos collections, or other kind of files like: vmcores,
application cores, logs, etc.

z
            Upload a file (can be an sos report, a must-gather, or others) to
             a distribution defined remote location
            zGeneric UploadgenericN/zhttps://s3.amazonaws.com upload_filecase_idlow_priorityFprofiles
upload_urlupload_directoryupload_userupload_passupload_methodautoupload_no_ssl_verifyupload_protocolupload_s3_endpointupload_s3_regionupload_s3_bucketupload_s3_access_keyupload_s3_secret_keyupload_s3_object_prefixupload_targetc                 ^    [         R                  " S5      U l        Xl        X0l        X l        g )Nsos_ui)logging	getLoggerui_logparsercmdlineargs)selfr%   r'   r&   s       =/usr/lib/python3/dist-packages/sos/upload/targets/__init__.py__init__UploadTarget.__init__a   s#    ''1	    c                     g)a3  This should be overridden by upload targets

This is called by sos upload on each target type that exists, and
is meant to return True when the upload target matches a criteria
that indicates that is the local upload target that should be used.

Only the first upload target to determine a match is selectedF r(   s    r)   check_distributionUploadTarget.check_distributionh   s     r,   c                     U R                   $ )N)upload_target_idr/   s    r)   get_target_idUploadTarget.get_target_idr   s    $$$r,   c                 p    U R                   (       a  U R                   $ U R                  R                  5       $ )z-Returns the upload target's name as a string.)upload_target_name__name__lower)clss    r)   nameUploadTarget.nameu   s,     !!)))||!!##r,   c                     U R                   S   U R                   S   U R                   S   R                  U R                   S   R                  S.$ )Ncmdlineoptspolicy)r>   r?   r   r   )hook_commonsr   r   r/   s    r)   get_commonsUploadTarget.get_commons|   sP    ,,];''1((7?? $ 1 1- @
 	
r,   c                     Xl         g)z9Set common host data for the Upload targets
to reference
N)commons)r(   rD   s     r)   set_commonsUploadTarget.set_commons   s	     r,   c                    Xl         U R                  5       U l        U R                  S   nU R                  S   nUR                  (       a  UR	                  5         UR
                  U l        UR                  U l        UR                  U l        UR                  U l	        SU l
        UR                  U l        UR                  U l        UR                  U l        UR                  U l        UR                  U l        UR                   U l        UR"                  (       d  UR$                  (       d  U R'                  5       (       a1  UR(                  S:X  d!  U R+                  5         U R-                  5         OPUR(                  S:X  a@  U R/                  5         U R1                  5         U R3                  5         U R5                  5         U R6                  R9                  S5        g g g )Nr>   r?   r   s3)r@   rA   rD   r   _configure_low_priorityr   r   r   r   upload_passwordupload_archive_namer   r   r   r   r   r   batchquietget_upload_urlr   prompt_for_upload_userprompt_for_upload_passwordprompt_for_upload_s3_bucketprompt_for_upload_s3_endpointprompt_for_upload_s3_access_keyprompt_for_upload_s3_secret_keyr$   info)r(   r@   cmdline_optsr?   s       r)   pre_workUploadTarget.pre_work   s   ('')||M2h'$$**,
 '11'33 , = =+77#% "."A"A , = =$0$E$E! , = ='3'K'K$$0$E$E! !!""""$$$44<++-//1--5002224446446KKR  # "r,   c                     U R                  5       (       d?  SU R                  5        SU R                  5        S3n[        [	        U5      5      U l        gg)zeShould be overridden by targets to determine if an access key needs
to be provided for upload or not
z0Please provide the upload access key for bucket  via endpoint : N)get_upload_s3_access_keyget_upload_s3_bucketget_upload_s3_endpointinput_r   r(   msgs     r)   rS   ,UploadTarget.prompt_for_upload_s3_access_key   s^     ,,..--/0 1//12"6 
 ).afD% /r,   c                     U R                  5       (       d6  SU R                  5        SU R                  5        S3n[        U5      U l        gg)zdShould be overridden by targets to determine if a secret key needs
to be provided for upload or not
z0Please provide the upload secret key for bucket rZ   r[   N)get_upload_s3_secret_keyr]   r^   r   r   ra   s     r)   rT   ,UploadTarget.prompt_for_upload_s3_secret_key   sZ     ,,..--/0 1//12"6 
 )0D% /r,   c                 2   U R                   (       d{  U R                  (       a@  U R                  R                  S5      (       a   U R                  SS U l         U R                   $ [        [	        S5      5      nUR                  S5      U l         U R                   $ )z`Should be overridden by targets to determine if a bucket needs to
be provided for upload or not
s3://   Nz"Please provide the upload bucket: r   )r   r   
startswithr_   r`   strip)r(   
user_inputs     r)   rQ   (UploadTarget.prompt_for_upload_s3_bucket   sz     $$4??#=#=g#F#F(,(;% $$$ #1%I#JK
(2(8(8(=%$$$r,   c                     U R                   nU R                  (       d;  SU R                  5        SU S3n[        [	        U5      5      nU=(       d    UU l        U R                  $ )zcShould be overridden by targets to determine if an endpoint needs
to be provided for upload or not
z.Please provide the upload endpoint for bucket z (default: z): )_upload_s3_endpointr   r]   r_   r`   )r(   default_endpointrb   rl   s       r)   rR   *UploadTarget.prompt_for_upload_s3_endpoint   sk      33&&--/0./s4 
 qvJ&0&D4DD#&&&r,   c                     U R                  5       (       d.  SU R                  5        S3n[        [        U5      5      U l        gg)zSShould be overridden by targets to determine if a user needs to
be provided or not
zPlease provide upload user for r[   N)get_upload_userrN   r_   r`   r   ra   s     r)   rO   #UploadTarget.prompt_for_upload_user   s@     ##%%3D4G4G4I3J"MC$QsV}D &r,   c                     U R                  5       (       dD  U R                  5       U R                  :w  a%  SU R                  5        S3n[        U5      U l        ggg)zbShould be overridden by targets to determine if a password needs to
be provided for upload or not
z'Please provide the upload password for r[   N)get_upload_passwordrs   _upload_userr   rJ   ra   s     r)   rP   'UploadTarget.prompt_for_upload_password   s]     ''))t/C/C/E/3/@/@0A<**,-R1C#*3<D 	0A)r,   c                 *   Xl         U R                  (       d  U R                  5       U l        U R                  (       d  [        S5      eU R	                  5       nU R
                  R                  [        SU R                  5        35      5        U" 5       $ )a)  
Entry point for sos attempts to upload the generated archive to a
target or user specified location.

Currently there is support for HTTPS, SFTP, and FTP. HTTPS uploads are
preferred for target-defined defaults.

Targets that need to override uploading methods should override the
respective upload_https(), upload_sftp(), and/or upload_ftp() methods
and should NOT override this method.

:param archive: The archive filepath to use for upload
:type archive: ``str``

In order to enable this for a target, that target needs to implement
the following:

Required Class Attrs

:_upload_url:     The default location to use. Note these MUST include
                  protocol header
:_upload_user:    Default username, if any else None
:_upload_password: Default password, if any else None

The following Class Attrs may optionally be overidden by the Target

:_upload_directory:     Default FTP server directory, if any


The following methods may be overridden by ``Target`` as needed

`prompt_for_upload_user()`
    Determines if sos should prompt for a username or not.

`get_upload_user()`
    Determines if the default or a different username should be used

`get_upload_https_auth()`
    Format authentication data for HTTPS uploads

`get_upload_url_string()`
    Print a more human-friendly string than vendor URLs
zBNo upload destination provided by upload target or by --upload-urlzAttempting upload to )	rK   r   rN   	Exception_determine_upload_typer$   rU   r`   get_upload_url_string)r(   archiveupload_funcs      r)   upload_archiveUploadTarget.upload_archive   s    X $+ "113DO 2 3 3113%d&@&@&B%CDE	
 }r,   c                 n   U R                   U R                  U R                  U R                  S.nU R                  S   R
                  U;   a  XR                  S   R
                     $ SU R                  ;  a  [        S5      eU R                  R                  S5      u  p#X!;  a  [        SU 35      eX   $ )zBased on the url provided, determine what type of upload to attempt.

Note that this requires users to provide a FQDN address, such as
https://myvendor.com/api or ftp://myvendor.com instead of
myvendor.com/api or myvendor.com
)ftpsftphttpsrH   r>   z://z#Must provide protocol in upload URLz&Unsupported or unrecognized protocol: )	
upload_ftpupload_sftpupload_https	upload_s3rD   r   r   rz   split)r(   protsprotr`   s       r)   r{   #UploadTarget._determine_upload_type3  s     ??$$&&..	
 <<&66%?m4DDEE'ABB//''.DTFKLL{r,   c                     U(       d  U R                  5       nU(       d  U R                  5       n[        R                  R	                  X5      $ )a0  Formats the user/password credentials using basic auth

:param user: The username for upload
:type user: ``str``

:param password: Password for `user` to use for upload
:type password: ``str``

:returns: The user/password auth suitable for use in requests calls
:rtype: ``requests.auth.HTTPBasicAuth()``
)rs   rv   requestsauthHTTPBasicAuth)r(   userpasswords      r)   get_upload_https_auth"UploadTarget.get_upload_https_authI  s;     '')D//1H}}**4::r,   c                 |    [         R                  " SS5      =(       d    U R                  =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload access key or one provided by the user

:returns: The access_key to use for upload
:rtype: ``str``
SOSUPLOADS3ACCESSKEYN)osgetenvr   _upload_s3_access_keyr/   s    r)   r\   %UploadTarget.get_upload_s3_access_key\  4     		0$7 +))+**	,r,   c                 \    U R                   (       d  U R                  5         U R                   $ )zHelper function to determine if we should use the target default
upload endpoint or one provided by the user

:returns: The S3 Endpoint to use for upload
:rtype: ``str``
)r   rR   r/   s    r)   r^   #UploadTarget.get_upload_s3_endpointg  s%     &&..0&&&r,   c                 @    U R                   =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload region or one provided by the user

:returns: The S3 region to use for upload
:rtype: ``str``
)r   _upload_s3_regionr/   s    r)   get_upload_s3_region!UploadTarget.get_upload_s3_regionr  s     $$>(>(>>r,   c                 h   U R                   (       ab  U R                   R                  S5      (       aB  U R                   SS R                  SS5      nUS   U l        [	        U5      S:  a
  US   U l        U R                  (       d  U R                  5         U R                  =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload bucket or one provided by the user

:returns: The S3 bucket to use for upload
:rtype: ``str``
rh   ri   Nr      r   )r   rj   r   r   lenr   rQ   _upload_s3_bucket)r(   bucket_and_prefixs     r)   r]   !UploadTarget.get_upload_s3_bucket{  s     ??t99'BB $ 3 9 9#q A$5a$8D!$%)/@/C,$$,,.$$>(>(>>r,   c                 @    U R                   =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload object prefix or one provided by the user

:returns: The S3 object prefix to use for upload
:rtype: ``str``
)r   _upload_s3_object_prefixr/   s    r)   get_upload_s3_object_prefix(UploadTarget.get_upload_s3_object_prefix  s     ++Lt/L/LLr,   c                 |    [         R                  " SS5      =(       d    U R                  =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload secret key or one provided by the user

:returns: The S3 secret key to use for upload
:rtype: ``str``
SOSUPLOADS3SECRETKEYN)r   r   r   _upload_s3_secret_keyr/   s    r)   re   %UploadTarget.get_upload_s3_secret_key  r   r,   c                 "   U R                   (       d`  U R                  (       aO  U R                  (       a>  U R                  (       a-  U R	                  5       nU R                  5       nSU SU 3U l        U R                   =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload url or one provided by the user

:returns: The URL to use for upload
:rtype: ``str``
rh   r   )r   r   r   r   r]   r   _upload_url)r(   bucketprefixs      r)   rN   UploadTarget.get_upload_url  sl     !!%%%%..0F557F!&vhax8D2$"2"22r,   c                 :    Sn[         R                  " USU5      nU$ )Nz([^:]+://[^:]+:)([^@]+)(@.+)z\1********\3)resub)r(   urlpatternobfuscated_urls       r)   _get_obfuscated_upload_url'UploadTarget._get_obfuscated_upload_url  s    1#>r,   c                 @    U R                  U R                  5       5      $ )zUsed by upload targets to potentially change the string used to
report upload location from the URL to a more human-friendly string
)r   rN   r/   s    r)   r|   "UploadTarget.get_upload_url_string  s     ..t/B/B/DEEr,   c                 |    [         R                  " SS5      =(       d    U R                  =(       d    U R                  $ )zHelper function to determine if we should use the target default
upload user or one provided by the user

:returns: The username to use for upload
:rtype: ``str``
SOSUPLOADUSERN)r   r   r   rw   r/   s    r)   rs   UploadTarget.get_upload_user  s3     		/40 "  "!!	#r,   c                 |    [         R                  " SS5      =(       d    U R                  =(       d    U R                  $ )a/  Helper function to determine if we should use the target default
upload password or one provided by the user

A user provided password, either via option or the 'SOSUPLOADPASSWORD'
environment variable will have precendent over any target value

:returns: The password to use for upload
:rtype: ``str``
SOSUPLOADPASSWORDN)r   r   rJ   _upload_passwordr/   s    r)   rv    UploadTarget.get_upload_password  s4     		-t4 &$$&%%	'r,   c                 h   [        S5      (       d  [        S5      e SSKnSnU(       d  U R	                  5       nU(       d  U R                  5       nU R                  5       R                  SS5      nS	U S
U 3nUR                  USS9nSSSUR                  UR                  /n	UR                  U	SS9n
U
S:X  a  SnOU
S:X  ar  UR                  U5        SSUR                  UR                  /nUR                  USS9S:H  nU(       d,  UR                  5         [        SU R                  5        35      eOcU
S:X  a  [        SU R                  5        S35      eU
S:X  a  [        SU R                  5        35      eU
S:X  a  [        SUR                   35      eU(       d,  UR                  5         [        SU R                  5        35      eSU R                    S U R#                  5        3nUR                  U5        S!UR                  UR                  S"/nUR                  US#S9nUS:X  a  UR                  S$5        gUS:X  a  [        S%5      eUS:X  a  [        S&UR                   35      eUS:X  a  [        S'5      e[        S(UR                   35      e! [         a  n[        S5      UeSnAff = f))a}  Attempts to upload the archive to an SFTP location.

Due to the lack of well maintained, secure, and generally widespread
python libraries for SFTP, sos will shell-out to the system's local ssh
installation in order to handle these uploads.

Do not override this method with one that uses python-paramiko, as the
upstream sos team will reject any PR that includes that dependency.
r   zSFTP is not locally supportedr   NzFSFTP upload requires python3-pexpect, which is not currently installedFzsftp://r   z sftp -oStrictHostKeyChecking=no @zutf-8)encodingzsftp>z	password:zConnection refused   timeoutTr   zPermission denied
   z#Incorrect username or password for    zConnection refused by z. Incorrect port?   z!Timeout hit trying to connect to    z,Unexpected error trying to connect to sftp: zUnable to connect via SFTP to zput  z100%zNo such file or directory   byezTimeout expired while uploadingzUnknown error during upload: z&Unable to write archive to destinationz!Unexpected response from server: )r   rz   pexpectImportErrorrs   rv   rN   replacespawnTIMEOUTEOFexpectsendlinecloser|   beforerK   _get_sftp_upload_name)r(   r   r   r   errsftp_connectedsftp_urlsftp_cmdretsftp_expectsidxpass_expectsput_cmdput_expectsput_successs                  r)   r   UploadTarget.upload_sftp  s    V$$;<<
	@
 '')D//1H &&(00B?5dV1XJGmmHwm7  OOKK
 jjrj2!8!NAXLL"#	L !ZZbZAQFN!		 E#'#=#=#?"@!B C C " AX4#99;<<MO P PAX?#99;<> ? ?AXJ"zzl, - - IIK<#99;<> ? ? $223100235W OOKK'	
 jjcj:!LL!=>>!;CJJ<HII!DEE;CJJ<HII]  	@ 6 7<?@	@s   J 
J1 J,,J1c                     U R                   R                  S5      S   nU R                  (       a*  [        R                  R                  U R                  U5      nU$ )zIf a specific file name pattern is required by the SFTP server,
override this method in the relevant Upload Target. Otherwise the
archive's name on disk will be used

:returns:       Filename as it will exist on the SFTP server
:rtype:         ``str``
r   )rK   r   r   r   pathjoin)r(   fnames     r)   r   "UploadTarget._get_sftp_upload_name7  sG     ((..s3B7  GGLL!6!6>Er,   c                 r    [         R                  " U R                  5       UU R                  5       U[        S9$ )zIf upload_https() needs to use requests.put(), use this method.

Targets should override this method instead of the base upload_https()

:param archive:     The open archive file object
)datar   verifyr   )r   putrN   r   r   )r(   r}   r   s      r)   _upload_https_putUploadTarget._upload_https_putD  s5     ||D//1!%!;!;!=#)?D 	Dr,   c                     0 $ )zJDefine any needed headers to be passed with the POST request here
        r.   r/   s    r)   _get_upload_headers UploadTarget._get_upload_headersO  s	     	r,   c                     SUR                   R                  S5      S   UU R                  5       40n[        R                  " U R                  5       UU R                  5       U[        S9$ )zIf upload_https() needs to use requests.post(), use this method.

Targets should override this method instead of the base upload_https()

:param archive:     The open archive file object
filer   r   )filesr   r   r   )r;   r   r   r   postrN   r   r   )r(   r}   r   r   s       r)   _upload_https_postUploadTarget._upload_https_postT  sj     W\\'',R0'--/1
 }}T002%"&"<"<">$*OE 	Er,   c                 @   [         (       d  [        S5      e[        U R                  S5       nU R                  S   R
                  S:X  a  U R                  nOU R                  S   R
                  nU R                  S   R                  SL nUS:X  a  U R                  X5      nOU R                  X5      nUR                  S;  a@  UR                  S:X  a  [        S	5      e[        S
UR                   SUR                   35      e SSS5        g! , (       d  f       g= f)zAttempts to upload the archive to an HTTPS location.

:returns: ``True`` if upload is successful
:rtype: ``bool``

:raises: ``Exception`` if upload was unsuccessful
z7Unable to upload due to missing python requests libraryrbr>   r   Fr   )      i  z/Authentication failed: invalid user credentialszPOST request returned r[   NT)REQUESTS_LOADEDrz   openrK   rD   r   _upload_methodr   r   r   status_codereason)r(   arcmethodr   rs        r)   r   UploadTarget.upload_httpsc  s     & ' ' $**D1S||M*88FB,,m4BB\\-0EENF**37++C8}}J.==C'#I   "8r#$88*!. / /# 211s   CD
Dc                    SSK nSSKnU(       d  U R                  5       nUc  [        S5      eUR	                  SS5      nU(       d  U R                  5       nU(       d  U R                  5       nU(       d  U R                  =(       d    U R                  n UR                  XUSS9nU(       d  [        S5      eUR                  U5        [!        U R"                  S5       n
UR%                  SU R"                  R                  S5      S    3U
5        SSS5        UR'                  5         g! UR                   a  n[        S	U 35      UeSnAfUR                   a  n[        S
U 35      UeSnAfUR                   az  n[        U5      R                  5       S   n	U	S:X  a  [        SU S35      UeU	S:X  a  [        SU S35      UeU	S:X  a  [        SU 35      Ue[        S[        U5       35      UeSnAff = f! , (       d  f       N= f)a  Attempts to upload the archive to either the target defined or user
provided FTP location.

:param url: The URL to upload to
:type url: ``str``

:param directory: The directory on the FTP server to write to
:type directory: ``str`` or ``None``

:param user: The user to authenticate with
:type user: ``str``

:param password: The password to use for `user`
:type password: ``str``

:returns: ``True`` if upload is successful
:rtype: ``bool``

:raises: ``Exception`` if upload in unsuccessful
r   NzPno FTP server specified by upload target, use --upload-url to specify a locationzftp://r   r   r   z3connection failed, did you set a user and password?z timeout hit while connecting to zunable to connect to 503zcould not login as ''530zinvalid password for user '550z"could not set upload directory to z#error trying to establish session: r   zSTOR r   r   T)ftplibsocketrN   rz   r   rs   rv   r   _upload_directoryFTPcwdr   gaierror
error_permstrr   r  rK   
storbinaryquit)r(   r   	directoryr   r   r  r  sessionr   errno_arcfiles              r)   r   UploadTarget.upload_ftp  s   * 	%%'C; E F F kk(B''')D//1H--G1G1GI	jjHbjAG !, - -KK	"" $**D1X0066s;B?@A8 2 	+ ~~ 	O>seDE3N 	D3C59:C   
	HNN$Q'E~"6tfA >?SH~"=dV1 EFCO~ D#,+!/ 0589A#c(LM
	 21s=   4D* 2G;*G8:E		G8E++G8>A5G33G8;
H	c                    [         (       d  [        S5      eU(       d  U R                  5       nU(       d  U R                  5       nU(       d  U R	                  5       R                  S5      nU(       d[  U R                  5       nUS:w  a  UR                  S5      (       a  USS nUS:w  a$  UR                  S5      (       d  U(       a  U S3OSnU(       d  U R                  5       nU(       d  U R                  5       n[        R                  " SUUUUS9n X@R                  R                  S5      S   -   nUR                  U R                  X85        g	! [         a  n	[        S
[!        U	5       35      U	eSn	A	ff = f)a5  Attempts to upload the archive to an S3 bucket.

:param endpoint: The S3 endpoint to upload to
:type endpoint: str

:param region: The S3 region to upload to
:type region: str

:param bucket: The name of the S3 bucket to upload to
:type bucket: str

:param prefix: The prefix for the S3 object/key
:type prefix: str

:param access_key: The access key for the S3 bucket
:type access_key: str

:param secret_key: The secret key for the S3 bucket
:type secret_key: str

:returns: True if upload is successful
:rtype: bool

:raises: Exception if upload is unsuccessful
z4Unable to upload due to missing python boto3 libraryr   r   r   NrH   )endpoint_urlregion_nameaws_access_key_idaws_secret_access_keyr   TzFailed to upload to S3: )BOTO3_LOADEDrz   r^   r   r]   rk   r   rj   endswithr\   re   boto3clientrK   r   r   r  )
r(   endpointregionr   r   
access_key
secret_key	s3_clientkeyes
             r)   r   UploadTarget.upload_s3  s[   6 | & ' ' 224H..0F..066s;F557F| 1 1# 6 6|FOOC$8$8)/F81R668J668JLLH-33=7AC	
	H3399#>rBBC!!$":":"(/ 	H6s1vh?@aG	Hs   <E 
E8E33E8)r   r'   r&   rD   r@   r%   r$   rK   r   rJ   r   r   r   r   r   r   r   r   )NNN)NN)T)NNNN)NNNNNN)Dr8   
__module____qualname____firstlineno____doc__descr7   r3   _upload_filer   r  rw   r   r  ro   r   r   r   r   r   r   r   rJ   r   r   r   r   r   r   r   arg_defaultsr*   r0   r4   classmethodr;   rA   rE   rW   rS   rT   rQ   rR   rO   rP   r   r{   r   r\   r^   r   r]   r   re   rN   r   r|   rs   rv   r   r   r   r   r   r   r   r   __static_attributes__r.   r,   r)   r   r       sG   D * LKLN4  !JKO"Mr2 	 	B	
 	d 	D 	t 	t 	 	 	6 	d 	D 	D 	  	!" 	"4#$ 	%L*% $ $
(!T6
5
%'-06p,;&	,	'?? M	,3"
F	#'aJF	D
E>DL IM.2?Hr,   r   )r   r   r"   r   sosr   r`   sos.utilitiesr   r   r   r  r   r'  r%  r   r.   r,   r)   <module>r<     sl    
 	    8OL
gH gH  O  Ls    : A AAAA