We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Webroot breaks for non-root due to chown
Hi,

Today I'm trying out letsencrypt and stumbled upon that using webroot breaks when not using root due to a chown on line 72 of letsencrypt/plugins/webroot.py:

```
 49     def prepare(self):  # pylint: disable=missing-docstring
 50         path_map = self.conf("map")
 51
 52         if not path_map:
 53             raise errors.PluginError("--{0} must be set".format(
 54                 self.option_name("path")))
 55         for name, path in path_map.items():
 56             if not os.path.isdir(path):
 57                 raise errors.PluginError(path + " does not exist or is not a directory")
 58             self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)
 59
 60             logger.debug("Creating root challenges validation dir at %s",
 61                          self.full_roots[name])
 62             try:
 63                 os.makedirs(self.full_roots[name])
 64                 # Set permissions as parent directory (GH #1389)
 65                 # We don't use the parameters in makedirs because it
 66                 # may not always work
 67                 # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python
 68                 stat_path = os.stat(path)
 69                 filemode = stat.S_IMODE(stat_path.st_mode)
 70                 os.chmod(self.full_roots[name], filemode)
 71                 # Set owner and group, too
 72                 os.chown(self.full_roots[name], stat_path.st_uid,
 73                           stat_path.st_gid)
 74
 75             except OSError as exception:
 76                 if exception.errno != errno.EEXIST:
 77                     raise errors.PluginError(
 78                         "Couldn't create root for {0} http-01 "
 79                         "challenge responses: {1}", name, exception)
```

