We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow location of home directories to be configured
When JupyterHub is run with `create_system_users` it would be nice to be able to configure the locations of the home directories (something other than `/home`). I think this can be done with the `-d /otherhome` flag to the `useradd` command.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `jupyterhub/auth.py`
Content:
```
1 """Simple PAM authenticator"""
2 
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5 
6 from grp import getgrnam
7 import pwd
8 from subprocess import check_call, check_output, CalledProcessError
9 
10 from tornado import gen
11 import pamela
12 
13 from traitlets.config import LoggingConfigurable
14 from traitlets import Bool, Set, Unicode, Any
15 
16 from .handlers.login import LoginHandler
17 from .utils import url_path_join
18 
19 class Authenticator(LoggingConfigurable):
20     """A class for authentication.
21     
22     The API is one method, `authenticate`, a tornado gen.coroutine.
23     """
24     
25     db = Any()
26     admin_users = Set(config=True,
27         help="""set of usernames of admin users
28 
29         If unspecified, only the user that launches the server will be admin.
30         """
31     )
32     whitelist = Set(config=True,
33         help="""Username whitelist.
34         
35         Use this to restrict which users can login.
36         If empty, allow any user to attempt login.
37         """
38     )
39     custom_html = Unicode('',
40         help="""HTML login form for custom handlers.
41         Override in form-based custom authenticators
42         that don't use username+password,
43         or need custom branding.
44         """
45     )
46     login_service = Unicode('',
47         help="""Name of the login service for external
48         login services (e.g. 'GitHub').
49         """
50     )
51     
52     @gen.coroutine
53     def authenticate(self, handler, data):
54         """Authenticate a user with login form data.
55         
56         This must be a tornado gen.coroutine.
57         It must return the username on successful authentication,
58         and return None on failed authentication.
59         """
60 
61     def pre_spawn_start(self, user, spawner):
62         """Hook called before spawning a user's server.
63         
64         Can be used to do auth-related startup, e.g. opening PAM sessions.
65         """
66     
67     def post_spawn_stop(self, user, spawner):
68         """Hook called after stopping a user container.
69         
70         Can be used to do auth-related cleanup, e.g. closing PAM sessions.
71         """
72     
73     def check_whitelist(self, user):
74         """
75         Return True if the whitelist is empty or user is in the whitelist.
76         """
77         # Parens aren't necessary here, but they make this easier to parse.
78         return (not self.whitelist) or (user in self.whitelist)
79 
80     def add_user(self, user):
81         """Add a new user
82         
83         By default, this just adds the user to the whitelist.
84         
85         Subclasses may do more extensive things,
86         such as adding actual unix users.
87         """
88         if self.whitelist:
89             self.whitelist.add(user.name)
90     
91     def delete_user(self, user):
92         """Triggered when a user is deleted.
93         
94         Removes the user from the whitelist.
95         """
96         self.whitelist.discard(user.name)
97     
98     def login_url(self, base_url):
99         """Override to register a custom login handler"""
100         return url_path_join(base_url, 'login')
101     
102     def logout_url(self, base_url):
103         """Override to register a custom logout handler"""
104         return url_path_join(base_url, 'logout')
105     
106     def get_handlers(self, app):
107         """Return any custom handlers the authenticator needs to register
108         
109         (e.g. for OAuth)
110         """
111         return [
112             ('/login', LoginHandler),
113         ]
114 
115 class LocalAuthenticator(Authenticator):
116     """Base class for Authenticators that work with local *ix users
117     
118     Checks for local users, and can attempt to create them if they exist.
119     """
120     
121     create_system_users = Bool(False, config=True,
122         help="""If a user is added that doesn't exist on the system,
123         should I try to create the system user?
124         """
125     )
126 
127     group_whitelist = Set(
128         config=True,
129         help="Automatically whitelist anyone in this group.",
130     )
131 
132     def _group_whitelist_changed(self, name, old, new):
133         if self.whitelist:
134             self.log.warn(
135                 "Ignoring username whitelist because group whitelist supplied!"
136             )
137 
138     def check_whitelist(self, username):
139         if self.group_whitelist:
140             return self.check_group_whitelist(username)
141         else:
142             return super().check_whitelist(username)
143 
144     def check_group_whitelist(self, username):
145         if not self.group_whitelist:
146             return False
147         for grnam in self.group_whitelist:
148             try:
149                 group = getgrnam(grnam)
150             except KeyError:
151                 self.log.error('No such group: [%s]' % grnam)
152                 continue
153             if username in group.gr_mem:
154                 return True
155         return False
156 
157     @gen.coroutine
158     def add_user(self, user):
159         """Add a new user
160         
161         By default, this just adds the user to the whitelist.
162         
163         Subclasses may do more extensive things,
164         such as adding actual unix users.
165         """
166         user_exists = yield gen.maybe_future(self.system_user_exists(user))
167         if not user_exists:
168             if self.create_system_users:
169                 yield gen.maybe_future(self.add_system_user(user))
170             else:
171                 raise KeyError("User %s does not exist." % user.name)
172         
173         yield gen.maybe_future(super().add_user(user))
174     
175     @staticmethod
176     def system_user_exists(user):
177         """Check if the user exists on the system"""
178         try:
179             pwd.getpwnam(user.name)
180         except KeyError:
181             return False
182         else:
183             return True
184     
185     @staticmethod
186     def add_system_user(user):
187         """Create a new *ix user on the system. Works on FreeBSD and Linux, at least."""
188         name = user.name
189         for useradd in (
190             ['pw', 'useradd', '-m'],
191             ['useradd', '-m'],
192         ):
193             try:
194                 check_output(['which', useradd[0]])
195             except CalledProcessError:
196                 continue
197             else:
198                 break
199         else:
200             raise RuntimeError("I don't know how to add users on this system.")
201     
202         check_call(useradd + [name])
203 
204 
205 class PAMAuthenticator(LocalAuthenticator):
206     """Authenticate local *ix users with PAM"""
207     encoding = Unicode('utf8', config=True,
208         help="""The encoding to use for PAM"""
209     )
210     service = Unicode('login', config=True,
211         help="""The PAM service to use for authentication."""
212     )
213     
214     @gen.coroutine
215     def authenticate(self, handler, data):
216         """Authenticate with PAM, and return the username if login is successful.
217     
218         Return None otherwise.
219         """
220         username = data['username']
221         if not self.check_whitelist(username):
222             return
223         try:
224             pamela.authenticate(username, data['password'], service=self.service)
225         except pamela.PAMError as e:
226             self.log.warn("PAM Authentication failed: %s", e)
227         else:
228             return username
229     
230     def pre_spawn_start(self, user, spawner):
231         """Open PAM session for user"""
232         try:
233             pamela.open_session(user.name, service=self.service)
234         except pamela.PAMError as e:
235             self.log.warn("Failed to open PAM session for %s: %s", user.name, e)
236     
237     def post_spawn_stop(self, user, spawner):
238         """Close PAM session for user"""
239         try:
240             pamela.close_session(user.name, service=self.service)
241         except pamela.PAMError as e:
242             self.log.warn("Failed to close PAM session for %s: %s", user.name, e)
243     
244
```
--- 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/jupyterhub/auth.py b/jupyterhub/auth.py
--- a/jupyterhub/auth.py
+++ b/jupyterhub/auth.py
@@ -123,6 +123,17 @@
         should I try to create the system user?
         """
     )
+    system_user_home = Unicode('', config=True,
+        help="""Specify root home directory for users if different from system default.
+        
+        Passed to `useradd -b`.
+        If specified, when system users are created their home directories will be created in
+        
+            system_user_home/username
+        
+        Only has an effect when users are created with `create_system_users=True`.
+        """
+    )
 
     group_whitelist = Set(
         config=True,
@@ -182,8 +193,7 @@
         else:
             return True
     
-    @staticmethod
-    def add_system_user(user):
+    def add_system_user(self, user):
         """Create a new *ix user on the system. Works on FreeBSD and Linux, at least."""
         name = user.name
         for useradd in (
@@ -198,8 +208,11 @@
                 break
         else:
             raise RuntimeError("I don't know how to add users on this system.")
-    
-        check_call(useradd + [name])
+        
+        cmd = useradd
+        if self.system_user_home:
+            cmd = cmd + ['-b', self.system_user_home]
+        check_call(cmd + [name])
 
 
 class PAMAuthenticator(LocalAuthenticator):

{"golden_diff": "diff --git a/jupyterhub/auth.py b/jupyterhub/auth.py\n--- a/jupyterhub/auth.py\n+++ b/jupyterhub/auth.py\n@@ -123,6 +123,17 @@\n         should I try to create the system user?\n         \"\"\"\n     )\n+    system_user_home = Unicode('', config=True,\n+        help=\"\"\"Specify root home directory for users if different from system default.\n+        \n+        Passed to `useradd -b`.\n+        If specified, when system users are created their home directories will be created in\n+        \n+            system_user_home/username\n+        \n+        Only has an effect when users are created with `create_system_users=True`.\n+        \"\"\"\n+    )\n \n     group_whitelist = Set(\n         config=True,\n@@ -182,8 +193,7 @@\n         else:\n             return True\n     \n-    @staticmethod\n-    def add_system_user(user):\n+    def add_system_user(self, user):\n         \"\"\"Create a new *ix user on the system. Works on FreeBSD and Linux, at least.\"\"\"\n         name = user.name\n         for useradd in (\n@@ -198,8 +208,11 @@\n                 break\n         else:\n             raise RuntimeError(\"I don't know how to add users on this system.\")\n-    \n-        check_call(useradd + [name])\n+        \n+        cmd = useradd\n+        if self.system_user_home:\n+            cmd = cmd + ['-b', self.system_user_home]\n+        check_call(cmd + [name])\n \n \n class PAMAuthenticator(LocalAuthenticator):\n", "issue": "Allow location of home directories to be configured\nWhen JupyterHub is run with `create_system_users` it would be nice to be able to configure the locations of the home directories (something other than `/home`). I think this can be done with the `-d /otherhome` flag to the `useradd` command.\n\n", "before_files": [{"content": "\"\"\"Simple PAM authenticator\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom grp import getgrnam\nimport pwd\nfrom subprocess import check_call, check_output, CalledProcessError\n\nfrom tornado import gen\nimport pamela\n\nfrom traitlets.config import LoggingConfigurable\nfrom traitlets import Bool, Set, Unicode, Any\n\nfrom .handlers.login import LoginHandler\nfrom .utils import url_path_join\n\nclass Authenticator(LoggingConfigurable):\n    \"\"\"A class for authentication.\n    \n    The API is one method, `authenticate`, a tornado gen.coroutine.\n    \"\"\"\n    \n    db = Any()\n    admin_users = Set(config=True,\n        help=\"\"\"set of usernames of admin users\n\n        If unspecified, only the user that launches the server will be admin.\n        \"\"\"\n    )\n    whitelist = Set(config=True,\n        help=\"\"\"Username whitelist.\n        \n        Use this to restrict which users can login.\n        If empty, allow any user to attempt login.\n        \"\"\"\n    )\n    custom_html = Unicode('',\n        help=\"\"\"HTML login form for custom handlers.\n        Override in form-based custom authenticators\n        that don't use username+password,\n        or need custom branding.\n        \"\"\"\n    )\n    login_service = Unicode('',\n        help=\"\"\"Name of the login service for external\n        login services (e.g. 'GitHub').\n        \"\"\"\n    )\n    \n    @gen.coroutine\n    def authenticate(self, handler, data):\n        \"\"\"Authenticate a user with login form data.\n        \n        This must be a tornado gen.coroutine.\n        It must return the username on successful authentication,\n        and return None on failed authentication.\n        \"\"\"\n\n    def pre_spawn_start(self, user, spawner):\n        \"\"\"Hook called before spawning a user's server.\n        \n        Can be used to do auth-related startup, e.g. opening PAM sessions.\n        \"\"\"\n    \n    def post_spawn_stop(self, user, spawner):\n        \"\"\"Hook called after stopping a user container.\n        \n        Can be used to do auth-related cleanup, e.g. closing PAM sessions.\n        \"\"\"\n    \n    def check_whitelist(self, user):\n        \"\"\"\n        Return True if the whitelist is empty or user is in the whitelist.\n        \"\"\"\n        # Parens aren't necessary here, but they make this easier to parse.\n        return (not self.whitelist) or (user in self.whitelist)\n\n    def add_user(self, user):\n        \"\"\"Add a new user\n        \n        By default, this just adds the user to the whitelist.\n        \n        Subclasses may do more extensive things,\n        such as adding actual unix users.\n        \"\"\"\n        if self.whitelist:\n            self.whitelist.add(user.name)\n    \n    def delete_user(self, user):\n        \"\"\"Triggered when a user is deleted.\n        \n        Removes the user from the whitelist.\n        \"\"\"\n        self.whitelist.discard(user.name)\n    \n    def login_url(self, base_url):\n        \"\"\"Override to register a custom login handler\"\"\"\n        return url_path_join(base_url, 'login')\n    \n    def logout_url(self, base_url):\n        \"\"\"Override to register a custom logout handler\"\"\"\n        return url_path_join(base_url, 'logout')\n    \n    def get_handlers(self, app):\n        \"\"\"Return any custom handlers the authenticator needs to register\n        \n        (e.g. for OAuth)\n        \"\"\"\n        return [\n            ('/login', LoginHandler),\n        ]\n\nclass LocalAuthenticator(Authenticator):\n    \"\"\"Base class for Authenticators that work with local *ix users\n    \n    Checks for local users, and can attempt to create them if they exist.\n    \"\"\"\n    \n    create_system_users = Bool(False, config=True,\n        help=\"\"\"If a user is added that doesn't exist on the system,\n        should I try to create the system user?\n        \"\"\"\n    )\n\n    group_whitelist = Set(\n        config=True,\n        help=\"Automatically whitelist anyone in this group.\",\n    )\n\n    def _group_whitelist_changed(self, name, old, new):\n        if self.whitelist:\n            self.log.warn(\n                \"Ignoring username whitelist because group whitelist supplied!\"\n            )\n\n    def check_whitelist(self, username):\n        if self.group_whitelist:\n            return self.check_group_whitelist(username)\n        else:\n            return super().check_whitelist(username)\n\n    def check_group_whitelist(self, username):\n        if not self.group_whitelist:\n            return False\n        for grnam in self.group_whitelist:\n            try:\n                group = getgrnam(grnam)\n            except KeyError:\n                self.log.error('No such group: [%s]' % grnam)\n                continue\n            if username in group.gr_mem:\n                return True\n        return False\n\n    @gen.coroutine\n    def add_user(self, user):\n        \"\"\"Add a new user\n        \n        By default, this just adds the user to the whitelist.\n        \n        Subclasses may do more extensive things,\n        such as adding actual unix users.\n        \"\"\"\n        user_exists = yield gen.maybe_future(self.system_user_exists(user))\n        if not user_exists:\n            if self.create_system_users:\n                yield gen.maybe_future(self.add_system_user(user))\n            else:\n                raise KeyError(\"User %s does not exist.\" % user.name)\n        \n        yield gen.maybe_future(super().add_user(user))\n    \n    @staticmethod\n    def system_user_exists(user):\n        \"\"\"Check if the user exists on the system\"\"\"\n        try:\n            pwd.getpwnam(user.name)\n        except KeyError:\n            return False\n        else:\n            return True\n    \n    @staticmethod\n    def add_system_user(user):\n        \"\"\"Create a new *ix user on the system. Works on FreeBSD and Linux, at least.\"\"\"\n        name = user.name\n        for useradd in (\n            ['pw', 'useradd', '-m'],\n            ['useradd', '-m'],\n        ):\n            try:\n                check_output(['which', useradd[0]])\n            except CalledProcessError:\n                continue\n            else:\n                break\n        else:\n            raise RuntimeError(\"I don't know how to add users on this system.\")\n    \n        check_call(useradd + [name])\n\n\nclass PAMAuthenticator(LocalAuthenticator):\n    \"\"\"Authenticate local *ix users with PAM\"\"\"\n    encoding = Unicode('utf8', config=True,\n        help=\"\"\"The encoding to use for PAM\"\"\"\n    )\n    service = Unicode('login', config=True,\n        help=\"\"\"The PAM service to use for authentication.\"\"\"\n    )\n    \n    @gen.coroutine\n    def authenticate(self, handler, data):\n        \"\"\"Authenticate with PAM, and return the username if login is successful.\n    \n        Return None otherwise.\n        \"\"\"\n        username = data['username']\n        if not self.check_whitelist(username):\n            return\n        try:\n            pamela.authenticate(username, data['password'], service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"PAM Authentication failed: %s\", e)\n        else:\n            return username\n    \n    def pre_spawn_start(self, user, spawner):\n        \"\"\"Open PAM session for user\"\"\"\n        try:\n            pamela.open_session(user.name, service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"Failed to open PAM session for %s: %s\", user.name, e)\n    \n    def post_spawn_stop(self, user, spawner):\n        \"\"\"Close PAM session for user\"\"\"\n        try:\n            pamela.close_session(user.name, service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"Failed to close PAM session for %s: %s\", user.name, e)\n    \n", "path": "jupyterhub/auth.py"}], "after_files": [{"content": "\"\"\"Simple PAM authenticator\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom grp import getgrnam\nimport pwd\nfrom subprocess import check_call, check_output, CalledProcessError\n\nfrom tornado import gen\nimport pamela\n\nfrom traitlets.config import LoggingConfigurable\nfrom traitlets import Bool, Set, Unicode, Any\n\nfrom .handlers.login import LoginHandler\nfrom .utils import url_path_join\n\nclass Authenticator(LoggingConfigurable):\n    \"\"\"A class for authentication.\n    \n    The API is one method, `authenticate`, a tornado gen.coroutine.\n    \"\"\"\n    \n    db = Any()\n    admin_users = Set(config=True,\n        help=\"\"\"set of usernames of admin users\n\n        If unspecified, only the user that launches the server will be admin.\n        \"\"\"\n    )\n    whitelist = Set(config=True,\n        help=\"\"\"Username whitelist.\n        \n        Use this to restrict which users can login.\n        If empty, allow any user to attempt login.\n        \"\"\"\n    )\n    custom_html = Unicode('',\n        help=\"\"\"HTML login form for custom handlers.\n        Override in form-based custom authenticators\n        that don't use username+password,\n        or need custom branding.\n        \"\"\"\n    )\n    login_service = Unicode('',\n        help=\"\"\"Name of the login service for external\n        login services (e.g. 'GitHub').\n        \"\"\"\n    )\n    \n    @gen.coroutine\n    def authenticate(self, handler, data):\n        \"\"\"Authenticate a user with login form data.\n        \n        This must be a tornado gen.coroutine.\n        It must return the username on successful authentication,\n        and return None on failed authentication.\n        \"\"\"\n\n    def pre_spawn_start(self, user, spawner):\n        \"\"\"Hook called before spawning a user's server.\n        \n        Can be used to do auth-related startup, e.g. opening PAM sessions.\n        \"\"\"\n    \n    def post_spawn_stop(self, user, spawner):\n        \"\"\"Hook called after stopping a user container.\n        \n        Can be used to do auth-related cleanup, e.g. closing PAM sessions.\n        \"\"\"\n    \n    def check_whitelist(self, user):\n        \"\"\"\n        Return True if the whitelist is empty or user is in the whitelist.\n        \"\"\"\n        # Parens aren't necessary here, but they make this easier to parse.\n        return (not self.whitelist) or (user in self.whitelist)\n\n    def add_user(self, user):\n        \"\"\"Add a new user\n        \n        By default, this just adds the user to the whitelist.\n        \n        Subclasses may do more extensive things,\n        such as adding actual unix users.\n        \"\"\"\n        if self.whitelist:\n            self.whitelist.add(user.name)\n    \n    def delete_user(self, user):\n        \"\"\"Triggered when a user is deleted.\n        \n        Removes the user from the whitelist.\n        \"\"\"\n        self.whitelist.discard(user.name)\n    \n    def login_url(self, base_url):\n        \"\"\"Override to register a custom login handler\"\"\"\n        return url_path_join(base_url, 'login')\n    \n    def logout_url(self, base_url):\n        \"\"\"Override to register a custom logout handler\"\"\"\n        return url_path_join(base_url, 'logout')\n    \n    def get_handlers(self, app):\n        \"\"\"Return any custom handlers the authenticator needs to register\n        \n        (e.g. for OAuth)\n        \"\"\"\n        return [\n            ('/login', LoginHandler),\n        ]\n\nclass LocalAuthenticator(Authenticator):\n    \"\"\"Base class for Authenticators that work with local *ix users\n    \n    Checks for local users, and can attempt to create them if they exist.\n    \"\"\"\n    \n    create_system_users = Bool(False, config=True,\n        help=\"\"\"If a user is added that doesn't exist on the system,\n        should I try to create the system user?\n        \"\"\"\n    )\n    system_user_home = Unicode('', config=True,\n        help=\"\"\"Specify root home directory for users if different from system default.\n        \n        Passed to `useradd -b`.\n        If specified, when system users are created their home directories will be created in\n        \n            system_user_home/username\n        \n        Only has an effect when users are created with `create_system_users=True`.\n        \"\"\"\n    )\n\n    group_whitelist = Set(\n        config=True,\n        help=\"Automatically whitelist anyone in this group.\",\n    )\n\n    def _group_whitelist_changed(self, name, old, new):\n        if self.whitelist:\n            self.log.warn(\n                \"Ignoring username whitelist because group whitelist supplied!\"\n            )\n\n    def check_whitelist(self, username):\n        if self.group_whitelist:\n            return self.check_group_whitelist(username)\n        else:\n            return super().check_whitelist(username)\n\n    def check_group_whitelist(self, username):\n        if not self.group_whitelist:\n            return False\n        for grnam in self.group_whitelist:\n            try:\n                group = getgrnam(grnam)\n            except KeyError:\n                self.log.error('No such group: [%s]' % grnam)\n                continue\n            if username in group.gr_mem:\n                return True\n        return False\n\n    @gen.coroutine\n    def add_user(self, user):\n        \"\"\"Add a new user\n        \n        By default, this just adds the user to the whitelist.\n        \n        Subclasses may do more extensive things,\n        such as adding actual unix users.\n        \"\"\"\n        user_exists = yield gen.maybe_future(self.system_user_exists(user))\n        if not user_exists:\n            if self.create_system_users:\n                yield gen.maybe_future(self.add_system_user(user))\n            else:\n                raise KeyError(\"User %s does not exist.\" % user.name)\n        \n        yield gen.maybe_future(super().add_user(user))\n    \n    @staticmethod\n    def system_user_exists(user):\n        \"\"\"Check if the user exists on the system\"\"\"\n        try:\n            pwd.getpwnam(user.name)\n        except KeyError:\n            return False\n        else:\n            return True\n    \n    def add_system_user(self, user):\n        \"\"\"Create a new *ix user on the system. Works on FreeBSD and Linux, at least.\"\"\"\n        name = user.name\n        for useradd in (\n            ['pw', 'useradd', '-m'],\n            ['useradd', '-m'],\n        ):\n            try:\n                check_output(['which', useradd[0]])\n            except CalledProcessError:\n                continue\n            else:\n                break\n        else:\n            raise RuntimeError(\"I don't know how to add users on this system.\")\n        \n        cmd = useradd\n        if self.system_user_home:\n            cmd = cmd + ['-b', self.system_user_home]\n        check_call(cmd + [name])\n\n\nclass PAMAuthenticator(LocalAuthenticator):\n    \"\"\"Authenticate local *ix users with PAM\"\"\"\n    encoding = Unicode('utf8', config=True,\n        help=\"\"\"The encoding to use for PAM\"\"\"\n    )\n    service = Unicode('login', config=True,\n        help=\"\"\"The PAM service to use for authentication.\"\"\"\n    )\n    \n    @gen.coroutine\n    def authenticate(self, handler, data):\n        \"\"\"Authenticate with PAM, and return the username if login is successful.\n    \n        Return None otherwise.\n        \"\"\"\n        username = data['username']\n        if not self.check_whitelist(username):\n            return\n        try:\n            pamela.authenticate(username, data['password'], service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"PAM Authentication failed: %s\", e)\n        else:\n            return username\n    \n    def pre_spawn_start(self, user, spawner):\n        \"\"\"Open PAM session for user\"\"\"\n        try:\n            pamela.open_session(user.name, service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"Failed to open PAM session for %s: %s\", user.name, e)\n    \n    def post_spawn_stop(self, user, spawner):\n        \"\"\"Close PAM session for user\"\"\"\n        try:\n            pamela.close_session(user.name, service=self.service)\n        except pamela.PAMError as e:\n            self.log.warn(\"Failed to close PAM session for %s: %s\", user.name, e)\n    \n", "path": "jupyterhub/auth.py"}]}