ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ"""Compat module to handle files security on Windows and Linux""" from __future__ import absolute_import from contextlib import contextmanager import errno import os # pylint: disable=os-module-forbidden import stat import sys from typing import Any from typing import Dict from typing import List from typing import Generator from typing import Optional try: import ntsecuritycon import pywintypes import win32api import win32con import win32file import win32security import winerror except ImportError: POSIX_MODE = True else: POSIX_MODE = False # Windows umask implementation, since Windows does not have a concept of umask by default. # We choose 022 as initial value since it is the default one on most Linux distributions, and # it is a decent choice to not have write permissions for group owner and everybody by default. # We use a class here to avoid needing to define a global variable, and the potential mistakes # that could happen with this kind of pattern. class _WindowsUmask: """Store the current umask to apply on Windows""" def __init__(self) -> None: self.mask = 0o022 _WINDOWS_UMASK = _WindowsUmask() def chmod(file_path: str, mode: int) -> None: """ Apply a POSIX mode on given file_path: - for Linux, the POSIX mode will be directly applied using chmod, - for Windows, the POSIX mode will be translated into a Windows DACL that make sense for Certbot context, and applied to the file using kernel calls. The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the method `_generate_windows_flags()`. :param str file_path: Path of the file :param int mode: POSIX mode to apply """ if POSIX_MODE: os.chmod(file_path, mode) else: _apply_win_mode(file_path, mode) def umask(mask: int) -> int: """ Set the current numeric umask and return the previous umask. On Linux, the built-in umask method is used. On Windows, our Certbot-side implementation is used. :param int mask: The user file-creation mode mask to apply. :rtype: int :return: The previous umask value. """ if POSIX_MODE: return os.umask(mask) previous_umask = _WINDOWS_UMASK.mask _WINDOWS_UMASK.mask = mask return previous_umask @contextmanager def temp_umask(mask: int) -> Generator[None, None, None]: """ Apply a umask temporarily, meant to be used in a `with` block. Uses the Certbot implementation of umask. :param int mask: The user file-creation mode mask to apply temporarily """ old_umask: Optional[int] = None try: old_umask = umask(mask) yield None finally: if old_umask is not None: umask(old_umask) # One could ask why there is no copy_ownership() function, or even a reimplementation # of os.chown() that would modify the ownership of file without touching the mode itself. # This is because on Windows, it would require recalculating the existing DACL against # the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically # the current owner of a file. This action would be necessary to keep consistency between # the POSIX mode applied to the file and the current owner of this file. # Since copying and editing arbitrary DACL is very difficult, and since we actually know # the mode to apply at the time the owner of a file should change, it is easier to just # change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does. def copy_ownership_and_apply_mode(src: str, dst: str, mode: int, copy_user: bool, copy_group: bool) -> None: """ Copy ownership (user and optionally group on Linux) from the source to the destination, then apply given mode in compatible way for Linux and Windows. This replaces the os.chown command. :param str src: Path of the source file :param str dst: Path of the destination file :param int mode: Permission mode to apply on the destination file :param bool copy_user: Copy user if `True` :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) """ if POSIX_MODE: stats = os.stat(src) user_id = stats.st_uid if copy_user else -1 group_id = stats.st_gid if copy_group else -1 # On Windows, os.chown does not exist. This is checked through POSIX_MODE value, # but MyPy/PyLint does not know it and raises an error here on Windows. # We disable specifically the check to fix the issue. os.chown(dst, user_id, group_id) elif copy_user: # There is no group handling in Windows _copy_win_ownership(src, dst) chmod(dst, mode) # Quite similar to copy_ownership_and_apply_mode, but this time the DACL is copied from # the source file on Windows. The DACL stays consistent with the dynamic rights of the # equivalent POSIX mode, because ownership and mode are copied altogether on the destination # file, so no recomputing of the DACL against the new owner is needed, as it would be # for a copy_ownership alone method. def copy_ownership_and_mode(src: str, dst: str, copy_user: bool = True, copy_group: bool = True) -> None: """ Copy ownership (user and optionally group on Linux) and mode/DACL from the source to the destination. :param str src: Path of the source file :param str dst: Path of the destination file :param bool copy_user: Copy user if `True` :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) """ if POSIX_MODE: # On Linux, we just delegate to chown and chmod. stats = os.stat(src) user_id = stats.st_uid if copy_user else -1 group_id = stats.st_gid if copy_group else -1 os.chown(dst, user_id, group_id) chmod(dst, stats.st_mode) else: if copy_user: # There is no group handling in Windows _copy_win_ownership(src, dst) _copy_win_mode(src, dst) def check_mode(file_path: str, mode: int) -> bool: """ Check if the given mode matches the permissions of the given file. On Linux, will make a direct comparison, on Windows, mode will be compared against the security model. :param str file_path: Path of the file :param int mode: POSIX mode to test :rtype: bool :return: True if the POSIX mode matches the file permissions """ if POSIX_MODE: return stat.S_IMODE(os.stat(file_path).st_mode) == mode return _check_win_mode(file_path, mode) def check_owner(file_path: str) -> bool: """ Check if given file is owned by current user. :param str file_path: File path to check :rtype: bool :return: True if given file is owned by current user, False otherwise. """ if POSIX_MODE: return os.stat(file_path).st_uid == os.getuid() # Get owner sid of the file security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) user = security.GetSecurityDescriptorOwner() # Compare sids return _get_current_user() == user def check_permissions(file_path: str, mode: int) -> bool: """ Check if given file has the given mode and is owned by current user. :param str file_path: File path to check :param int mode: POSIX mode to check :rtype: bool :return: True if file has correct mode and owner, False otherwise. """ return check_owner(file_path) and check_mode(file_path, mode) def open(file_path: str, flags: int, mode: int = 0o777) -> int: # pylint: disable=redefined-builtin """ Wrapper of original os.open function, that will ensure on Windows that given mode is correctly applied. :param str file_path: The file path to open :param int flags: Flags to apply on file while opened :param int mode: POSIX mode to apply on file when opened, Python defaults will be applied if ``None`` :returns: the file descriptor to the opened file :rtype: int :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set, OSError(errno.EACCES) on Windows if the file already exists and is a directory, and os.O_CREAT is set. """ if POSIX_MODE: # On Linux, invoke os.open directly. return os.open(file_path, flags, mode) # Windows: handle creation of the file atomically with proper permissions. if flags & os.O_CREAT: # If os.O_EXCL is set, we will use the "CREATE_NEW", that will raise an exception if # file exists, matching the API contract of this bit flag. Otherwise, we use # "CREATE_ALWAYS" that will always create the file whether it exists or not. disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS attributes = win32security.SECURITY_ATTRIBUTES() security = attributes.SECURITY_DESCRIPTOR user = _get_current_user() dacl = _generate_dacl(user, mode, _WINDOWS_UMASK.mask) # We set second parameter to 0 (`False`) to say that this security descriptor is # NOT constructed from a default mechanism, but is explicitly set by the user. # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long security.SetSecurityDescriptorOwner(user, 0) # We set first parameter to 1 (`True`) to say that this security descriptor contains # a DACL. Otherwise second and third parameters are ignored. # We set third parameter to 0 (`False`) to say that this security descriptor is # NOT constructed from a default mechanism, but is explicitly set by the user. # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long security.SetSecurityDescriptorDacl(1, dacl, 0) handle = None try: handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, attributes, disposition, 0, None) except pywintypes.error as err: # Handle native windows errors into python errors to be consistent with the API # of os.open in the situation of a file already existing or locked. if err.winerror == winerror.ERROR_FILE_EXISTS: raise OSError(errno.EEXIST, err.strerror) if err.winerror == winerror.ERROR_SHARING_VIOLATION: raise OSError(errno.EACCES, err.strerror) raise err finally: if handle: handle.Close() # At this point, the file that did not exist has been created with proper permissions, # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to # avoid a FileExists exception before calling os.open. return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL) # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine. handle = os.open(file_path, flags) chmod(file_path, mode) return handle def makedirs(file_path: str, mode: int = 0o777) -> None: """ Rewrite of original os.makedirs function, that will ensure on Windows that given mode is correctly applied. :param str file_path: The file path to open :param int mode: POSIX mode to apply on leaf directory when created, Python defaults will be applied if ``None`` """ current_umask = umask(0) try: # Since Python 3.7, os.makedirs does not set the given mode to the intermediate # directories that could be created in the process. To keep things safe and consistent # on all Python versions, we set the umask accordingly to have all directories # (intermediate and leaf) created with the given mode. umask(current_umask | 0o777 ^ mode) if POSIX_MODE: return os.makedirs(file_path, mode) orig_mkdir_fn = os.mkdir try: # As we know that os.mkdir is called internally by os.makedirs, we will swap the # function in os module for the time of makedirs execution on Windows. os.mkdir = mkdir # type: ignore return os.makedirs(file_path, mode) finally: os.mkdir = orig_mkdir_fn finally: umask(current_umask) def mkdir(file_path: str, mode: int = 0o777) -> None: """ Rewrite of original os.mkdir function, that will ensure on Windows that given mode is correctly applied. :param str file_path: The file path to open :param int mode: POSIX mode to apply on directory when created, Python defaults will be applied if ``None`` """ if POSIX_MODE: return os.mkdir(file_path, mode) attributes = win32security.SECURITY_ATTRIBUTES() security = attributes.SECURITY_DESCRIPTOR user = _get_current_user() dacl = _generate_dacl(user, mode, _WINDOWS_UMASK.mask) security.SetSecurityDescriptorOwner(user, False) security.SetSecurityDescriptorDacl(1, dacl, 0) try: win32file.CreateDirectory(file_path, attributes) except pywintypes.error as err: # Handle native windows error into python error to be consistent with the API # of os.mkdir in the situation of a directory already existing. if err.winerror == winerror.ERROR_ALREADY_EXISTS: raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror) raise err return None def replace(src: str, dst: str) -> None: """ Rename a file to a destination path and handles situations where the destination exists. :param str src: The current file path. :param str dst: The new file path. """ if hasattr(os, 'replace'): # Use replace if possible. Since we don't support Python 2 on Windows # and os.replace() was added in Python 3.3, we can assume that # os.replace() is always available on Windows. getattr(os, 'replace')(src, dst) else: # Otherwise, use os.rename() that behaves like os.replace() on Linux. os.rename(src, dst) def realpath(file_path: str) -> str: """ Find the real path for the given path. This method resolves symlinks, including recursive symlinks, and is protected against symlinks that creates an infinite loop. :param str file_path: The path to resolve :returns: The real path for the given path :rtype: str """ original_path = file_path # Since Python 3.8, os.path.realpath also resolves symlinks on Windows. if POSIX_MODE or sys.version_info >= (3, 8): path = os.path.realpath(file_path) if os.path.islink(path): # If path returned by realpath is still a link, it means that it failed to # resolve the symlink because of a loop. # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) return path inspected_paths: List[str] = [] while os.path.islink(file_path): link_path = file_path file_path = os.readlink(file_path) if not os.path.isabs(file_path): file_path = os.path.join(os.path.dirname(link_path), file_path) if file_path in inspected_paths: raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) inspected_paths.append(file_path) return os.path.abspath(file_path) def readlink(link_path: str) -> str: """ Return a string representing the path to which the symbolic link points. :param str link_path: The symlink path to resolve :return: The path the symlink points to :returns: str :raise: ValueError if a long path (260> characters) is encountered on Windows """ path = os.readlink(link_path) if POSIX_MODE or not path.startswith('\\\\?\\'): return path # At this point, we know we are on Windows and that the path returned uses # the extended form which is done for all paths in Python 3.8+ # Max length of a normal path is 260 characters on Windows, including the non printable # termination character "". The termination character is not included in Python # strings, giving a max length of 259 characters, + 4 characters for the extended form # prefix, to an effective max length 263 characters on a string representing a normal path. if len(path) < 264: return path[4:] raise ValueError("Long paths are not supported by Certbot on Windows.") # On Windows is_executable run from an unprivileged shell may claim that a path is # executable when it is executable only if run from a privileged shell. This result # is due to the fact that GetEffectiveRightsFromAcl calculate effective rights # without taking into consideration if the target user has currently required the # elevated privileges or not. However this is not a problem since certbot always # requires to be run under a privileged shell, so the user will always benefit # from the highest (privileged one) set of permissions on a given file. def is_executable(path: str) -> bool: """ Is path an executable file? :param str path: path to test :return: True if path is an executable file :rtype: bool """ if POSIX_MODE: return os.path.isfile(path) and os.access(path, os.X_OK) return _win_is_executable(path) def has_world_permissions(path: str) -> bool: """ Check if everybody/world has any right (read/write/execute) on a file given its path. :param str path: path to test :return: True if everybody/world has any right to the file :rtype: bool """ if POSIX_MODE: return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO) security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) dacl = security.GetSecurityDescriptorDacl() return bool(dacl.GetEffectiveRightsFromAcl({ 'TrusteeForm': win32security.TRUSTEE_IS_SID, 'TrusteeType': win32security.TRUSTEE_IS_USER, 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'), })) def compute_private_key_mode(old_key: str, base_mode: int) -> int: """ Calculate the POSIX mode to apply to a private key given the previous private key. :param str old_key: path to the previous private key :param int base_mode: the minimum modes to apply to a private key :return: the POSIX mode to apply :rtype: int """ if POSIX_MODE: # On Linux, we keep read/write/execute permissions # for group and read permissions for everybody. old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) & (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH)) return base_mode | old_mode # On Windows, the mode returned by os.stat is not reliable, # so we do not keep any permission from the previous private key. return base_mode def has_same_ownership(path1: str, path2: str) -> bool: """ Return True if the ownership of two files given their respective path is the same. On Windows, ownership is checked against owner only, since files do not have a group owner. :param str path1: path to the first file :param str path2: path to the second file :return: True if both files have the same ownership, False otherwise :rtype: bool """ if POSIX_MODE: stats1 = os.stat(path1) stats2 = os.stat(path2) return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid) security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION) user1 = security1.GetSecurityDescriptorOwner() security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION) user2 = security2.GetSecurityDescriptorOwner() return user1 == user2 def has_min_permissions(path: str, min_mode: int) -> bool: """ Check if a file given its path has at least the permissions defined by the given minimal mode. On Windows, group permissions are ignored since files do not have a group owner. :param str path: path to the file to check :param int min_mode: the minimal permissions expected :return: True if the file matches the minimal permissions expectations, False otherwise :rtype: bool """ if POSIX_MODE: st_mode = os.stat(path).st_mode return st_mode == st_mode | min_mode # Resolve symlinks, to get a consistent result with os.stat on Linux, # that follows symlinks by default. path = realpath(path) # Get owner sid of the file security = win32security.GetFileSecurity( path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) user = security.GetSecurityDescriptorOwner() dacl = security.GetSecurityDescriptorDacl() min_dacl = _generate_dacl(user, min_mode) for index in range(min_dacl.GetAceCount()): min_ace = min_dacl.GetAce(index) # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID. # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html mask = min_ace[1] user = min_ace[2] effective_mask = dacl.GetEffectiveRightsFromAcl({ 'TrusteeForm': win32security.TRUSTEE_IS_SID, 'TrusteeType': win32security.TRUSTEE_IS_USER, 'Identifier': user, }) if effective_mask != effective_mask | mask: return False return True def _win_is_executable(path: str) -> bool: if not os.path.isfile(path): return False security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) dacl = security.GetSecurityDescriptorDacl() mode = dacl.GetEffectiveRightsFromAcl({ 'TrusteeForm': win32security.TRUSTEE_IS_SID, 'TrusteeType': win32security.TRUSTEE_IS_USER, 'Identifier': _get_current_user(), }) return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE def _apply_win_mode(file_path: str, mode: int) -> None: """ This function converts the given POSIX mode into a Windows ACL list, and applies it to the file given its path. If the given path is a symbolic link, it will resolved to apply the mode on the targeted file. """ file_path = realpath(file_path) # Get owner sid of the file security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) user = security.GetSecurityDescriptorOwner() # New DACL, that will overwrite existing one (including inherited permissions) dacl = _generate_dacl(user, mode) # Apply the new DACL security.SetSecurityDescriptorDacl(1, dacl, 0) win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) def _generate_dacl(user_sid: Any, mode: int, mask: Optional[int] = None) -> Any: if mask: mode = mode & (0o777 - mask) analysis = _analyze_mode(mode) # Get standard accounts from "well-known" sid # See the list here: # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems system = win32security.ConvertStringSidToSid('S-1-5-18') admins = win32security.ConvertStringSidToSid('S-1-5-32-544') everyone = win32security.ConvertStringSidToSid('S-1-1-0') # New dacl, without inherited permissions dacl = win32security.ACL() # If user is already system or admins, any ACE defined here would be superseded by # the full control ACE that will be added after. if user_sid not in [system, admins]: # Handle user rights user_flags = _generate_windows_flags(analysis['user']) if user_flags: dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) # Handle everybody rights everybody_flags = _generate_windows_flags(analysis['all']) if everybody_flags: dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) # Handle administrator rights full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) return dacl def _analyze_mode(mode: int) -> Dict[str, Dict[str, int]]: return { 'user': { 'read': mode & stat.S_IRUSR, 'write': mode & stat.S_IWUSR, 'execute': mode & stat.S_IXUSR, }, 'all': { 'read': mode & stat.S_IROTH, 'write': mode & stat.S_IWOTH, 'execute': mode & stat.S_IXOTH, }, } def _copy_win_ownership(src: str, dst: str) -> None: # Resolve symbolic links src = realpath(src) security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION) user_src = security_src.GetSecurityDescriptorOwner() security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION) # Second parameter indicates, if `False`, that the owner of the file is not provided by some # default mechanism, but is explicitly set instead. This is obviously what we are doing here. security_dst.SetSecurityDescriptorOwner(user_src, False) win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst) def _copy_win_mode(src: str, dst: str) -> None: # Resolve symbolic links src = realpath(src) # Copy the DACL from src to dst. security_src = win32security.GetFileSecurity(src, win32security.DACL_SECURITY_INFORMATION) dacl = security_src.GetSecurityDescriptorDacl() security_dst = win32security.GetFileSecurity(dst, win32security.DACL_SECURITY_INFORMATION) security_dst.SetSecurityDescriptorDacl(1, dacl, 0) win32security.SetFileSecurity(dst, win32security.DACL_SECURITY_INFORMATION, security_dst) def _generate_windows_flags(rights_desc: Dict[str, int]) -> int: # Some notes about how each POSIX right is interpreted. # # For the rights read and execute, we have a pretty bijective relation between # POSIX flags and their generic counterparts on Windows, so we use them directly # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). # # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS # subtracted of the rights corresponding to POSIX read and POSIX execute. # # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, # so a "Full Control" on the file. # # A complete list of the rights defined on NTFS can be found here: # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders flag = 0 if rights_desc['read']: flag = flag | ntsecuritycon.FILE_GENERIC_READ if rights_desc['write']: flag = flag | (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ ^ ntsecuritycon.FILE_GENERIC_EXECUTE) if rights_desc['execute']: flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE return flag def _check_win_mode(file_path: str, mode: int) -> bool: # Resolve symbolic links file_path = realpath(file_path) # Get current dacl file security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) dacl = security.GetSecurityDescriptorDacl() # Get current file owner sid user = security.GetSecurityDescriptorOwner() if not dacl: # No DACL means full control to everyone # This is not a deterministic permissions set. return False # Calculate the target dacl ref_dacl = _generate_dacl(user, mode) return _compare_dacls(dacl, ref_dacl) def _compare_dacls(dacl1: Any, dacl2: Any) -> bool: """ This method compare the two given DACLs to check if they are identical. Identical means here that they contains the same set of ACEs in the same order. """ return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] == [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())]) def _get_current_user() -> Any: """ Return the pySID corresponding to the current user. """ # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx, # because this function returns nonsense values when Certbot is run under NT AUTHORITY\SYSTEM. # To run Certbot under NT AUTHORITY\SYSTEM, you can open a shell using the instructions here: # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/ account_name = r"{0}\{1}".format(win32api.GetDomainName(), win32api.GetUserName()) # LookupAccountName() expects the system name as first parameter. By passing None to it, # we instruct Windows to first search the matching account in the machine local accounts, # then into the primary domain accounts, if the machine has joined a domain, then finally # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows # if there is no reason to use a specific lookup mechanism. # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea return win32security.LookupAccountName(None, account_name)[0]