It tries to set the ownership of the directory to the owner of the parent. So currently to bypass this issue either the webroot must be chown'd to the letsencrypt user, or the script needs to be run as root. Neither of which are satisfactory solutions. Is chown at all neccesary when letsencrypt is not run as root?


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `letsencrypt/plugins/webroot.py`
Content:
```
1 """Webroot plugin."""
2 import errno
3 import logging
4 import os
5 import stat
6 
7 import zope.interface
8 
9 from acme import challenges
10 
11 from letsencrypt import errors
12 from letsencrypt import interfaces
13 from letsencrypt.plugins import common
14 
15 
16 logger = logging.getLogger(__name__)
17 
18 
19 class Authenticator(common.Plugin):
20     """Webroot Authenticator."""
21     zope.interface.implements(interfaces.IAuthenticator)
22     zope.interface.classProvides(interfaces.IPluginFactory)
23 
24     description = "Webroot Authenticator"
25 
26     MORE_INFO = """\
27 Authenticator plugin that performs http-01 challenge by saving
28 necessary validation resources to appropriate paths on the file
29 system. It expects that there is some other HTTP server configured
30 to serve all files under specified web root ({0})."""
31 
32     def more_info(self):  # pylint: disable=missing-docstring,no-self-use
33         return self.MORE_INFO.format(self.conf("path"))
34 
35     @classmethod
36     def add_parser_arguments(cls, add):
37         # --webroot-path and --webroot-map are added in cli.py because they
38         # are parsed in conjunction with --domains
39         pass
40 
41     def get_chall_pref(self, domain):  # pragma: no cover
42         # pylint: disable=missing-docstring,no-self-use,unused-argument
43         return [challenges.HTTP01]
44 
45     def __init__(self, *args, **kwargs):
46         super(Authenticator, self).__init__(*args, **kwargs)
47         self.full_roots = {}
48 
49     def prepare(self):  # pylint: disable=missing-docstring
50         path_map = self.conf("map")
51 
52         if not path_map:
53             raise errors.PluginError("--{0} must be set".format(
54                 self.option_name("path")))
55         for name, path in path_map.items():
56             if not os.path.isdir(path):
57                 raise errors.PluginError(path + " does not exist or is not a directory")
58             self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)
59 
60             logger.debug("Creating root challenges validation dir at %s",
61                          self.full_roots[name])
62             try:
63                 os.makedirs(self.full_roots[name])
64                 # Set permissions as parent directory (GH #1389)
65                 # We don't use the parameters in makedirs because it
66                 # may not always work
67                 # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python
68                 stat_path = os.stat(path)
69                 filemode = stat.S_IMODE(stat_path.st_mode)
70                 os.chmod(self.full_roots[name], filemode)
71                 # Set owner and group, too
72                 os.chown(self.full_roots[name], stat_path.st_uid,
73                           stat_path.st_gid)
74 
75             except OSError as exception:
76                 if exception.errno != errno.EEXIST:
77                     raise errors.PluginError(
78                         "Couldn't create root for {0} http-01 "
79                         "challenge responses: {1}", name, exception)
80 
81     def perform(self, achalls):  # pylint: disable=missing-docstring
82         assert self.full_roots, "Webroot plugin appears to be missing webroot map"
83         return [self._perform_single(achall) for achall in achalls]
84 
85     def _path_for_achall(self, achall):
86         try:
87             path = self.full_roots[achall.domain]
88         except IndexError:
89             raise errors.PluginError("Missing --webroot-path for domain: {1}"
90                         .format(achall.domain))
91         if not os.path.exists(path):
92             raise errors.PluginError("Mysteriously missing path {0} for domain: {1}"
93                         .format(path, achall.domain))
94         return os.path.join(path, achall.chall.encode("token"))
95 
96     def _perform_single(self, achall):
97         response, validation = achall.response_and_validation()
98         path = self._path_for_achall(achall)
99         logger.debug("Attempting to save validation to %s", path)
100         with open(path, "w") as validation_file:
101             validation_file.write(validation.encode())
102 
103         # Set permissions as parent directory (GH #1389)
104         parent_path = self.full_roots[achall.domain]
105         stat_parent_path = os.stat(parent_path)
106         filemode = stat.S_IMODE(stat_parent_path.st_mode)
107         # Remove execution bit (not needed for this file)
108         os.chmod(path, filemode & ~stat.S_IEXEC)
109         os.chown(path, stat_parent_path.st_uid, stat_parent_path.st_gid)
110 
111         return response
112 
113     def cleanup(self, achalls):  # pylint: disable=missing-docstring
114         for achall in achalls:
115             path = self._path_for_achall(achall)
116             logger.debug("Removing %s", path)
117             os.remove(path)
118
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/letsencrypt/plugins/webroot.py b/letsencrypt/plugins/webroot.py
--- a/letsencrypt/plugins/webroot.py
+++ b/letsencrypt/plugins/webroot.py
@@ -2,7 +2,6 @@
 import errno
 import logging
 import os
-import stat
 
 import zope.interface
 
@@ -59,24 +58,38 @@
 
             logger.debug("Creating root challenges validation dir at %s",
                          self.full_roots[name])
+
+            # Change the permissions to be writable (GH #1389)
+            # Umask is used instead of chmod to ensure the client can also
+            # run as non-root (GH #1795)
+            old_umask = os.umask(0o022)
+
             try:
-                os.makedirs(self.full_roots[name])
-                # Set permissions as parent directory (GH #1389)
-                # We don't use the parameters in makedirs because it
-                # may not always work
+                # This is coupled with the "umask" call above because
+                # os.makedirs's "mode" parameter may not always work:
                 # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python
-                stat_path = os.stat(path)
-                filemode = stat.S_IMODE(stat_path.st_mode)
-                os.chmod(self.full_roots[name], filemode)
-                # Set owner and group, too
-                os.chown(self.full_roots[name], stat_path.st_uid,
-                          stat_path.st_gid)
+                os.makedirs(self.full_roots[name], 0o0755)
+
+                # Set owner as parent directory if possible
+                try:
+                    stat_path = os.stat(path)
+                    os.chown(self.full_roots[name], stat_path.st_uid,
+                             stat_path.st_gid)
+                except OSError as exception:
+                    if exception.errno == errno.EACCES:
+                        logger.debug("Insufficient permissions to change owner and uid - ignoring")
+                    else:
+                        raise errors.PluginError(
+                            "Couldn't create root for {0} http-01 "
+                            "challenge responses: {1}", name, exception)
 
             except OSError as exception:
                 if exception.errno != errno.EEXIST:
                     raise errors.PluginError(
                         "Couldn't create root for {0} http-01 "
                         "challenge responses: {1}", name, exception)
+            finally:
+                os.umask(old_umask)
 
     def perform(self, achalls):  # pylint: disable=missing-docstring
         assert self.full_roots, "Webroot plugin appears to be missing webroot map"
@@ -87,26 +100,26 @@
             path = self.full_roots[achall.domain]
         except IndexError:
             raise errors.PluginError("Missing --webroot-path for domain: {1}"
-                        .format(achall.domain))
+                                     .format(achall.domain))
         if not os.path.exists(path):
             raise errors.PluginError("Mysteriously missing path {0} for domain: {1}"
-                        .format(path, achall.domain))
+                                     .format(path, achall.domain))
         return os.path.join(path, achall.chall.encode("token"))
 
     def _perform_single(self, achall):
         response, validation = achall.response_and_validation()
+
         path = self._path_for_achall(achall)
         logger.debug("Attempting to save validation to %s", path)
-        with open(path, "w") as validation_file:
-            validation_file.write(validation.encode())
-
-        # Set permissions as parent directory (GH #1389)
-        parent_path = self.full_roots[achall.domain]
-        stat_parent_path = os.stat(parent_path)
-        filemode = stat.S_IMODE(stat_parent_path.st_mode)
-        # Remove execution bit (not needed for this file)
-        os.chmod(path, filemode & ~stat.S_IEXEC)
-        os.chown(path, stat_parent_path.st_uid, stat_parent_path.st_gid)
+
+        # Change permissions to be world-readable, owner-writable (GH #1795)
+        old_umask = os.umask(0o022)
+
+        try:
+            with open(path, "w") as validation_file:
+                validation_file.write(validation.encode())
+        finally:
+            os.umask(old_umask)
 
         return response

{"golden_diff": "diff --git a/letsencrypt/plugins/webroot.py b/letsencrypt/plugins/webroot.py\n--- a/letsencrypt/plugins/webroot.py\n+++ b/letsencrypt/plugins/webroot.py\n@@ -2,7 +2,6 @@\n import errno\n import logging\n import os\n-import stat\n \n import zope.interface\n \n@@ -59,24 +58,38 @@\n \n             logger.debug(\"Creating root challenges validation dir at %s\",\n                          self.full_roots[name])\n+\n+            # Change the permissions to be writable (GH #1389)\n+            # Umask is used instead of chmod to ensure the client can also\n+            # run as non-root (GH #1795)\n+            old_umask = os.umask(0o022)\n+\n             try:\n-                os.makedirs(self.full_roots[name])\n-                # Set permissions as parent directory (GH #1389)\n-                # We don't use the parameters in makedirs because it\n-                # may not always work\n+                # This is coupled with the \"umask\" call above because\n+                # os.makedirs's \"mode\" parameter may not always work:\n                 # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python\n-                stat_path = os.stat(path)\n-                filemode = stat.S_IMODE(stat_path.st_mode)\n-                os.chmod(self.full_roots[name], filemode)\n-                # Set owner and group, too\n-                os.chown(self.full_roots[name], stat_path.st_uid,\n-                          stat_path.st_gid)\n+                os.makedirs(self.full_roots[name], 0o0755)\n+\n+                # Set owner as parent directory if possible\n+                try:\n+                    stat_path = os.stat(path)\n+                    os.chown(self.full_roots[name], stat_path.st_uid,\n+                             stat_path.st_gid)\n+                except OSError as exception:\n+                    if exception.errno == errno.EACCES:\n+                        logger.debug(\"Insufficient permissions to change owner and uid - ignoring\")\n+                    else:\n+                        raise errors.PluginError(\n+                            \"Couldn't create root for {0} http-01 \"\n+                            \"challenge responses: {1}\", name, exception)\n \n             except OSError as exception:\n                 if exception.errno != errno.EEXIST:\n                     raise errors.PluginError(\n                         \"Couldn't create root for {0} http-01 \"\n                         \"challenge responses: {1}\", name, exception)\n+            finally:\n+                os.umask(old_umask)\n \n     def perform(self, achalls):  # pylint: disable=missing-docstring\n         assert self.full_roots, \"Webroot plugin appears to be missing webroot map\"\n@@ -87,26 +100,26 @@\n             path = self.full_roots[achall.domain]\n         except IndexError:\n             raise errors.PluginError(\"Missing --webroot-path for domain: {1}\"\n-                        .format(achall.domain))\n+                                     .format(achall.domain))\n         if not os.path.exists(path):\n             raise errors.PluginError(\"Mysteriously missing path {0} for domain: {1}\"\n-                        .format(path, achall.domain))\n+                                     .format(path, achall.domain))\n         return os.path.join(path, achall.chall.encode(\"token\"))\n \n     def _perform_single(self, achall):\n         response, validation = achall.response_and_validation()\n+\n         path = self._path_for_achall(achall)\n         logger.debug(\"Attempting to save validation to %s\", path)\n-        with open(path, \"w\") as validation_file:\n-            validation_file.write(validation.encode())\n-\n-        # Set permissions as parent directory (GH #1389)\n-        parent_path = self.full_roots[achall.domain]\n-        stat_parent_path = os.stat(parent_path)\n-        filemode = stat.S_IMODE(stat_parent_path.st_mode)\n-        # Remove execution bit (not needed for this file)\n-        os.chmod(path, filemode & ~stat.S_IEXEC)\n-        os.chown(path, stat_parent_path.st_uid, stat_parent_path.st_gid)\n+\n+        # Change permissions to be world-readable, owner-writable (GH #1795)\n+        old_umask = os.umask(0o022)\n+\n+        try:\n+            with open(path, \"w\") as validation_file:\n+                validation_file.write(validation.encode())\n+        finally:\n+            os.umask(old_umask)\n \n         return response\n", "issue": "Webroot breaks for non-root due to chown\nHi,\n\nToday I'm trying out letsencrypt and stumbled upon that using webroot breaks when not using root due to a chown on line 72 of letsencrypt/plugins/webroot.py:\n\n```\n 49     def prepare(self):  # pylint: disable=missing-docstring\n 50         path_map = self.conf(\"map\")\n 51\n 52         if not path_map:\n 53             raise errors.PluginError(\"--{0} must be set\".format(\n 54                 self.option_name(\"path\")))\n 55         for name, path in path_map.items():\n 56             if not os.path.isdir(path):\n 57                 raise errors.PluginError(path + \" does not exist or is not a directory\")\n 58             self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)\n 59\n 60             logger.debug(\"Creating root challenges validation dir at %s\",\n 61                          self.full_roots[name])\n 62             try:\n 63                 os.makedirs(self.full_roots[name])\n 64                 # Set permissions as parent directory (GH #1389)\n 65                 # We don't use the parameters in makedirs because it\n 66                 # may not always work\n 67                 # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python\n 68                 stat_path = os.stat(path)\n 69                 filemode = stat.S_IMODE(stat_path.st_mode)\n 70                 os.chmod(self.full_roots[name], filemode)\n 71                 # Set owner and group, too\n 72                 os.chown(self.full_roots[name], stat_path.st_uid,\n 73                           stat_path.st_gid)\n 74\n 75             except OSError as exception:\n 76                 if exception.errno != errno.EEXIST:\n 77                     raise errors.PluginError(\n 78                         \"Couldn't create root for {0} http-01 \"\n 79                         \"challenge responses: {1}\", name, exception)\n```\n\nIt tries to set the ownership of the directory to the owner of the parent. So currently to bypass this issue either the webroot must be chown'd to the letsencrypt user, or the script needs to be run as root. Neither of which are satisfactory solutions. Is chown at all neccesary when letsencrypt is not run as root?\n\n", "before_files": [{"content": "\"\"\"Webroot plugin.\"\"\"\nimport errno\nimport logging\nimport os\nimport stat\n\nimport zope.interface\n\nfrom acme import challenges\n\nfrom letsencrypt import errors\nfrom letsencrypt import interfaces\nfrom letsencrypt.plugins import common\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Authenticator(common.Plugin):\n    \"\"\"Webroot Authenticator.\"\"\"\n    zope.interface.implements(interfaces.IAuthenticator)\n    zope.interface.classProvides(interfaces.IPluginFactory)\n\n    description = \"Webroot Authenticator\"\n\n    MORE_INFO = \"\"\"\\\nAuthenticator plugin that performs http-01 challenge by saving\nnecessary validation resources to appropriate paths on the file\nsystem. It expects that there is some other HTTP server configured\nto serve all files under specified web root ({0}).\"\"\"\n\n    def more_info(self):  # pylint: disable=missing-docstring,no-self-use\n        return self.MORE_INFO.format(self.conf(\"path\"))\n\n    @classmethod\n    def add_parser_arguments(cls, add):\n        # --webroot-path and --webroot-map are added in cli.py because they\n        # are parsed in conjunction with --domains\n        pass\n\n    def get_chall_pref(self, domain):  # pragma: no cover\n        # pylint: disable=missing-docstring,no-self-use,unused-argument\n        return [challenges.HTTP01]\n\n    def __init__(self, *args, **kwargs):\n        super(Authenticator, self).__init__(*args, **kwargs)\n        self.full_roots = {}\n\n    def prepare(self):  # pylint: disable=missing-docstring\n        path_map = self.conf(\"map\")\n\n        if not path_map:\n            raise errors.PluginError(\"--{0} must be set\".format(\n                self.option_name(\"path\")))\n        for name, path in path_map.items():\n            if not os.path.isdir(path):\n                raise errors.PluginError(path + \" does not exist or is not a directory\")\n            self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)\n\n            logger.debug(\"Creating root challenges validation dir at %s\",\n                         self.full_roots[name])\n            try:\n                os.makedirs(self.full_roots[name])\n                # Set permissions as parent directory (GH #1389)\n                # We don't use the parameters in makedirs because it\n                # may not always work\n                # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python\n                stat_path = os.stat(path)\n                filemode = stat.S_IMODE(stat_path.st_mode)\n                os.chmod(self.full_roots[name], filemode)\n                # Set owner and group, too\n                os.chown(self.full_roots[name], stat_path.st_uid,\n                          stat_path.st_gid)\n\n            except OSError as exception:\n                if exception.errno != errno.EEXIST:\n                    raise errors.PluginError(\n                        \"Couldn't create root for {0} http-01 \"\n                        \"challenge responses: {1}\", name, exception)\n\n    def perform(self, achalls):  # pylint: disable=missing-docstring\n        assert self.full_roots, \"Webroot plugin appears to be missing webroot map\"\n        return [self._perform_single(achall) for achall in achalls]\n\n    def _path_for_achall(self, achall):\n        try:\n            path = self.full_roots[achall.domain]\n        except IndexError:\n            raise errors.PluginError(\"Missing --webroot-path for domain: {1}\"\n                        .format(achall.domain))\n        if not os.path.exists(path):\n            raise errors.PluginError(\"Mysteriously missing path {0} for domain: {1}\"\n                        .format(path, achall.domain))\n        return os.path.join(path, achall.chall.encode(\"token\"))\n\n    def _perform_single(self, achall):\n        response, validation = achall.response_and_validation()\n        path = self._path_for_achall(achall)\n        logger.debug(\"Attempting to save validation to %s\", path)\n        with open(path, \"w\") as validation_file:\n            validation_file.write(validation.encode())\n\n        # Set permissions as parent directory (GH #1389)\n        parent_path = self.full_roots[achall.domain]\n        stat_parent_path = os.stat(parent_path)\n        filemode = stat.S_IMODE(stat_parent_path.st_mode)\n        # Remove execution bit (not needed for this file)\n        os.chmod(path, filemode & ~stat.S_IEXEC)\n        os.chown(path, stat_parent_path.st_uid, stat_parent_path.st_gid)\n\n        return response\n\n    def cleanup(self, achalls):  # pylint: disable=missing-docstring\n        for achall in achalls:\n            path = self._path_for_achall(achall)\n            logger.debug(\"Removing %s\", path)\n            os.remove(path)\n", "path": "letsencrypt/plugins/webroot.py"}], "after_files": [{"content": "\"\"\"Webroot plugin.\"\"\"\nimport errno\nimport logging\nimport os\n\nimport zope.interface\n\nfrom acme import challenges\n\nfrom letsencrypt import errors\nfrom letsencrypt import interfaces\nfrom letsencrypt.plugins import common\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Authenticator(common.Plugin):\n    \"\"\"Webroot Authenticator.\"\"\"\n    zope.interface.implements(interfaces.IAuthenticator)\n    zope.interface.classProvides(interfaces.IPluginFactory)\n\n    description = \"Webroot Authenticator\"\n\n    MORE_INFO = \"\"\"\\\nAuthenticator plugin that performs http-01 challenge by saving\nnecessary validation resources to appropriate paths on the file\nsystem. It expects that there is some other HTTP server configured\nto serve all files under specified web root ({0}).\"\"\"\n\n    def more_info(self):  # pylint: disable=missing-docstring,no-self-use\n        return self.MORE_INFO.format(self.conf(\"path\"))\n\n    @classmethod\n    def add_parser_arguments(cls, add):\n        # --webroot-path and --webroot-map are added in cli.py because they\n        # are parsed in conjunction with --domains\n        pass\n\n    def get_chall_pref(self, domain):  # pragma: no cover\n        # pylint: disable=missing-docstring,no-self-use,unused-argument\n        return [challenges.HTTP01]\n\n    def __init__(self, *args, **kwargs):\n        super(Authenticator, self).__init__(*args, **kwargs)\n        self.full_roots = {}\n\n    def prepare(self):  # pylint: disable=missing-docstring\n        path_map = self.conf(\"map\")\n\n        if not path_map:\n            raise errors.PluginError(\"--{0} must be set\".format(\n                self.option_name(\"path\")))\n        for name, path in path_map.items():\n            if not os.path.isdir(path):\n                raise errors.PluginError(path + \" does not exist or is not a directory\")\n            self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)\n\n            logger.debug(\"Creating root challenges validation dir at %s\",\n                         self.full_roots[name])\n\n            # Change the permissions to be writable (GH #1389)\n            # Umask is used instead of chmod to ensure the client can also\n            # run as non-root (GH #1795)\n            old_umask = os.umask(0o022)\n\n            try:\n                # This is coupled with the \"umask\" call above because\n                # os.makedirs's \"mode\" parameter may not always work:\n                # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python\n                os.makedirs(self.full_roots[name], 0o0755)\n\n                # Set owner as parent directory if possible\n                try:\n                    stat_path = os.stat(path)\n                    os.chown(self.full_roots[name], stat_path.st_uid,\n                             stat_path.st_gid)\n                except OSError as exception:\n                    if exception.errno == errno.EACCES:\n                        logger.debug(\"Insufficient permissions to change owner and uid - ignoring\")\n                    else:\n                        raise errors.PluginError(\n                            \"Couldn't create root for {0} http-01 \"\n                            \"challenge responses: {1}\", name, exception)\n\n            except OSError as exception:\n                if exception.errno != errno.EEXIST:\n                    raise errors.PluginError(\n                        \"Couldn't create root for {0} http-01 \"\n                        \"challenge responses: {1}\", name, exception)\n            finally:\n                os.umask(old_umask)\n\n    def perform(self, achalls):  # pylint: disable=missing-docstring\n        assert self.full_roots, \"Webroot plugin appears to be missing webroot map\"\n        return [self._perform_single(achall) for achall in achalls]\n\n    def _path_for_achall(self, achall):\n        try:\n            path = self.full_roots[achall.domain]\n        except IndexError:\n            raise errors.PluginError(\"Missing --webroot-path for domain: {1}\"\n                                     .format(achall.domain))\n        if not os.path.exists(path):\n            raise errors.PluginError(\"Mysteriously missing path {0} for domain: {1}\"\n                                     .format(path, achall.domain))\n        return os.path.join(path, achall.chall.encode(\"token\"))\n\n    def _perform_single(self, achall):\n        response, validation = achall.response_and_validation()\n\n        path = self._path_for_achall(achall)\n        logger.debug(\"Attempting to save validation to %s\", path)\n\n        # Change permissions to be world-readable, owner-writable (GH #1795)\n        old_umask = os.umask(0o022)\n\n        try:\n            with open(path, \"w\") as validation_file:\n                validation_file.write(validation.encode())\n        finally:\n            os.umask(old_umask)\n\n        return response\n\n    def cleanup(self, achalls):  # pylint: disable=missing-docstring\n        for achall in achalls:\n            path = self._path_for_achall(achall)\n            logger.debug(\"Removing %s\", path)\n            os.remove(path)\n", "path": "letsencrypt/plugins/webroot.py"}]}