You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by wz...@apache.org on 2022/05/15 22:27:59 UTC

[impala] 03/03: IMPALA-10745 (part 2): Support Kerberos over HTTP for impala-shell

This is an automated email from the ASF dual-hosted git repository.

wzhou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit b867f4c4f1794b434c90f7bd702b277a570b6303
Author: wzhou-code <wz...@cloudera.com>
AuthorDate: Wed May 11 13:28:24 2022 -0700

    IMPALA-10745 (part 2): Support Kerberos over HTTP for impala-shell
    
    This patch adds kerberos-1.3.1 Python module to shell/ext-py so that
    the egg file of Kerberos module is built and added into impala-shell
    tarball when running script shell/make_shell_tarball.sh.
    Kerberos Python module is distributed under Apache License Version 2.
    Its source distribution is available at:
    https://pypi.org/project/kerberos/
    
    Testing:
     - Passed core run.
     - Installed impala-shell from impala-shell tarball on dev box as
       standalone package. Verified that impala-shell could be ran without
       additional configurations.
     - Installed impala-shell from impala-shell tarball on a real cluster
       with a full Kerberos setup. Verified that impala-shell could
       connect to impala server with options "-k --protocol=hs2-http".
    
    Change-Id: Id34074cbe725ba2cf1407fcf59e00475cd417a6d
    Reviewed-on: http://gerrit.cloudera.org:8080/18523
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 bin/rat_exclude_files.txt                          |    1 +
 shell/ext-py/kerberos-1.3.1/MANIFEST.in            |    3 +
 shell/ext-py/kerberos-1.3.1/PKG-INFO               |  138 +++
 shell/ext-py/kerberos-1.3.1/README.md              |  119 +++
 .../kerberos-1.3.1/kerberos.egg-info/PKG-INFO      |  138 +++
 .../kerberos-1.3.1/kerberos.egg-info/SOURCES.txt   |   18 +
 .../kerberos.egg-info/dependency_links.txt         |    1 +
 .../kerberos-1.3.1/kerberos.egg-info/top_level.txt |    1 +
 shell/ext-py/kerberos-1.3.1/pysrc/kerberos.py      |  461 +++++++++
 shell/ext-py/kerberos-1.3.1/setup.cfg              |    4 +
 shell/ext-py/kerberos-1.3.1/setup.py               |  138 +++
 shell/ext-py/kerberos-1.3.1/src/base64.c           |  133 +++
 shell/ext-py/kerberos-1.3.1/src/base64.h           |   20 +
 shell/ext-py/kerberos-1.3.1/src/kerberos.c         |  935 ++++++++++++++++++
 shell/ext-py/kerberos-1.3.1/src/kerberosbasic.c    |  171 ++++
 shell/ext-py/kerberos-1.3.1/src/kerberosbasic.h    |   26 +
 shell/ext-py/kerberos-1.3.1/src/kerberosgss.c      | 1007 ++++++++++++++++++++
 shell/ext-py/kerberos-1.3.1/src/kerberosgss.h      |   91 ++
 shell/ext-py/kerberos-1.3.1/src/kerberospw.c       |  172 ++++
 shell/ext-py/kerberos-1.3.1/src/kerberospw.h       |   26 +
 20 files changed, 3603 insertions(+)

diff --git a/bin/rat_exclude_files.txt b/bin/rat_exclude_files.txt
index db2c6017c..44917e1d8 100644
--- a/bin/rat_exclude_files.txt
+++ b/bin/rat_exclude_files.txt
@@ -46,6 +46,7 @@ tests/comparison/leopard/static/css/bootstrap*
 tests/comparison/leopard/static/fonts/glyphicons-halflings*
 tests/comparison/leopard/static/js/bootstrap*
 shell/ext-py/bitarray-2.3.0/*
+shell/ext-py/kerberos-1.3.1/*
 shell/ext-py/prettytable-0.7.2/*
 shell/ext-py/sasl-0.2.1/*
 shell/ext-py/six-1.14.0/*
diff --git a/shell/ext-py/kerberos-1.3.1/MANIFEST.in b/shell/ext-py/kerberos-1.3.1/MANIFEST.in
new file mode 100644
index 000000000..39d92ed76
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/MANIFEST.in
@@ -0,0 +1,3 @@
+include LICENSE README.md
+recursive-include src *.c *.h
+recursive-include pysrc *.py
diff --git a/shell/ext-py/kerberos-1.3.1/PKG-INFO b/shell/ext-py/kerberos-1.3.1/PKG-INFO
new file mode 100644
index 000000000..2d7d27866
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/PKG-INFO
@@ -0,0 +1,138 @@
+Metadata-Version: 2.1
+Name: kerberos
+Version: 1.3.1
+Summary: Kerberos high-level interface
+Home-page: https://github.com/apple/ccs-pykerberos
+Author: Apple Inc.
+Author-email: calendarserver-dev@lists.macosforge.org
+License: Apache License, Version 2.0
+Description: # PyKerberos Package
+        
+        This Python package is a high-level wrapper for Kerberos (GSSAPI)
+        operations.  The goal is to avoid having to build a module that wraps
+        the entire Kerberos.framework, and instead offer a limited set of
+        functions that do what is needed for client/server Kerberos
+        authentication based on <http://www.ietf.org/rfc/rfc4559.txt>.
+        
+        Much of the C-code here is adapted from Apache's mod_auth_kerb-5.0rc7.
+        
+        
+        ## Build
+        
+        In this directory, run:
+        
+        ```
+        python setup.py build
+        ```
+        
+        ## Testing
+        
+        To run the tests in the tests folder, you must have a valid Kerberos setup on
+        the test machine. You can use the script .travis.sh as quick and easy way to
+        setup a Kerberos KDC and Apache web endpoint that can be used for the tests.
+        Otherwise you can also run the following to run a self contained Docker
+        container
+        
+        ```
+        docker run \
+        -v $(pwd):/app \
+        -w /app \
+        -e PYENV=2.7.13 \
+        -e KERBEROS_USERNAME=administrator \
+        -e KERBEROS_PASSWORD=Password01 \
+        -e KERBEROS_REALM=example.com \
+        -e KERBEROS_PORT=80 \
+        ubuntu:16.04 \
+        /bin/bash .travis.sh
+        ```
+        
+        The docker command needs to be run in the same directory as this library and
+        you can test it with different Python versions by changing the value of the
+        PYENV environment value set in the command.
+        
+        Please have a look at testing_notes.md for more information.
+        
+        
+        ## IMPORTANT
+        
+        The checkPassword method provided by this library is meant only for testing purposes as it does
+        not offer any protection against possible KDC spoofing. That method should not be used in any
+        production code.
+        
+        
+        ## Channel Bindings
+        
+        You can use this library to authenticate with Channel Binding support. Channel
+        Bindings are tags that identify the particular data channel being used with the
+        authentication. You can use Channel bindings to offer more proof of a valid
+        identity. Some services like Microsoft's Extended Protection can enforce
+        Channel Binding support on authorisation and you can use this library to meet
+        those requirements.
+        
+        More details on Channel Bindings as set through the GSSAPI can be found here
+        <https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html>. Using
+        TLS as a example this is how you would add Channel Binding support to your
+        authentication mechanism. The following code snippet is based on RFC5929
+        <https://tools.ietf.org/html/rfc5929> using the 'tls-server-endpoint-point'
+        type.
+        
+        ```
+        import hashlib
+        
+        def get_channel_bindings_application_data(socket):
+            # This is a highly simplified example, there are other use cases
+            # where you might need to use different hash types or get a socket
+            # object somehow.
+            server_certificate = socket.getpeercert(True)
+            certificate_hash = hashlib.sha256(server_certificate).hexdigest().upper()
+            certificate_digest = base64.b16decode(certificate_hash)
+            application_data = b'tls-server-end-point:%s' % certificate_digest
+        
+            return application_data
+        
+        def main():
+            # Code to setup a socket with the server
+            # A lot of code to setup the handshake and start the auth process
+            socket = getsocketsomehow()
+        
+            # Connect to the host and start the auth process
+        
+            # Build the channel bindings object
+            application_data = get_channel_bindings_application_data(socket)
+            channel_bindings = kerberos.channelBindings(application_data=application_data)
+        
+            # More work to get responses from the server
+        
+            result, context = kerberos.authGSSClientInit(kerb_spn, gssflags=gssflags, principal=principal)
+        
+            # Pass through the channel_bindings object as created in the kerberos.channelBindings method
+            result = kerberos.authGSSClientStep(context, neg_resp_value, channel_bindings=channel_bindings)
+        
+            # Repeat as necessary
+        ```
+        
+        ## Python APIs
+        
+        See kerberos.py.
+        
+        
+        ## Copyright and License
+        
+        Copyright (c) 2006-2021 Apple Inc.  All rights reserved.
+        
+        This software is licensed under the Apache License, Version 2.0.  The
+        Apache License is a well-established open source license, enabling
+        collaborative open source software development.
+        
+        See the "LICENSE" file for the full text of the license terms.
+        
+Platform: all
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
+Description-Content-Type: text/markdown
diff --git a/shell/ext-py/kerberos-1.3.1/README.md b/shell/ext-py/kerberos-1.3.1/README.md
new file mode 100644
index 000000000..ce8f3c5cf
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/README.md
@@ -0,0 +1,119 @@
+# PyKerberos Package
+
+This Python package is a high-level wrapper for Kerberos (GSSAPI)
+operations.  The goal is to avoid having to build a module that wraps
+the entire Kerberos.framework, and instead offer a limited set of
+functions that do what is needed for client/server Kerberos
+authentication based on <http://www.ietf.org/rfc/rfc4559.txt>.
+
+Much of the C-code here is adapted from Apache's mod_auth_kerb-5.0rc7.
+
+
+## Build
+
+In this directory, run:
+
+```
+python setup.py build
+```
+
+## Testing
+
+To run the tests in the tests folder, you must have a valid Kerberos setup on
+the test machine. You can use the script .travis.sh as quick and easy way to
+setup a Kerberos KDC and Apache web endpoint that can be used for the tests.
+Otherwise you can also run the following to run a self contained Docker
+container
+
+```
+docker run \
+-v $(pwd):/app \
+-w /app \
+-e PYENV=2.7.13 \
+-e KERBEROS_USERNAME=administrator \
+-e KERBEROS_PASSWORD=Password01 \
+-e KERBEROS_REALM=example.com \
+-e KERBEROS_PORT=80 \
+ubuntu:16.04 \
+/bin/bash .travis.sh
+```
+
+The docker command needs to be run in the same directory as this library and
+you can test it with different Python versions by changing the value of the
+PYENV environment value set in the command.
+
+Please have a look at testing_notes.md for more information.
+
+
+## IMPORTANT
+
+The checkPassword method provided by this library is meant only for testing purposes as it does
+not offer any protection against possible KDC spoofing. That method should not be used in any
+production code.
+
+
+## Channel Bindings
+
+You can use this library to authenticate with Channel Binding support. Channel
+Bindings are tags that identify the particular data channel being used with the
+authentication. You can use Channel bindings to offer more proof of a valid
+identity. Some services like Microsoft's Extended Protection can enforce
+Channel Binding support on authorisation and you can use this library to meet
+those requirements.
+
+More details on Channel Bindings as set through the GSSAPI can be found here
+<https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html>. Using
+TLS as a example this is how you would add Channel Binding support to your
+authentication mechanism. The following code snippet is based on RFC5929
+<https://tools.ietf.org/html/rfc5929> using the 'tls-server-endpoint-point'
+type.
+
+```
+import hashlib
+
+def get_channel_bindings_application_data(socket):
+    # This is a highly simplified example, there are other use cases
+    # where you might need to use different hash types or get a socket
+    # object somehow.
+    server_certificate = socket.getpeercert(True)
+    certificate_hash = hashlib.sha256(server_certificate).hexdigest().upper()
+    certificate_digest = base64.b16decode(certificate_hash)
+    application_data = b'tls-server-end-point:%s' % certificate_digest
+
+    return application_data
+
+def main():
+    # Code to setup a socket with the server
+    # A lot of code to setup the handshake and start the auth process
+    socket = getsocketsomehow()
+
+    # Connect to the host and start the auth process
+
+    # Build the channel bindings object
+    application_data = get_channel_bindings_application_data(socket)
+    channel_bindings = kerberos.channelBindings(application_data=application_data)
+
+    # More work to get responses from the server
+
+    result, context = kerberos.authGSSClientInit(kerb_spn, gssflags=gssflags, principal=principal)
+
+    # Pass through the channel_bindings object as created in the kerberos.channelBindings method
+    result = kerberos.authGSSClientStep(context, neg_resp_value, channel_bindings=channel_bindings)
+
+    # Repeat as necessary
+```
+
+## Python APIs
+
+See kerberos.py.
+
+
+## Copyright and License
+
+Copyright (c) 2006-2021 Apple Inc.  All rights reserved.
+
+This software is licensed under the Apache License, Version 2.0.  The
+Apache License is a well-established open source license, enabling
+collaborative open source software development.
+
+See the "LICENSE" file for the full text of the license terms.
diff --git a/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/PKG-INFO b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/PKG-INFO
new file mode 100644
index 000000000..2d7d27866
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/PKG-INFO
@@ -0,0 +1,138 @@
+Metadata-Version: 2.1
+Name: kerberos
+Version: 1.3.1
+Summary: Kerberos high-level interface
+Home-page: https://github.com/apple/ccs-pykerberos
+Author: Apple Inc.
+Author-email: calendarserver-dev@lists.macosforge.org
+License: Apache License, Version 2.0
+Description: # PyKerberos Package
+        
+        This Python package is a high-level wrapper for Kerberos (GSSAPI)
+        operations.  The goal is to avoid having to build a module that wraps
+        the entire Kerberos.framework, and instead offer a limited set of
+        functions that do what is needed for client/server Kerberos
+        authentication based on <http://www.ietf.org/rfc/rfc4559.txt>.
+        
+        Much of the C-code here is adapted from Apache's mod_auth_kerb-5.0rc7.
+        
+        
+        ## Build
+        
+        In this directory, run:
+        
+        ```
+        python setup.py build
+        ```
+        
+        ## Testing
+        
+        To run the tests in the tests folder, you must have a valid Kerberos setup on
+        the test machine. You can use the script .travis.sh as quick and easy way to
+        setup a Kerberos KDC and Apache web endpoint that can be used for the tests.
+        Otherwise you can also run the following to run a self contained Docker
+        container
+        
+        ```
+        docker run \
+        -v $(pwd):/app \
+        -w /app \
+        -e PYENV=2.7.13 \
+        -e KERBEROS_USERNAME=administrator \
+        -e KERBEROS_PASSWORD=Password01 \
+        -e KERBEROS_REALM=example.com \
+        -e KERBEROS_PORT=80 \
+        ubuntu:16.04 \
+        /bin/bash .travis.sh
+        ```
+        
+        The docker command needs to be run in the same directory as this library and
+        you can test it with different Python versions by changing the value of the
+        PYENV environment value set in the command.
+        
+        Please have a look at testing_notes.md for more information.
+        
+        
+        ## IMPORTANT
+        
+        The checkPassword method provided by this library is meant only for testing purposes as it does
+        not offer any protection against possible KDC spoofing. That method should not be used in any
+        production code.
+        
+        
+        ## Channel Bindings
+        
+        You can use this library to authenticate with Channel Binding support. Channel
+        Bindings are tags that identify the particular data channel being used with the
+        authentication. You can use Channel bindings to offer more proof of a valid
+        identity. Some services like Microsoft's Extended Protection can enforce
+        Channel Binding support on authorisation and you can use this library to meet
+        those requirements.
+        
+        More details on Channel Bindings as set through the GSSAPI can be found here
+        <https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html>. Using
+        TLS as a example this is how you would add Channel Binding support to your
+        authentication mechanism. The following code snippet is based on RFC5929
+        <https://tools.ietf.org/html/rfc5929> using the 'tls-server-endpoint-point'
+        type.
+        
+        ```
+        import hashlib
+        
+        def get_channel_bindings_application_data(socket):
+            # This is a highly simplified example, there are other use cases
+            # where you might need to use different hash types or get a socket
+            # object somehow.
+            server_certificate = socket.getpeercert(True)
+            certificate_hash = hashlib.sha256(server_certificate).hexdigest().upper()
+            certificate_digest = base64.b16decode(certificate_hash)
+            application_data = b'tls-server-end-point:%s' % certificate_digest
+        
+            return application_data
+        
+        def main():
+            # Code to setup a socket with the server
+            # A lot of code to setup the handshake and start the auth process
+            socket = getsocketsomehow()
+        
+            # Connect to the host and start the auth process
+        
+            # Build the channel bindings object
+            application_data = get_channel_bindings_application_data(socket)
+            channel_bindings = kerberos.channelBindings(application_data=application_data)
+        
+            # More work to get responses from the server
+        
+            result, context = kerberos.authGSSClientInit(kerb_spn, gssflags=gssflags, principal=principal)
+        
+            # Pass through the channel_bindings object as created in the kerberos.channelBindings method
+            result = kerberos.authGSSClientStep(context, neg_resp_value, channel_bindings=channel_bindings)
+        
+            # Repeat as necessary
+        ```
+        
+        ## Python APIs
+        
+        See kerberos.py.
+        
+        
+        ## Copyright and License
+        
+        Copyright (c) 2006-2021 Apple Inc.  All rights reserved.
+        
+        This software is licensed under the Apache License, Version 2.0.  The
+        Apache License is a well-established open source license, enabling
+        collaborative open source software development.
+        
+        See the "LICENSE" file for the full text of the license terms.
+        
+Platform: all
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
+Description-Content-Type: text/markdown
diff --git a/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/SOURCES.txt b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/SOURCES.txt
new file mode 100644
index 000000000..230d66dce
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/SOURCES.txt
@@ -0,0 +1,18 @@
+MANIFEST.in
+README.md
+setup.cfg
+setup.py
+kerberos.egg-info/PKG-INFO
+kerberos.egg-info/SOURCES.txt
+kerberos.egg-info/dependency_links.txt
+kerberos.egg-info/top_level.txt
+pysrc/kerberos.py
+src/base64.c
+src/base64.h
+src/kerberos.c
+src/kerberosbasic.c
+src/kerberosbasic.h
+src/kerberosgss.c
+src/kerberosgss.h
+src/kerberospw.c
+src/kerberospw.h
\ No newline at end of file
diff --git a/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/dependency_links.txt b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/dependency_links.txt
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/top_level.txt b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/top_level.txt
new file mode 100644
index 000000000..4fb686928
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/kerberos.egg-info/top_level.txt
@@ -0,0 +1 @@
+kerberos
diff --git a/shell/ext-py/kerberos-1.3.1/pysrc/kerberos.py b/shell/ext-py/kerberos-1.3.1/pysrc/kerberos.py
new file mode 100644
index 000000000..d4f53a6bc
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/pysrc/kerberos.py
@@ -0,0 +1,461 @@
+##
+# Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+PyKerberos Function Description.
+"""
+
+
+
+class KrbError(Exception):
+    pass
+
+
+
+class BasicAuthError(KrbError):
+    pass
+
+
+
+class GSSError(KrbError):
+    pass
+
+
+
+def checkPassword(user, pswd, service, default_realm):
+    """
+    This function provides a simple way to verify that a user name and password
+    match those normally used for Kerberos authentication.
+    It does this by checking that the supplied user name and password can be
+    used to get a ticket for the supplied service.
+    If the user name does not contain a realm, then the default realm supplied
+    is used.
+
+    For this to work properly the Kerberos must be configured properly on this
+    machine.
+    That will likely mean ensuring that the edu.mit.Kerberos preference file
+    has the correct realms and KDCs listed.
+
+    IMPORTANT: This method is vulnerable to KDC spoofing attacks and it should
+    only used for testing. Do not use this in any production system - your
+    security could be compromised if you do.
+
+    @param user: A string containing the Kerberos user name.
+        A realm may be included by appending an C{"@"} followed by the realm
+        string to the actual user id.
+        If no realm is supplied, then the realm set in the default_realm
+        argument will be used.
+
+    @param pswd: A string containing the password for the user.
+
+    @param service: A string containing the Kerberos service to check access
+        for.
+        This will be of the form C{"sss/xx.yy.zz"}, where C{"sss"} is the
+        service identifier (e.g., C{"http"}, C{"krbtgt"}), and C{"xx.yy.zz"} is
+        the hostname of the server.
+
+    @param default_realm: A string containing the default realm to use if one
+        is not supplied in the user argument.
+        Note that Kerberos realms are normally all uppercase (e.g.,
+        C{"EXAMPLE.COM"}).
+
+    @return: True if authentication succeeds, false otherwise.
+    """
+
+
+
+def changePassword(user, oldpswd, newpswd):
+    """
+    This function allows to change the user password on the KDC.
+
+    @param user: A string containing the Kerberos user name.
+        A realm may be included by appending a C{"@"} followed by the realm
+        string to the actual user id.
+        If no realm is supplied, then the realm set in the default_realm
+        argument will be used.
+
+    @param oldpswd: A string containing the old (current) password for the
+        user.
+
+    @param newpswd: A string containing the new password for the user.
+
+    @return: True if password changing succeeds, false otherwise.
+    """
+
+
+
+def getServerPrincipalDetails(service, hostname):
+    """
+    This function returns the service principal for the server given a service
+    type and hostname.
+    Details are looked up via the C{/etc/keytab} file.
+
+    @param service: A string containing the Kerberos service type for the
+        server.
+
+    @param hostname: A string containing the hostname of the server.
+
+    @return: A string containing the service principal.
+    """
+
+
+
+"""
+GSSAPI Function Result Codes:
+
+    -1 : Error
+    0  : GSSAPI step continuation (only returned by 'Step' function)
+    1  : GSSAPI step complete, or function return OK
+
+"""
+
+# Some useful result codes
+AUTH_GSS_CONTINUE     = 0
+AUTH_GSS_COMPLETE     = 1
+
+# Some useful gss flags
+GSS_C_DELEG_FLAG      = 1
+GSS_C_MUTUAL_FLAG     = 2
+GSS_C_REPLAY_FLAG     = 4
+GSS_C_SEQUENCE_FLAG   = 8
+GSS_C_CONF_FLAG       = 16
+GSS_C_INTEG_FLAG      = 32
+GSS_C_ANON_FLAG       = 64
+GSS_C_PROT_READY_FLAG = 128
+GSS_C_TRANS_FLAG      = 256
+
+
+
+def authGSSClientInit(service, **kwargs):
+    """
+    Initializes a context for GSSAPI client-side authentication with the given
+    service principal.
+    L{authGSSClientClean} must be called after this function returns an OK
+    result to dispose of the context once all GSSAPI operations are complete.
+
+    @param service: A string containing the service principal in the form
+        C{"type@fqdn"}.
+
+    @param principal: Optional string containing the client principal in the
+        form C{"user@realm"}.
+
+    @param gssflags: Optional integer used to set GSS flags.
+        (e.g. C{GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG|GSS_C_SEQUENCE_FLAG} will
+        allow for forwarding credentials to the remote host)
+
+    @param delegated: Optional server context containing delegated credentials
+
+    @param mech_oid: Optional GGS mech OID
+
+    @return: A tuple of (result, context) where result is the result code (see
+        above) and context is an opaque value that will need to be passed to
+        subsequent functions.
+    """
+
+
+
+def authGSSClientClean(context):
+    """
+    Destroys the context for GSSAPI client-side authentication. This function
+    is provided for compatibility with earlier versions of PyKerberos but does
+    nothing. The context object destroys itself when it is reclaimed.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A result code (see above).
+    """
+
+
+
+def authGSSClientInquireCred(context):
+    """
+    Get the current user name, if any, without a client-side GSSAPI step.
+    If the principal has already been authenticated via completed client-side
+    GSSAPI steps then the user name of the authenticated principal is kept. The
+    user name will be available via authGSSClientUserName.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A result code (see above).
+    """
+
+
+
+"""
+Address Types for Channel Bindings
+https://docs.oracle.com/cd/E19455-01/806-3814/6jcugr7dp/index.html#reference-9
+
+"""
+
+GSS_C_AF_UNSPEC    = 0
+GSS_C_AF_LOCAL     = 1
+GSS_C_AF_INET      = 2
+GSS_C_AF_IMPLINK   = 3
+GSS_C_AF_PUP       = 4
+GSS_C_AF_CHAOS     = 5
+GSS_C_AF_NS        = 6
+GSS_C_AF_NBS       = 7
+GSS_C_AF_ECMA      = 8
+GSS_C_AF_DATAKIT   = 9
+GSS_C_AF_CCITT     = 10
+GSS_C_AF_SNA       = 11
+GSS_C_AF_DECnet    = 12
+GSS_C_AF_DLI       = 13
+GSS_C_AF_LAT       = 14
+GSS_C_AF_HYLINK    = 15
+GSS_C_AF_APPLETALK = 16
+GSS_C_AF_BSC       = 17
+GSS_C_AF_DSS       = 18
+GSS_C_AF_OSI       = 19
+GSS_C_AF_X25       = 21
+GSS_C_AF_NULLADDR  = 255
+
+
+
+def channelBindings(**kwargs):
+    """
+    Builds a gss_channel_bindings_struct which can be used to pass onto
+    L{authGSSClientStep} to bind onto the auth. Details on Channel Bindings
+    can be foud at https://tools.ietf.org/html/rfc5929. More details on the
+    struct can be found at
+    https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html
+
+    @param initiator_addrtype: Optional integer used to set the
+        initiator_addrtype, defaults to GSS_C_AF_UNSPEC if not set
+
+    @param initiator_address: Optional byte string containing the
+        initiator_address
+
+    @param acceptor_addrtype: Optional integer used to set the
+        acceptor_addrtype, defaults to GSS_C_AF_UNSPEC if not set
+
+    @param acceptor_address: Optional byte string containing the
+        acceptor_address
+
+    @param application_data: Optional byte string containing the
+        application_data. An example would be 'tls-server-end-point:{cert-hash}'
+        where {cert-hash} is the hash of the server's certificate
+
+    @return: A tuple of (result, gss_channel_bindings_struct) where result is
+        the result code and gss_channel_bindings_struct is the channel bindings
+        structure that can be passed onto L{authGSSClientStep}
+    """
+
+
+
+def authGSSClientStep(context, challenge, **kwargs):
+    """
+    Processes a single GSSAPI client-side step using the supplied server data.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @param challenge: A string containing the base64-encoded server data (which
+        may be empty for the first step).
+
+    @param channel_bindings: Optional channel bindings to bind onto the auth
+        request. This struct can be built using :{channelBindings}
+        and if not specified it will pass along GSS_C_NO_CHANNEL_BINDINGS as
+        a default.
+
+    @return: A result code (see above).
+    """
+
+
+
+def authGSSClientResponse(context):
+    """
+    Get the client response from the last successful GSSAPI client-side step.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the base64-encoded client data to be sent to
+        the server.
+    """
+
+
+
+def authGSSClientResponseConf(context):
+    """
+    Determine whether confidentiality was enabled in the previously unwrapped
+    buffer.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: C{1} if confidentiality was enabled in the previously unwrapped
+        buffer, C{0} otherwise.
+    """
+
+
+
+def authGSSClientUserName(context):
+    """
+    Get the user name of the principal authenticated via the now complete
+    GSSAPI client-side operations, or the current user name obtained via
+    authGSSClientInquireCred. This method must only be called after
+    authGSSClientStep or authGSSClientInquireCred return a complete response
+    code.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the user name.
+    """
+
+
+
+def authGSSClientUnwrap(context, challenge):
+    """
+    Perform the client side GSSAPI unwrap step.
+
+    @param challenge: A string containing the base64-encoded server data.
+
+    @return: A result code (see above)
+    """
+
+
+
+def authGSSClientWrap(context, data, user=None, protect=0):
+    """
+    Perform the client side GSSAPI wrap step.
+
+    @param data: The result of the L{authGSSClientResponse} after the
+        L{authGSSClientUnwrap}.
+
+    @param user: The user to authorize.
+
+    @param protect: If C{0}, then just provide integrity protection.
+        If C{1}, then provide confidentiality as well.
+
+    @return: A result code (see above)
+    """
+
+
+
+def authGSSServerInit(service):
+    """
+    Initializes a context for GSSAPI server-side authentication with the given
+    service principal.
+    authGSSServerClean must be called after this function returns an OK result
+    to dispose of the context once all GSSAPI operations are complete.
+
+    @param service: A string containing the service principal in the form
+        C{"type@fqdn"}. To initialize the context for the purpose of accepting
+        delegated credentials, pass the literal string C{"DELEGATE"}.
+
+    @return: A tuple of (result, context) where result is the result code (see
+        above) and context is an opaque value that will need to be passed to
+        subsequent functions.
+    """
+
+
+
+def authGSSServerClean(context):
+    """
+    Destroys the context for GSSAPI server-side authentication. This function
+    is provided for compatibility with earlier versions of PyKerberos but does
+    nothing. The context object destroys itself when it is reclaimed.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A result code (see above).
+    """
+
+
+
+def authGSSServerStep(context, challenge):
+    """
+    Processes a single GSSAPI server-side step using the supplied client data.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @param challenge: A string containing the base64-encoded client data.
+
+    @return: A result code (see above).
+    """
+
+
+
+def authGSSServerResponse(context):
+    """
+    Get the server response from the last successful GSSAPI server-side step.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the base64-encoded server data to be sent to
+        the client.
+    """
+
+
+
+def authGSSServerHasDelegated(context):
+    """
+    Checks whether a server context has delegated credentials.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A bool saying whether delegated credentials are available.
+    """
+
+
+
+def authGSSServerUserName(context):
+    """
+    Get the user name of the principal trying to authenticate to the server.
+    This method must only be called after L{authGSSServerStep} returns a
+    complete or continue response code.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the user name.
+    """
+
+
+
+def authGSSServerTargetName(context):
+    """
+    Get the target name if the server did not supply its own credentials.
+    This method must only be called after L{authGSSServerStep} returns a
+    complete or continue response code.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the target name.
+    """
+
+
+
+def authGSSServerStoreDelegate(context):
+    """
+    Save the ticket sent to the server in the file C{/tmp/krb5_pyserv_XXXXXX}.
+    This method must only be called after L{authGSSServerStep} returns a
+    complete or continue response code.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A result code (see above).
+    """
+
+
+
+def authGSSServerCacheName(context):
+    """
+    Get the name of the credential cache created with
+    L{authGSSServerStoreDelegate}.
+    This method must only be called after L{authGSSServerStoreDelegate}.
+
+    @param context: The context object returned from L{authGSSClientInit}.
+
+    @return: A string containing the cache name.
+    """
diff --git a/shell/ext-py/kerberos-1.3.1/setup.cfg b/shell/ext-py/kerberos-1.3.1/setup.cfg
new file mode 100644
index 000000000..8bfd5a12f
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/shell/ext-py/kerberos-1.3.1/setup.py b/shell/ext-py/kerberos-1.3.1/setup.py
new file mode 100644
index 000000000..8bc0c4f6a
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/setup.py
@@ -0,0 +1,138 @@
+##
+# Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from os.path import dirname, join as joinpath
+from setuptools import setup, Extension
+from io import open
+
+try:
+    from subprocess import getoutput
+except ImportError:
+    from commands import getoutput
+
+
+#
+# Options
+#
+
+project_name = "kerberos"
+
+version_string = "1.3.1"
+
+description = "Kerberos high-level interface"
+
+with open("README.md", "r", encoding="utf-8") as fh:
+    long_description = fh.read()
+
+long_description_content_type = "text/markdown"
+
+url = "https://github.com/apple/ccs-pykerberos"
+
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: Apache Software License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 2",
+    "Programming Language :: Python :: 3",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: System :: Systems Administration :: Authentication/Directory",
+]
+
+author = "Apple Inc."
+
+author_email = "calendarserver-dev@lists.macosforge.org"
+
+license = "Apache License, Version 2.0"
+
+platforms = ["all"]
+
+
+#
+# Entry points
+#
+
+entry_points = {
+    "console_scripts": [],
+}
+
+
+#
+# Dependencies
+#
+
+setup_requirements = []
+
+install_requirements = []
+
+extras_requirements = {}
+
+extra_link_args = getoutput("krb5-config --libs gssapi").split()
+
+extra_compile_args = getoutput("krb5-config --cflags gssapi").split()
+
+
+#
+# Set up Extension modules that need to be built
+#
+
+extensions = [
+    Extension(
+        "kerberos",
+        extra_link_args=extra_link_args,
+        extra_compile_args=extra_compile_args,
+        sources=[
+            "src/base64.c",
+            "src/kerberos.c",
+            "src/kerberosbasic.c",
+            "src/kerberosgss.c",
+            "src/kerberospw.c",
+        ],
+    ),
+]
+
+
+#
+# Run setup
+#
+
+
+def doSetup():
+    setup(
+        name=project_name,
+        version=version_string,
+        description=description,
+        long_description=long_description,
+        long_description_content_type=long_description_content_type,
+        url=url,
+        classifiers=classifiers,
+        author=author,
+        author_email=author_email,
+        license=license,
+        platforms=platforms,
+        ext_modules=extensions,
+        setup_requires=setup_requirements,
+        install_requires=install_requirements,
+        extras_require=extras_requirements,
+    )
+
+
+#
+# Main
+#
+
+if __name__ == "__main__":
+    doSetup()
diff --git a/shell/ext-py/kerberos-1.3.1/src/base64.c b/shell/ext-py/kerberos-1.3.1/src/base64.c
new file mode 100644
index 000000000..4496f92e2
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/base64.c
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include "base64.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+// base64 tables
+static char basis_64[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static signed char index_64[128] =
+{
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+    52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
+    -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
+    15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+    -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+    41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+};
+#define CHAR64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
+
+// base64_encode    :    base64 encode
+//
+// value            :    data to encode
+// vlen             :    length of data
+// (result)         :    new char[] - c-str of result
+char *base64_encode(const unsigned char *value, size_t vlen)
+{
+    char *result = (char *)malloc((vlen * 4) / 3 + 5);
+    if (result == NULL)
+    {
+        return NULL;
+    }
+    char *out = result;
+    while (vlen >= 3)
+    {
+        *out++ = basis_64[value[0] >> 2];
+        *out++ = basis_64[((value[0] << 4) & 0x30) | (value[1] >> 4)];
+        *out++ = basis_64[((value[1] << 2) & 0x3C) | (value[2] >> 6)];
+        *out++ = basis_64[value[2] & 0x3F];
+        value += 3;
+        vlen -= 3;
+    }
+    if (vlen > 0)
+    {
+        *out++ = basis_64[value[0] >> 2];
+        unsigned char oval = (value[0] << 4) & 0x30;
+        if (vlen > 1) oval |= value[1] >> 4;
+        *out++ = basis_64[oval];
+        *out++ = (vlen < 2) ? '=' : basis_64[(value[1] << 2) & 0x3C];
+        *out++ = '=';
+    }
+    *out = '\0';
+
+    return result;
+}
+
+// base64_decode    :    base64 decode
+//
+// value            :    c-str to decode
+// rlen             :    length of decoded result
+// (result)         :    new unsigned char[] - decoded result
+unsigned char *base64_decode(const char *value, size_t *rlen)
+{
+    *rlen = 0;
+    int c1, c2, c3, c4;
+
+    size_t vlen = strlen(value);
+    unsigned char *result =(unsigned char *)malloc((vlen * 3) / 4 + 1);
+    if (result == NULL)
+    {
+        return NULL;
+    }
+    unsigned char *out = result;
+
+    while (1) {
+        if (value[0]==0) {
+            return result;
+        }
+        c1 = value[0];
+        if (CHAR64(c1) == -1) {
+            goto base64_decode_error;;
+        }
+        c2 = value[1];
+        if (CHAR64(c2) == -1) {
+            goto base64_decode_error;;
+        }
+        c3 = value[2];
+        if ((c3 != '=') && (CHAR64(c3) == -1)) {
+            goto base64_decode_error;;
+        }
+        c4 = value[3];
+        if ((c4 != '=') && (CHAR64(c4) == -1)) {
+            goto base64_decode_error;;
+        }
+
+        value += 4;
+        *out++ = (CHAR64(c1) << 2) | (CHAR64(c2) >> 4);
+        *rlen += 1;
+
+        if (c3 != '=') {
+            *out++ = ((CHAR64(c2) << 4) & 0xf0) | (CHAR64(c3) >> 2);
+            *rlen += 1;
+
+            if (c4 != '=') {
+                *out++ = ((CHAR64(c3) << 6) & 0xc0) | CHAR64(c4);
+                *rlen += 1;
+            }
+        }
+    }
+
+base64_decode_error:
+    *result = 0;
+    *rlen = 0;
+
+    return result;
+}
diff --git a/shell/ext-py/kerberos-1.3.1/src/base64.h b/shell/ext-py/kerberos-1.3.1/src/base64.h
new file mode 100644
index 000000000..4ddff3e33
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/base64.h
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <stddef.h>
+
+char *base64_encode(const unsigned char *value, size_t vlen);
+unsigned char *base64_decode(const char *value, size_t *rlen);
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberos.c b/shell/ext-py/kerberos-1.3.1/src/kerberos.c
new file mode 100644
index 000000000..1e889dfd5
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberos.c
@@ -0,0 +1,935 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <Python.h>
+
+#include "kerberosbasic.h"
+#include "kerberospw.h"
+#include "kerberosgss.h"
+
+
+/*
+ * Support the Python 3 API while maintaining backward compatibility for the
+ * Python 2 API.
+ * Thanks to Lennart Regebro for http://python3porting.com/cextensions.html
+ */
+// Handle basic API changes
+#if PY_MAJOR_VERSION >= 3
+    // Basic renames (function parameters are the same)
+    // No more int objects
+    #define PyInt_FromLong PyLong_FromLong
+#endif
+
+#if PY_VERSION_HEX >= 0x03020000
+    // CObjects to Capsules
+    #define PyCObject_Check PyCapsule_CheckExact
+    #define PyCObject_SetVoidPtr PyCapsule_SetPointer
+
+    // More complex macros (function parameters are not the same)
+    // Note for PyCObject_FromVoidPtr, destr is now the third parameter
+    #define PyCObject_FromVoidPtr(cobj, destr) PyCapsule_New(cobj, NULL, destr)
+    #define PyCObject_AsVoidPtr(pobj) PyCapsule_GetPointer(pobj, NULL)
+#endif
+// Handle differences in module definition syntax and interface
+#if PY_MAJOR_VERSION >= 3
+    #define MOD_ERROR_VAL NULL
+    #define MOD_SUCCESS_VAL(val) val
+    #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+    #define MOD_DEF(ob, name, doc, methods) \
+          static struct PyModuleDef moduledef = { \
+            PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+          ob = PyModule_Create(&moduledef);
+#else
+    #define MOD_ERROR_VAL
+    #define MOD_SUCCESS_VAL(val)
+    #define MOD_INIT(name) void init##name(void)
+    #define MOD_DEF(ob, name, doc, methods) \
+          ob = Py_InitModule3(name, methods, doc);
+#endif
+
+typedef union { char b[16]; uint64_t ull[2]; } align16;
+typedef union { char b[8]; uint64_t ull; } align8;
+
+static align16 krb5_mech_oid_bytes = { { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 } };
+gss_OID_desc krb5_mech_oid = { 9, NULL };
+
+static align8 spnego_mech_oid_bytes = { { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 } };
+gss_OID_desc spnego_mech_oid = { 6, NULL };
+
+PyObject *KrbException_class;
+PyObject *BasicAuthException_class;
+PyObject *PwdChangeException_class;
+PyObject *GssException_class;
+
+static PyObject *checkPassword(PyObject *self, PyObject *args)
+{
+    const char *user = NULL;
+    const char *pswd = NULL;
+    const char *service = NULL;
+    const char *default_realm = NULL;
+    int result = 0;
+
+    if (! PyArg_ParseTuple(args, "ssss", &user, &pswd, &service, &default_realm)) {
+        return NULL;
+    }
+
+    result = authenticate_user_krb5pwd(user, pswd, service, default_realm);
+
+    if (result) {
+        return Py_INCREF(Py_True), Py_True;
+    } else {
+        return NULL;
+    }
+}
+
+static PyObject *changePassword(PyObject *self, PyObject *args)
+{
+    const char *newpswd = NULL;
+    const char *oldpswd = NULL;
+    const char *user = NULL;
+    int result = 0;
+
+    if (! PyArg_ParseTuple(args, "sss", &user, &oldpswd, &newpswd)) {
+        return NULL;
+    }
+
+    result = change_user_krb5pwd(user, oldpswd, newpswd);
+
+    if (result) {
+        return Py_INCREF(Py_True), Py_True;
+    } else {
+        return NULL;
+    }
+}
+
+static PyObject *getServerPrincipalDetails(PyObject *self, PyObject *args)
+{
+    const char *service = NULL;
+    const char *hostname = NULL;
+    char* result = NULL;
+
+    if (! PyArg_ParseTuple(args, "ss", &service, &hostname)) {
+        return NULL;
+    }
+
+    result = server_principal_details(service, hostname);
+
+    if (result != NULL) {
+        PyObject* pyresult = Py_BuildValue("s", result);
+        free(result);
+        return pyresult;
+    } else {
+        return NULL;
+    }
+}
+
+static void
+#if PY_VERSION_HEX >= 0x03020000
+destroy_gss_client(PyObject *obj) {
+    gss_client_state *state = PyCapsule_GetPointer(obj, NULL);
+#else
+destroy_gss_client(void *obj) {
+    gss_client_state *state = (gss_client_state *)obj;
+#endif
+    if (state) {
+        authenticate_gss_client_clean(state);
+        free(state);
+    }
+}
+
+static PyObject* authGSSClientInit(PyObject* self, PyObject* args, PyObject* keywds)
+{
+    const char *service = NULL;
+    const char *principal = NULL;
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+    gss_server_state *delegatestate = NULL;
+    PyObject *pydelegatestate = NULL;
+    gss_OID mech_oid = GSS_C_NO_OID;
+    PyObject *pymech_oid = NULL;
+    static char *kwlist[] = {
+        "service", "principal", "gssflags", "delegated", "mech_oid", NULL
+    };
+    long int gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
+    int result = 0;
+
+    if (! PyArg_ParseTupleAndKeywords(
+        args, keywds, "s|zlOO", kwlist,
+        &service, &principal, &gss_flags, &pydelegatestate, &pymech_oid
+    )) {
+        return NULL;
+    }
+
+    state = (gss_client_state *) malloc(sizeof(gss_client_state));
+    if (state == NULL)
+    {
+        PyErr_NoMemory();
+        return NULL;
+    }
+    pystate = PyCObject_FromVoidPtr(state, &destroy_gss_client);
+    if (pystate == NULL) {
+        free(state);
+        return NULL;
+    }
+
+    if (pydelegatestate != NULL && PyCObject_Check(pydelegatestate)) {
+        delegatestate = (gss_server_state*)PyCObject_AsVoidPtr(pydelegatestate);
+    }
+
+    if (pymech_oid != NULL && PyCObject_Check(pymech_oid)) {
+        mech_oid = (gss_OID)PyCObject_AsVoidPtr(pymech_oid);
+    }
+
+    result = authenticate_gss_client_init(
+        service, principal, gss_flags, delegatestate, mech_oid, state
+    );
+
+    if (result == AUTH_GSS_ERROR) {
+        Py_DECREF(pystate);
+        return NULL;
+    }
+
+    return Py_BuildValue("(iN)", result, pystate);
+}
+
+static PyObject *authGSSClientClean(PyObject *self, PyObject *args)
+{
+    return Py_BuildValue("i", AUTH_GSS_COMPLETE);
+}
+
+#if PY_VERSION_HEX >= 0x03020000
+void destruct_channel_bindings(PyObject* o) {
+    struct gss_channel_bindings_struct *channel_bindings = PyCapsule_GetPointer(o, NULL);
+#else
+void destruct_channel_bindings(void* o) {
+    struct gss_channel_bindings_struct *channel_bindings = (struct gss_channel_bindings_struct *)o;
+#endif
+
+    if (channel_bindings != NULL) {
+        if (channel_bindings->initiator_address.value != NULL) {
+            PyMem_Free(channel_bindings->initiator_address.value);
+        }
+
+        if (channel_bindings->acceptor_address.value != NULL) {
+            PyMem_Free(channel_bindings->acceptor_address.value);
+        }
+
+        if (channel_bindings->application_data.value != NULL) {
+            PyMem_Free(channel_bindings->application_data.value);
+        }
+
+        free(channel_bindings);
+    }
+}
+
+static PyObject *channelBindings(PyObject *self, PyObject *args, PyObject* keywds)
+{
+    int initiator_addrtype = GSS_C_AF_UNSPEC;
+    int acceptor_addrtype = GSS_C_AF_UNSPEC;
+
+    const char *encoding = NULL;
+    char *initiator_address = NULL;
+    char *acceptor_address = NULL;
+    char *application_data = NULL;
+    int initiator_length = 0;
+    int acceptor_length = 0;
+    int application_length = 0;
+
+    PyObject *pychan_bindings = NULL;
+    struct gss_channel_bindings_struct *input_chan_bindings;
+    static char *kwlist[] = {"initiator_addrtype", "initiator_address", "acceptor_addrtype",
+        "acceptor_address", "application_data", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iet#iet#et#", kwlist,
+            &initiator_addrtype, &encoding, &initiator_address, &initiator_length,
+            &acceptor_addrtype, &encoding, &acceptor_address, &acceptor_length,
+            &encoding, &application_data, &application_length)) {
+        return NULL;
+    }
+
+    input_chan_bindings = (struct gss_channel_bindings_struct *) malloc(sizeof(struct gss_channel_bindings_struct));
+    pychan_bindings = PyCObject_FromVoidPtr(input_chan_bindings, &destruct_channel_bindings);
+
+    input_chan_bindings->initiator_addrtype = initiator_addrtype;
+    input_chan_bindings->initiator_address.length = initiator_length;
+    input_chan_bindings->initiator_address.value = initiator_address;
+
+    input_chan_bindings->acceptor_addrtype = acceptor_addrtype;
+    input_chan_bindings->acceptor_address.length = acceptor_length;
+    input_chan_bindings->acceptor_address.value = acceptor_address;
+
+    input_chan_bindings->application_data.length = application_length;
+    input_chan_bindings->application_data.value = application_data;
+
+    return Py_BuildValue("N", pychan_bindings);
+}
+
+static PyObject *authGSSClientStep(PyObject *self, PyObject *args, PyObject* keywds)
+{
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+    char *challenge = NULL;
+    PyObject *pychan_bindings = NULL;
+    struct gss_channel_bindings_struct *channel_bindings;
+    static char *kwlist[] = {"state", "challenge", "channel_bindings", NULL};
+    int result = 0;
+
+    if (! PyArg_ParseTupleAndKeywords(args, keywds, "Os|O", kwlist, &pystate, &challenge, &pychan_bindings)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    if (pychan_bindings == NULL) {
+        channel_bindings = GSS_C_NO_CHANNEL_BINDINGS;
+    } else {
+        if (!PyCObject_Check(pychan_bindings)) {
+            PyErr_SetString(PyExc_TypeError, "Expected a gss_channel_bindings_struct object");
+            return NULL;
+        }
+        channel_bindings = (struct gss_channel_bindings_struct *)PyCObject_AsVoidPtr(pychan_bindings);
+    }
+
+    result = authenticate_gss_client_step(state, challenge, channel_bindings);
+
+    if (result == AUTH_GSS_ERROR) {
+        return NULL;
+    }
+
+    return Py_BuildValue("i", result);
+}
+
+static PyObject *authGSSClientResponseConf(PyObject *self, PyObject *args)
+{
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return Py_BuildValue("i", state->responseConf);
+}
+
+static PyObject *authGSSServerHasDelegated(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return PyBool_FromLong(authenticate_gss_server_has_delegated(state));
+}
+
+static PyObject *authGSSClientResponse(PyObject *self, PyObject *args)
+{
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return Py_BuildValue("s", state->response);
+}
+
+static PyObject *authGSSClientUserName(PyObject *self, PyObject *args)
+{
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return Py_BuildValue("s", state->username);
+}
+
+static PyObject *authGSSClientUnwrap(PyObject *self, PyObject *args)
+{
+	gss_client_state *state = NULL;
+	PyObject *pystate = NULL;
+	char *challenge = NULL;
+	int result = 0;
+
+	if (! PyArg_ParseTuple(args, "Os", &pystate, &challenge)) {
+		return NULL;
+    }
+
+	if (! PyCObject_Check(pystate)) {
+		PyErr_SetString(PyExc_TypeError, "Expected a context object");
+		return NULL;
+	}
+
+	state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+	if (state == NULL) {
+		return NULL;
+    }
+
+	result = authenticate_gss_client_unwrap(state, challenge);
+
+	if (result == AUTH_GSS_ERROR) {
+		return NULL;
+    }
+
+	return Py_BuildValue("i", result);
+}
+
+static PyObject *authGSSClientWrap(PyObject *self, PyObject *args)
+{
+	gss_client_state *state = NULL;
+	PyObject *pystate = NULL;
+	char *challenge = NULL;
+	char *user = NULL;
+	int protect = 0;
+	int result = 0;
+
+	if (! PyArg_ParseTuple(
+        args, "Os|zi", &pystate, &challenge, &user, &protect
+    )) {
+		return NULL;
+    }
+
+	if (! PyCObject_Check(pystate)) {
+		PyErr_SetString(PyExc_TypeError, "Expected a context object");
+		return NULL;
+	}
+
+	state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+
+	if (state == NULL) {
+		return NULL;
+    }
+
+	result = authenticate_gss_client_wrap(state, challenge, user, protect);
+
+	if (result == AUTH_GSS_ERROR) {
+		return NULL;
+    }
+
+	return Py_BuildValue("i", result);
+}
+
+static PyObject *authGSSClientInquireCred(PyObject *self, PyObject *args)
+{
+    gss_client_state *state = NULL;
+    PyObject *pystate = NULL;
+    int result = 0;
+    if (!PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (!PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_client_state *)PyCObject_AsVoidPtr(pystate);
+    if (state == NULL) {
+        return NULL;
+    }
+
+    result = authenticate_gss_client_inquire_cred(state);
+    if (result == AUTH_GSS_ERROR) {
+        return NULL;
+    }
+
+    return Py_BuildValue("i", result);
+}
+
+static void
+#if PY_VERSION_HEX >= 0x03020000
+destroy_gss_server(PyObject *obj) {
+    gss_server_state *state = PyCapsule_GetPointer(obj, NULL);
+#else
+destroy_gss_server(void *obj) {
+    gss_server_state *state = (gss_server_state *)obj;
+#endif
+    if (state) {
+        authenticate_gss_server_clean(state);
+        free(state);
+    }
+}
+
+static PyObject *authGSSServerInit(PyObject *self, PyObject *args)
+{
+    const char *service = NULL;
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    int result = 0;
+
+    if (! PyArg_ParseTuple(args, "s", &service)) {
+        return NULL;
+    }
+
+    state = (gss_server_state *) malloc(sizeof(gss_server_state));
+    if (state == NULL)
+    {
+        PyErr_NoMemory();
+        return NULL;
+    }
+    pystate = PyCObject_FromVoidPtr(state, &destroy_gss_server);
+    if (pystate == NULL) {
+        free(state);
+        return NULL;
+    }
+
+    result = authenticate_gss_server_init(service, state);
+
+    if (result == AUTH_GSS_ERROR) {
+        Py_DECREF(pystate);
+        return NULL;
+    }
+
+    return Py_BuildValue("(iN)", result, pystate);
+}
+
+static PyObject *authGSSServerClean(PyObject *self, PyObject *args)
+{
+    return Py_BuildValue("i", AUTH_GSS_COMPLETE);
+}
+
+static PyObject *authGSSServerStep(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    char *challenge = NULL;
+    int result = 0;
+
+    if (! PyArg_ParseTuple(args, "Os", &pystate, &challenge)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    result = authenticate_gss_server_step(state, challenge);
+
+    if (result == AUTH_GSS_ERROR) {
+        return NULL;
+    }
+
+    return Py_BuildValue("i", result);
+}
+
+static PyObject *authGSSServerStoreDelegate(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    int result = 0;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    result = authenticate_gss_server_store_delegate(state);
+
+    if (result == AUTH_GSS_ERROR) {
+        return NULL;
+    }
+    
+    return Py_BuildValue("i", result);
+}
+
+static PyObject *authGSSServerResponse(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return Py_BuildValue("s", state->response);
+}
+
+static PyObject *authGSSServerUserName(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+    
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+    
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+    
+    return Py_BuildValue("s", state->username);
+}
+
+static PyObject *authGSSServerCacheName(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+    
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+    
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+
+    return Py_BuildValue("s", state->ccname);
+}
+
+static PyObject *authGSSServerTargetName(PyObject *self, PyObject *args)
+{
+    gss_server_state *state = NULL;
+    PyObject *pystate = NULL;
+    
+    if (! PyArg_ParseTuple(args, "O", &pystate)) {
+        return NULL;
+    }
+    
+    if (! PyCObject_Check(pystate)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a context object");
+        return NULL;
+    }
+    
+    state = (gss_server_state *)PyCObject_AsVoidPtr(pystate);
+
+    if (state == NULL) {
+        return NULL;
+    }
+    
+    return Py_BuildValue("s", state->targetname);
+}
+
+static PyMethodDef KerberosMethods[] = {
+    {
+        "checkPassword",
+        checkPassword, METH_VARARGS,
+        "Check the supplied user/password against Kerberos KDC."
+    },
+    {
+        "changePassword",
+        changePassword, METH_VARARGS,
+        "Change the user password."
+    },
+    {
+        "getServerPrincipalDetails",
+        getServerPrincipalDetails, METH_VARARGS,
+        "Return the service principal for a given service and hostname."
+    },
+    {
+        "authGSSClientInit",
+        (PyCFunction)authGSSClientInit, METH_VARARGS | METH_KEYWORDS,
+        "Initialize client-side GSSAPI operations."
+    },
+    {
+        "channelBindings",
+        (PyCFunction)channelBindings, METH_VARARGS | METH_KEYWORDS,
+        "Build the Channel Bindings Structure for authGSSClientStep."
+    },
+    {
+        "authGSSClientClean",
+        authGSSClientClean, METH_VARARGS,
+        "Terminate client-side GSSAPI operations."
+    },
+    {
+        "authGSSClientStep",
+        (PyCFunction)authGSSClientStep, METH_VARARGS | METH_KEYWORDS,
+        "Do a client-side GSSAPI step."
+    },
+    {
+        "authGSSClientResponse",
+        authGSSClientResponse, METH_VARARGS,
+        "Get the response from the last client-side GSSAPI step."
+    },
+    {
+        "authGSSClientInquireCred",  authGSSClientInquireCred, METH_VARARGS,
+        "Get the current user name, if any, without a client-side GSSAPI step"
+    },
+    {
+        "authGSSClientResponseConf",
+        authGSSClientResponseConf, METH_VARARGS,
+        "return 1 if confidentiality was set in the last unwrapped buffer, 0 otherwise."
+    },
+    {
+        "authGSSClientUserName",
+        authGSSClientUserName, METH_VARARGS,
+        "Get the user name from the last client-side GSSAPI step."
+    },
+    {
+        "authGSSServerInit",
+        authGSSServerInit, METH_VARARGS,
+        "Initialize server-side GSSAPI operations."
+    },
+    {
+        "authGSSClientWrap",
+        authGSSClientWrap, METH_VARARGS,
+        "Do a GSSAPI wrap."
+    },
+    {
+        "authGSSClientUnwrap",
+        authGSSClientUnwrap, METH_VARARGS,
+        "Do a GSSAPI unwrap."
+    },
+    {
+        "authGSSClientInquireCred", authGSSClientInquireCred, METH_VARARGS,
+        "Get the current user name, if any."
+    },
+    {
+        "authGSSServerClean",
+        authGSSServerClean, METH_VARARGS,
+        "Terminate server-side GSSAPI operations."
+    },
+    {
+        "authGSSServerStep",
+        authGSSServerStep, METH_VARARGS,
+        "Do a server-side GSSAPI step."
+    },
+    {
+        "authGSSServerHasDelegated",
+        authGSSServerHasDelegated, METH_VARARGS,
+        "Check whether the client delegated credentials to us."
+    },
+    {
+        "authGSSServerStoreDelegate",
+        authGSSServerStoreDelegate, METH_VARARGS,
+        "Store the delegated Credentials."
+    },
+    {
+        "authGSSServerResponse",
+        authGSSServerResponse, METH_VARARGS,
+        "Get the response from the last server-side GSSAPI step."
+    },
+    {
+        "authGSSServerUserName",
+        authGSSServerUserName, METH_VARARGS,
+        "Get the user name from the last server-side GSSAPI step."
+    },
+    {
+        "authGSSServerCacheName",
+        authGSSServerCacheName, METH_VARARGS,
+        "Get the location of the cache where delegated credentials are stored."
+    },
+    {
+        "authGSSServerTargetName",
+        authGSSServerTargetName, METH_VARARGS,
+        "Get the target name from the last server-side GSSAPI step."
+    },
+    {NULL, NULL, 0, NULL}        /* Sentinel */
+};
+
+MOD_INIT(kerberos)
+{
+    PyObject *m,*d;
+
+    MOD_DEF(m, "kerberos", NULL, KerberosMethods);
+
+    if (m == NULL) {
+        return MOD_ERROR_VAL;
+    }
+
+    d = PyModule_GetDict(m);
+
+    /* create the base exception class */
+    if (! (KrbException_class = PyErr_NewException(
+        "kerberos.KrbError", NULL, NULL
+    ))) {
+        goto error;
+    }
+
+    PyDict_SetItemString(d, "KrbError", KrbException_class);
+    Py_INCREF(KrbException_class);
+
+    /* ...and the derived exceptions */
+    if (! (BasicAuthException_class = PyErr_NewException(
+        "kerberos.BasicAuthError", KrbException_class, NULL
+    ))) {
+        goto error;
+    }
+
+    Py_INCREF(BasicAuthException_class);
+    PyDict_SetItemString(d, "BasicAuthError", BasicAuthException_class);
+
+    if (! (PwdChangeException_class = PyErr_NewException(
+        "kerberos.PwdChangeError", KrbException_class, NULL
+    ))) {
+        goto error;
+    }
+
+    Py_INCREF(PwdChangeException_class);
+    PyDict_SetItemString(d, "PwdChangeError", PwdChangeException_class);
+
+    if (! (GssException_class = PyErr_NewException(
+        "kerberos.GSSError", KrbException_class, NULL
+    ))) {
+        goto error;
+    }
+
+    Py_INCREF(GssException_class);
+    PyDict_SetItemString(
+        d, "GSSError", GssException_class
+    );
+
+    PyDict_SetItemString(
+        d, "AUTH_GSS_COMPLETE", PyInt_FromLong(AUTH_GSS_COMPLETE)
+    );
+    PyDict_SetItemString(
+        d, "AUTH_GSS_CONTINUE", PyInt_FromLong(AUTH_GSS_CONTINUE)
+    );
+
+    PyDict_SetItemString(
+        d, "GSS_C_DELEG_FLAG", PyInt_FromLong(GSS_C_DELEG_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_MUTUAL_FLAG", PyInt_FromLong(GSS_C_MUTUAL_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_REPLAY_FLAG", PyInt_FromLong(GSS_C_REPLAY_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_SEQUENCE_FLAG", PyInt_FromLong(GSS_C_SEQUENCE_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_CONF_FLAG", PyInt_FromLong(GSS_C_CONF_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_INTEG_FLAG", PyInt_FromLong(GSS_C_INTEG_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_ANON_FLAG", PyInt_FromLong(GSS_C_ANON_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_PROT_READY_FLAG", PyInt_FromLong(GSS_C_PROT_READY_FLAG)
+    );
+    PyDict_SetItemString(
+        d, "GSS_C_TRANS_FLAG", PyInt_FromLong(GSS_C_TRANS_FLAG)
+    );
+    krb5_mech_oid.elements =  &krb5_mech_oid_bytes.b;
+
+    PyDict_SetItemString(
+        d, "GSS_MECH_OID_KRB5", PyCObject_FromVoidPtr(&krb5_mech_oid, NULL)
+    );
+
+    spnego_mech_oid.elements = &spnego_mech_oid_bytes.b;
+    PyDict_SetItemString(
+        d, "GSS_MECH_OID_SPNEGO", PyCObject_FromVoidPtr(&spnego_mech_oid, NULL)
+    );
+
+error:
+    if (PyErr_Occurred()) {
+         PyErr_SetString(PyExc_ImportError, "kerberos: init failed");
+        return MOD_ERROR_VAL;
+    }
+
+    return MOD_SUCCESS_VAL(m);
+}
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.c b/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.c
new file mode 100644
index 000000000..08703e156
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.c
@@ -0,0 +1,171 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <Python.h>
+#include "kerberosbasic.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef PRINTFS
+
+extern PyObject *BasicAuthException_class;
+static void set_basicauth_error(krb5_context context, krb5_error_code code);
+
+static krb5_error_code verify_krb5_user(
+    krb5_context context, krb5_principal principal, const char *password,
+    krb5_principal server
+);
+
+int authenticate_user_krb5pwd(
+    const char *user, const char *pswd, const char *service,
+    const char *default_realm
+) {
+    krb5_context    kcontext = NULL;
+    krb5_error_code code;
+    krb5_principal  client = NULL;
+    krb5_principal  server = NULL;
+    int             ret = 0;
+    char            *name = NULL;
+    char            *p = NULL;
+
+    code = krb5_init_context(&kcontext);
+    if (code)
+    {
+        PyErr_SetObject(
+            BasicAuthException_class,
+            Py_BuildValue(
+                "((s:i))", "Cannot initialize Kerberos5 context", code
+            )
+        );
+        return 0;
+    }
+
+    ret = krb5_parse_name (kcontext, service, &server);
+
+    if (ret) {
+        set_basicauth_error(kcontext, ret);
+        ret = 0;
+        goto end;
+    }
+
+    code = krb5_unparse_name(kcontext, server, &name);
+    if (code) {
+        set_basicauth_error(kcontext, code);
+        ret = 0;
+        goto end;
+    }
+#ifdef PRINTFS
+    printf("Using %s as server principal for password verification\n", name);
+#endif
+    free(name);
+    name = NULL;
+
+    name = (char *)malloc(256);
+    if (name == NULL)
+    {
+        PyErr_NoMemory();
+        ret = 0;
+        goto end;
+    }
+    p = strchr(user, '@');
+    if (p == NULL) {
+        snprintf(name, 256, "%s@%s", user, default_realm);
+    } else {
+        snprintf(name, 256, "%s", user);
+    }
+
+    code = krb5_parse_name(kcontext, name, &client);
+    if (code) {
+        set_basicauth_error(kcontext, code);
+        ret = 0;
+        goto end;
+    }
+
+    code = verify_krb5_user(kcontext, client, pswd, server);
+
+    if (code) {
+        ret = 0;
+        goto end;
+    }
+
+    ret = 1;
+
+end:
+#ifdef PRINTFS
+    printf(
+        "kerb_authenticate_user_krb5pwd ret=%d user=%s authtype=%s\n",
+        ret, user, "Basic"
+    );
+#endif
+    if (name) {
+        free(name);
+    }
+    if (client) {
+        krb5_free_principal(kcontext, client);
+    }
+    if (server) {
+        krb5_free_principal(kcontext, server);
+    }
+    krb5_free_context(kcontext);
+
+    return ret;
+}
+
+/* Inspired by krb5_verify_user from Heimdal */
+static krb5_error_code verify_krb5_user(
+    krb5_context context, krb5_principal principal, const char *password,
+    krb5_principal server
+) {
+    krb5_creds creds;
+    krb5_get_init_creds_opt gic_options;
+    krb5_error_code ret;
+    char *name = NULL;
+
+    memset(&creds, 0, sizeof(creds));
+
+    ret = krb5_unparse_name(context, principal, &name);
+    if (ret == 0) {
+#ifdef PRINTFS
+        printf("Trying to get TGT for user %s\n", name);
+#endif
+        free(name);
+    }
+
+    krb5_get_init_creds_opt_init(&gic_options);
+    ret = krb5_get_init_creds_password(
+        context, &creds, principal, (char *)password,
+        NULL, NULL, 0, NULL, &gic_options
+    );
+    if (ret) {
+        set_basicauth_error(context, ret);
+        goto end;
+    }
+
+end:
+    krb5_free_cred_contents(context, &creds);
+
+    return ret;
+}
+
+static void set_basicauth_error(krb5_context context, krb5_error_code code)
+{
+    PyErr_SetObject(
+        BasicAuthException_class,
+        Py_BuildValue("(s:i)", krb5_get_err_text(context, code), code)
+    );
+}
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.h b/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.h
new file mode 100644
index 000000000..c558234e4
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberosbasic.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+
+#define krb5_get_err_text(context,code) error_message(code)
+
+int authenticate_user_krb5pwd(
+    const char *user, const char *pswd, const char *service,
+    const char *default_realm
+);
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberosgss.c b/shell/ext-py/kerberos-1.3.1/src/kerberosgss.c
new file mode 100644
index 000000000..c82a5e439
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberosgss.c
@@ -0,0 +1,1007 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <Python.h>
+#include "kerberosgss.h"
+
+#include "base64.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+static void set_gss_error(OM_uint32 err_maj, OM_uint32 err_min);
+
+int create_krb5_ccache(
+    gss_server_state *state, krb5_context kcontext, krb5_principal princ,
+    krb5_ccache *ccache
+);
+
+extern PyObject *GssException_class;
+extern PyObject *KrbException_class;
+
+char* server_principal_details(const char* service, const char* hostname)
+{
+    char match[1024];
+    size_t match_len = 0;
+    char* result = NULL;
+    
+    int code;
+    krb5_context kcontext;
+    krb5_keytab kt = NULL;
+    krb5_kt_cursor cursor = NULL;
+    krb5_keytab_entry entry;
+    char* pname = NULL;
+    
+    // Generate the principal prefix we want to match
+    snprintf(match, 1024, "%s/%s@", service, hostname);
+    match_len = strlen(match);
+    
+    code = krb5_init_context(&kcontext);
+    if (code) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "((s:i))", "Cannot initialize Kerberos5 context", code
+            )
+        );
+        return NULL;
+    }
+    
+    if ((code = krb5_kt_default(kcontext, &kt))) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue("((s:i))", "Cannot get default keytab", code)
+        );
+        goto end;
+    }
+    
+    if ((code = krb5_kt_start_seq_get(kcontext, kt, &cursor))) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "((s:i))", "Cannot get sequence cursor from keytab", code
+            )
+        );
+        goto end;
+    }
+    
+    while ((code = krb5_kt_next_entry(kcontext, kt, &entry, &cursor)) == 0) {
+        if ((code = krb5_unparse_name(kcontext, entry.principal, &pname))) {
+            PyErr_SetObject(
+                KrbException_class,
+                Py_BuildValue(
+                    "((s:i))", "Cannot parse principal name from keytab", code
+                )
+            );
+            goto end;
+        }
+        
+        if (strncmp(pname, match, match_len) == 0) {
+            result = malloc(strlen(pname) + 1);
+            if (result == NULL) {
+                PyErr_NoMemory();
+                goto end;
+            }
+            strcpy(result, pname);
+            krb5_free_unparsed_name(kcontext, pname);
+            krb5_free_keytab_entry_contents(kcontext, &entry);
+            break;
+        }
+        
+        krb5_free_unparsed_name(kcontext, pname);
+        krb5_free_keytab_entry_contents(kcontext, &entry);
+    }
+    
+    if (result == NULL) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue("((s:i))", "Principal not found in keytab", -1)
+        );
+    }
+    
+end:
+    if (cursor) {
+        krb5_kt_end_seq_get(kcontext, kt, &cursor);
+    }
+    if (kt) {
+        krb5_kt_close(kcontext, kt);
+    }
+    krb5_free_context(kcontext);
+    
+    return result;
+}
+
+int authenticate_gss_client_init(
+    const char* service, const char* principal, long int gss_flags,
+    gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state
+)
+{
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc principal_token = GSS_C_EMPTY_BUFFER;
+    int ret = AUTH_GSS_COMPLETE;
+    
+    state->server_name = GSS_C_NO_NAME;
+    state->mech_oid = mech_oid;
+    state->context = GSS_C_NO_CONTEXT;
+    state->gss_flags = gss_flags;
+    state->client_creds = GSS_C_NO_CREDENTIAL;
+    state->username = NULL;
+    state->response = NULL;
+    
+    // Import server name first
+    name_token.length = strlen(service);
+    name_token.value = (char *)service;
+    
+    maj_stat = gss_import_name(
+        &min_stat, &name_token, gss_krb5_nt_service_name, &state->server_name
+    );
+    
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    // Use the delegate credentials if they exist
+    if (delegatestate && delegatestate->client_creds != GSS_C_NO_CREDENTIAL) {
+        state->client_creds = delegatestate->client_creds;
+    }
+    // If available use the principal to extract its associated credentials
+    else if (principal && *principal) {
+        gss_name_t name;
+        principal_token.length = strlen(principal);
+        principal_token.value = (char *)principal;
+
+        maj_stat = gss_import_name(
+            &min_stat, &principal_token, GSS_C_NT_USER_NAME, &name
+        );
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+    	    goto end;
+        }
+
+        maj_stat = gss_acquire_cred(
+            &min_stat, name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
+            GSS_C_INITIATE, &state->client_creds, NULL, NULL
+        );
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+
+        maj_stat = gss_release_name(&min_stat, &name);
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+    }
+
+end:
+    return ret;
+}
+
+int authenticate_gss_client_clean(gss_client_state *state)
+{
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    int ret = AUTH_GSS_COMPLETE;
+    
+    if (state->context != GSS_C_NO_CONTEXT) {
+        maj_stat = gss_delete_sec_context(
+            &min_stat, &state->context, GSS_C_NO_BUFFER
+        );
+    }
+    if (state->server_name != GSS_C_NO_NAME) {
+        maj_stat = gss_release_name(&min_stat, &state->server_name);
+    }
+    if (
+        state->client_creds != GSS_C_NO_CREDENTIAL &&
+        ! (state->gss_flags & GSS_C_DELEG_FLAG)
+    ) {
+        maj_stat = gss_release_cred(&min_stat, &state->client_creds);
+    }
+    if (state->username != NULL) {
+        free(state->username);
+        state->username = NULL;
+    }
+    if (state->response != NULL) {
+        free(state->response);
+        state->response = NULL;
+    }
+    
+    return ret;
+}
+
+int authenticate_gss_client_step(
+    gss_client_state* state, const char* challenge, struct gss_channel_bindings_struct* channel_bindings
+) {
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+    int ret = AUTH_GSS_CONTINUE;
+    
+    // Always clear out the old response
+    if (state->response != NULL) {
+        free(state->response);
+        state->response = NULL;
+    }
+    
+    // If there is a challenge (data from the server) we need to give it to GSS
+    if (challenge && *challenge) {
+        size_t len;
+        input_token.value = base64_decode(challenge, &len);
+        if (input_token.value == NULL)
+        {
+            PyErr_NoMemory();
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        input_token.length = len;
+    }
+    
+    // Do GSSAPI step
+    Py_BEGIN_ALLOW_THREADS
+    maj_stat = gss_init_sec_context(
+        &min_stat,
+        state->client_creds,
+        &state->context,
+        state->server_name,
+        state->mech_oid,
+        (OM_uint32)state->gss_flags,
+        0,
+        channel_bindings,
+        &input_token,
+        NULL,
+        &output_token,
+        NULL,
+        NULL
+    );
+    Py_END_ALLOW_THREADS
+    
+    if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    
+    ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE;
+    // Grab the client response to send back to the server
+    if (output_token.length) {
+        state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
+        if (state->response == NULL) {
+            PyErr_NoMemory();
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        maj_stat = gss_release_buffer(&min_stat, &output_token);
+    }
+    
+    // Try to get the user name if we have completed all GSS operations
+    if (ret == AUTH_GSS_COMPLETE) {
+        gss_name_t gssuser = GSS_C_NO_NAME;
+        maj_stat = gss_inquire_context(&min_stat, state->context, &gssuser, NULL, NULL, NULL,  NULL, NULL, NULL);
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        
+        gss_buffer_desc name_token;
+        name_token.length = 0;
+        maj_stat = gss_display_name(&min_stat, gssuser, &name_token, NULL);
+        if (GSS_ERROR(maj_stat)) {
+            if (name_token.value) {
+                gss_release_buffer(&min_stat, &name_token);
+            }
+            gss_release_name(&min_stat, &gssuser);
+            
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        } else {
+            if (state->username != NULL) {                                                                                                    
+                free(state->username);                                                                                                        
+                state->username = NULL;                                                                                                       
+            }                                                                                                                                 
+            state->username = (char *)malloc(name_token.length + 1);
+            if (state->username == NULL) {
+                PyErr_NoMemory();
+                ret = AUTH_GSS_ERROR;
+                goto end;
+            }
+            strncpy(state->username, (char*) name_token.value, name_token.length);
+            state->username[name_token.length] = 0;
+            gss_release_buffer(&min_stat, &name_token);
+            gss_release_name(&min_stat, &gssuser);
+        }
+    }
+
+end:
+    if (output_token.value) {
+        gss_release_buffer(&min_stat, &output_token);
+    }
+    if (input_token.value) {
+        free(input_token.value);
+    }
+    return ret;
+}
+
+int authenticate_gss_client_unwrap(
+    gss_client_state *state, const char *challenge
+) {
+	OM_uint32 maj_stat;
+	OM_uint32 min_stat;
+	gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+	gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+	int ret = AUTH_GSS_CONTINUE;
+	int conf = 0;
+    
+	// Always clear out the old response
+	if (state->response != NULL) {
+		free(state->response);
+		state->response = NULL;
+		state->responseConf = 0;
+	}
+    
+	// If there is a challenge (data from the server) we need to give it to GSS
+	if (challenge && *challenge) {
+		size_t len;
+		input_token.value = base64_decode(challenge, &len);
+		if (input_token.value == NULL) {
+		    PyErr_NoMemory();
+		    ret = AUTH_GSS_ERROR;
+		    goto end;
+		}
+		input_token.length = len;
+	}
+    
+	// Do GSSAPI step
+	maj_stat = gss_unwrap(
+        &min_stat,
+        state->context,
+        &input_token,
+        &output_token,
+        &conf,
+        NULL
+    );
+    
+	if (maj_stat != GSS_S_COMPLETE)	{
+		set_gss_error(maj_stat, min_stat);
+		ret = AUTH_GSS_ERROR;
+		goto end;
+	} else {
+		ret = AUTH_GSS_COMPLETE;
+    }
+    
+	// Grab the client response
+	if (output_token.length) {
+		state->response = base64_encode(
+            (const unsigned char *)output_token.value, output_token.length
+        );
+		if (state->response == NULL)
+		{
+		    PyErr_NoMemory();
+		    ret = AUTH_GSS_ERROR;
+		    goto end;
+		}
+		state->responseConf = conf;
+		maj_stat = gss_release_buffer(&min_stat, &output_token);
+	}
+
+end:
+	if (output_token.value) {
+		gss_release_buffer(&min_stat, &output_token);
+    }
+	if (input_token.value) {
+		free(input_token.value);
+    }
+	return ret;
+}
+
+int authenticate_gss_client_wrap(
+    gss_client_state* state, const char* challenge, const char* user,
+    int protect
+) {
+	OM_uint32 maj_stat;
+	OM_uint32 min_stat;
+	gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+	gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+	int ret = AUTH_GSS_CONTINUE;
+	char buf[4096], server_conf_flags;
+	unsigned long buf_size;
+    
+	// Always clear out the old response
+	if (state->response != NULL) {
+		free(state->response);
+		state->response = NULL;
+	}
+    
+	if (challenge && *challenge) {
+		size_t len;
+		input_token.value = base64_decode(challenge, &len);
+		if (input_token.value == NULL)
+		{
+		    PyErr_NoMemory();
+		    ret = AUTH_GSS_ERROR;
+		    goto end;
+		}
+		input_token.length = len;
+	}
+    
+	if (user) {
+		// get bufsize
+		server_conf_flags = ((char*) input_token.value)[0];
+		((char*) input_token.value)[0] = 0;
+		buf_size = ntohl(*((long *) input_token.value));
+		free(input_token.value);
+#ifdef PRINTFS
+		printf(
+            "User: %s, %c%c%c\n", user,
+            server_conf_flags & GSS_AUTH_P_NONE      ? 'N' : '-',
+            server_conf_flags & GSS_AUTH_P_INTEGRITY ? 'I' : '-',
+            server_conf_flags & GSS_AUTH_P_PRIVACY   ? 'P' : '-'
+        );
+		printf("Maximum GSS token size is %ld\n", buf_size);
+#endif
+        
+		// agree to terms (hack!)
+		buf_size = htonl(buf_size); // not relevant without integrity/privacy
+		memcpy(buf, &buf_size, 4);
+		buf[0] = GSS_AUTH_P_NONE;
+		// server decides if principal can log in as user
+		strncpy(buf + 4, user, sizeof(buf) - 4);
+		input_token.value = buf;
+		input_token.length = 4 + strlen(user);
+	}
+    
+	// Do GSSAPI wrap
+	maj_stat = gss_wrap(
+        &min_stat,
+        state->context,
+        protect,
+        GSS_C_QOP_DEFAULT,
+        &input_token,
+        NULL,
+        &output_token
+    );
+    
+	if (maj_stat != GSS_S_COMPLETE)	{
+		set_gss_error(maj_stat, min_stat);
+		ret = AUTH_GSS_ERROR;
+		goto end;
+	} else {
+		ret = AUTH_GSS_COMPLETE;
+    }
+	// Grab the client response to send back to the server
+	if (output_token.length) {
+		state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
+		if (state->response == NULL) {
+		    PyErr_NoMemory();
+		    ret = AUTH_GSS_ERROR;
+		    goto end;
+		}
+		maj_stat = gss_release_buffer(&min_stat, &output_token);
+	}
+
+end:
+	if (output_token.value) {
+		gss_release_buffer(&min_stat, &output_token);
+    }
+	return ret;
+}
+
+int authenticate_gss_client_inquire_cred(gss_client_state* state)
+{
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL;
+    gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
+    gss_name_t name = GSS_C_NO_NAME;
+    int ret = AUTH_GSS_COMPLETE;
+
+    // Check whether credentials have already been obtained.
+    if (state->username != NULL) {
+        goto end;
+    }
+
+    // Get credentials
+    maj_stat = gss_acquire_cred(
+        &min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+        GSS_C_NO_OID_SET, GSS_C_INITIATE, &client_creds, NULL, NULL
+    );
+
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    // Get the name
+    maj_stat = gss_inquire_cred(
+        &min_stat, client_creds, &name, NULL, NULL, NULL
+    );
+
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    maj_stat = gss_display_name(&min_stat, name, &name_token, NULL);
+
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    state->username = (char *)malloc(name_token.length + 1);
+    if (state->username == NULL) {
+        PyErr_NoMemory();
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    strncpy(state->username, (char*) name_token.value, name_token.length);
+    state->username[name_token.length] = 0;
+
+end:
+    if (client_creds != GSS_C_NO_CREDENTIAL) {
+        gss_release_cred(&min_stat, &client_creds);
+    }
+    if (name_token.length) {
+        gss_release_buffer(&min_stat, &name_token);
+    }
+    if (name != GSS_C_NO_NAME) {
+        gss_release_name(&min_stat, &name);
+    }
+    return ret;
+}
+
+int authenticate_gss_server_init(const char *service, gss_server_state *state)
+{
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
+    int ret = AUTH_GSS_COMPLETE;
+    
+    state->context = GSS_C_NO_CONTEXT;
+    state->server_name = GSS_C_NO_NAME;
+    state->client_name = GSS_C_NO_NAME;
+    state->server_creds = GSS_C_NO_CREDENTIAL;
+    state->client_creds = GSS_C_NO_CREDENTIAL;
+    state->username = NULL;
+    state->targetname = NULL;
+    state->response = NULL;
+    state->ccname = NULL;
+    int cred_usage = GSS_C_ACCEPT;
+    
+    // Server name may be empty which means we aren't going to create our own creds
+    size_t service_len = strlen(service);
+    if (service_len != 0) {
+        // Import server name first
+        if (strcmp(service, "DELEGATE") == 0) {
+	    cred_usage = GSS_C_BOTH;
+        }
+        else {
+            name_token.length = strlen(service);
+            name_token.value = (char *)service;
+        
+            maj_stat = gss_import_name(
+                &min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE,
+                &state->server_name
+            );
+        
+            if (GSS_ERROR(maj_stat)) {
+                set_gss_error(maj_stat, min_stat);
+                ret = AUTH_GSS_ERROR;
+                goto end;
+            }
+	}
+
+        // Get credentials
+        maj_stat = gss_acquire_cred(
+            &min_stat, state->server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
+            cred_usage, &state->server_creds, NULL, NULL
+        );
+
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+    }
+    
+end:
+    return ret;
+}
+
+int authenticate_gss_server_clean(gss_server_state *state)
+{
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    int ret = AUTH_GSS_COMPLETE;
+    
+    if (state->context != GSS_C_NO_CONTEXT) {
+        maj_stat = gss_delete_sec_context(
+            &min_stat, &state->context, GSS_C_NO_BUFFER
+        );
+    }
+    if (state->server_name != GSS_C_NO_NAME) {
+        maj_stat = gss_release_name(&min_stat, &state->server_name);
+    }
+    if (state->client_name != GSS_C_NO_NAME) {
+        maj_stat = gss_release_name(&min_stat, &state->client_name);
+    }
+    if (state->server_creds != GSS_C_NO_CREDENTIAL) {
+        maj_stat = gss_release_cred(&min_stat, &state->server_creds);
+    }
+    if (state->client_creds != GSS_C_NO_CREDENTIAL) {
+        maj_stat = gss_release_cred(&min_stat, &state->client_creds);
+    }
+    if (state->username != NULL) {
+        free(state->username);
+        state->username = NULL;
+    }
+    if (state->targetname != NULL) {
+        free(state->targetname);
+        state->targetname = NULL;
+    }
+    if (state->response != NULL) {
+        free(state->response);
+        state->response = NULL;
+    }
+    if (state->ccname != NULL) {
+        free(state->ccname);
+        state->ccname = NULL;
+    }
+    
+    return ret;
+}
+
+int authenticate_gss_server_step(
+    gss_server_state *state, const char *challenge
+) {
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+    int ret = AUTH_GSS_CONTINUE;
+    
+    // Always clear out the old response
+    if (state->response != NULL) {
+        free(state->response);
+        state->response = NULL;
+    }
+    
+    // If there is a challenge (data from the server) we need to give it to GSS
+    if (challenge && *challenge) {
+        size_t len;
+        input_token.value = base64_decode(challenge, &len);
+        if (input_token.value == NULL)
+        {
+            PyErr_NoMemory();
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        input_token.length = len;
+    } else {
+        PyErr_SetString(
+            KrbException_class, "No challenge parameter in request from client"
+        );
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    
+    Py_BEGIN_ALLOW_THREADS
+    maj_stat = gss_accept_sec_context(
+        &min_stat,
+        &state->context,
+        state->server_creds,
+        &input_token,
+        GSS_C_NO_CHANNEL_BINDINGS,
+        &state->client_name,
+        NULL,
+        &output_token,
+        NULL,
+        NULL,
+        &state->client_creds
+    );
+    Py_END_ALLOW_THREADS
+    
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    
+    // Grab the server response to send back to the client
+    if (output_token.length) {
+        state->response = base64_encode(
+            (const unsigned char *)output_token.value, output_token.length
+        );
+        if (state->response == NULL)
+        {
+            PyErr_NoMemory();
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        maj_stat = gss_release_buffer(&min_stat, &output_token);
+    }
+    
+    // Get the user name
+    maj_stat = gss_display_name(
+        &min_stat, state->client_name, &output_token, NULL
+    );
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    state->username = (char *)malloc(output_token.length + 1);
+    if (state->username == NULL)
+    {
+        PyErr_NoMemory();
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+    strncpy(state->username, (char*) output_token.value, output_token.length);
+    state->username[output_token.length] = 0;
+    
+    // Get the target name if no server creds were supplied
+    if (state->server_creds == GSS_C_NO_CREDENTIAL) {
+        gss_name_t target_name = GSS_C_NO_NAME;
+        maj_stat = gss_inquire_context(
+            &min_stat, state->context, NULL, &target_name, NULL, NULL, NULL,
+            NULL, NULL
+        );
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        maj_stat = gss_display_name(
+            &min_stat, target_name, &output_token, NULL
+        );
+        if (GSS_ERROR(maj_stat)) {
+            set_gss_error(maj_stat, min_stat);
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        state->targetname = (char *)malloc(output_token.length + 1);
+        if (state->targetname == NULL)
+        {
+            PyErr_NoMemory();
+            ret = AUTH_GSS_ERROR;
+            goto end;
+        }
+        strncpy(
+            state->targetname, (char*) output_token.value, output_token.length
+        );
+        state->targetname[output_token.length] = 0;
+    }
+
+    ret = AUTH_GSS_COMPLETE;
+    
+end:
+    if (output_token.length) {
+        gss_release_buffer(&min_stat, &output_token);
+    }
+    if (input_token.value) {
+        free(input_token.value);
+    }
+    return ret;
+}
+
+int authenticate_gss_server_has_delegated(gss_server_state *state)
+{
+    return (state->client_creds != GSS_C_NO_CREDENTIAL);
+}
+
+static void set_gss_error(OM_uint32 err_maj, OM_uint32 err_min)
+{
+    OM_uint32 maj_stat, min_stat;
+    OM_uint32 msg_ctx = 0;
+    gss_buffer_desc status_string;
+    char buf_maj[512];
+    char buf_min[512];
+    
+    do {
+        maj_stat = gss_display_status(
+            &min_stat,
+            err_maj,
+            GSS_C_GSS_CODE,
+            GSS_C_NO_OID,
+            &msg_ctx,
+            &status_string
+        );
+        if (GSS_ERROR(maj_stat)) {
+            break;
+        }
+        strncpy(buf_maj, (char*) status_string.value, sizeof(buf_maj));
+        gss_release_buffer(&min_stat, &status_string);
+        
+        maj_stat = gss_display_status(
+            &min_stat,
+            err_min,
+            GSS_C_MECH_CODE,
+            GSS_C_NULL_OID,
+            &msg_ctx,
+            &status_string
+        );
+        if (! GSS_ERROR(maj_stat)) {
+            strncpy(buf_min, (char*) status_string.value, sizeof(buf_min));
+            gss_release_buffer(&min_stat, &status_string);
+        }
+    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
+    
+    PyErr_SetObject(
+        GssException_class,
+        Py_BuildValue("((s:i)(s:i))", buf_maj, err_maj, buf_min, err_min)
+    );
+}
+
+int authenticate_gss_server_store_delegate(gss_server_state *state)
+{
+    gss_cred_id_t delegated_cred = state->client_creds;
+    char *princ_name = state->username;
+    OM_uint32 maj_stat, min_stat;
+    krb5_principal princ = NULL;
+    krb5_ccache ccache = NULL;
+    krb5_error_code problem;
+    krb5_context context;
+    int ret = 500;
+
+    if (delegated_cred == GSS_C_NO_CREDENTIAL){
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue("(s)", "Ticket is not delegatable")
+        );
+        return AUTH_GSS_ERROR;
+    }
+
+    problem = krb5_init_context(&context);
+    if (problem) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue("(s)", "Cannot initialize krb5 context")
+        );
+        return AUTH_GSS_ERROR;
+    }
+
+    problem = krb5_parse_name(context, princ_name, &princ);
+    if (problem) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "(s:s)", "Cannot parse delegated username",
+                krb5_get_err_text(context, problem)
+            )
+        );
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    problem = create_krb5_ccache(state, context, princ, &ccache);
+    if (problem) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "(s:s)", "Error in creating krb5 cache",
+                krb5_get_err_text(context, problem)
+            )
+        );
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
+    if (GSS_ERROR(maj_stat)) {
+        set_gss_error(maj_stat, min_stat);
+        ret = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    krb5_cc_close(context, ccache);
+    ccache = NULL;
+    ret = 0;
+
+end:
+    if (princ) {
+        krb5_free_principal(context, princ);
+    }
+    if (ccache) {
+        krb5_cc_destroy(context, ccache);
+    }
+    krb5_free_context(context);
+
+    return ret;
+}
+
+int create_krb5_ccache(
+    gss_server_state *state, krb5_context kcontext, krb5_principal princ,
+    krb5_ccache *ccache
+) {
+    int fd;
+    char ccname[32];
+    krb5_error_code problem;
+    int ret;
+    krb5_ccache tmp_ccache = NULL;
+
+    snprintf(ccname, sizeof(ccname), "/tmp/krb5cc_pyserv_XXXXXX");
+    fd = mkstemp(ccname);
+    if (fd < 0) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue("(s:s)", "Error in mkstemp", strerror(errno))
+        );
+        ret = 1;
+        goto end;
+    }
+    close(fd);
+
+    problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache);
+    if (problem) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "(s:s)", "Error resolving the credential cache",
+                krb5_get_err_text(kcontext, problem)
+            )
+        );
+        ret = 1;
+        unlink(ccname);
+        goto end;
+    }
+
+    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
+    if (problem) {
+        PyErr_SetObject(
+            KrbException_class,
+            Py_BuildValue(
+                "(s:s)", "Error initialising the credential cache",
+                krb5_get_err_text(kcontext, problem)
+            )
+        );
+        ret = 1;
+        goto end;
+    }
+
+    *ccache = tmp_ccache;
+    tmp_ccache = NULL;
+
+    ret = 0;
+
+end:
+    if (tmp_ccache) {
+        krb5_cc_destroy(kcontext, tmp_ccache);
+    }
+
+    state->ccname = (char *)malloc(32*sizeof(char));
+    if (state->ccname == NULL) {
+        PyErr_NoMemory();
+        return 1;
+    }
+    strcpy(state->ccname, ccname);
+
+    return ret;
+}
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberosgss.h b/shell/ext-py/kerberos-1.3.1/src/kerberosgss.h
new file mode 100644
index 000000000..362040d98
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberosgss.h
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2006-2018 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+
+#define krb5_get_err_text(context,code) error_message(code)
+
+#define AUTH_GSS_ERROR      -1
+#define AUTH_GSS_COMPLETE    1
+#define AUTH_GSS_CONTINUE    0
+
+#define GSS_AUTH_P_NONE         1
+#define GSS_AUTH_P_INTEGRITY    2
+#define GSS_AUTH_P_PRIVACY      4
+
+typedef struct {
+    gss_ctx_id_t     context;
+    gss_name_t       server_name;
+    gss_OID          mech_oid;
+    long int         gss_flags;
+    gss_cred_id_t    client_creds;
+    char*            username;
+    char*            response;
+    int              responseConf;
+} gss_client_state;
+
+typedef struct {
+    gss_ctx_id_t     context;
+    gss_name_t       server_name;
+    gss_name_t       client_name;
+    gss_cred_id_t    server_creds;
+    gss_cred_id_t    client_creds;
+    char*            username;
+    char*            targetname;
+    char*            response;
+    char*            ccname;
+} gss_server_state;
+
+char* server_principal_details(const char* service, const char* hostname);
+
+int authenticate_gss_client_init(
+    const char* service, const char* principal, long int gss_flags,
+    gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state
+);
+int authenticate_gss_client_clean(
+    gss_client_state *state
+);
+int authenticate_gss_client_step(
+    gss_client_state *state, const char *challenge, struct gss_channel_bindings_struct *channel_bindings
+);
+int authenticate_gss_client_unwrap(
+    gss_client_state* state, const char* challenge
+);
+int authenticate_gss_client_wrap(
+    gss_client_state* state, const char* challenge, const char* user,
+    int protect
+);
+int authenticate_gss_client_inquire_cred(
+    gss_client_state* state
+);
+
+int authenticate_gss_server_init(
+    const char* service, gss_server_state* state
+);
+int authenticate_gss_server_clean(
+    gss_server_state *state
+);
+int authenticate_gss_server_step(
+    gss_server_state *state, const char *challenge
+);
+int authenticate_gss_server_store_delegate(
+    gss_server_state *state
+);
+int authenticate_gss_server_has_delegated(
+    gss_server_state *state
+);
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberospw.c b/shell/ext-py/kerberos-1.3.1/src/kerberospw.c
new file mode 100644
index 000000000..e6abc0d8a
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberospw.c
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2008 Guido Guenther <ag...@sigxcpu.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include <Python.h>
+#include "kerberospw.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef PRINTFS
+
+extern PyObject *PwdChangeException_class;
+
+static void set_pwchange_error(krb5_context context, krb5_error_code code)
+{
+    PyErr_SetObject(
+        PwdChangeException_class,
+        Py_BuildValue("(s:i)", krb5_get_err_text(context, code), code)
+    );
+}
+
+/* Inspired by krb5_verify_user from Heimdal */
+static krb5_error_code verify_krb5_user(
+    krb5_context context,
+    krb5_principal principal,
+    const char *password,
+    const char *service,
+    krb5_creds* creds
+) {
+    krb5_get_init_creds_opt gic_options;
+    krb5_error_code code;
+    int ret = 0;
+    
+#ifdef PRINTFS
+    {
+        char *name = NULL;
+        code = krb5_unparse_name(context, principal, &name);
+        if (!code) {
+            printf("Trying to get TGT for user %s\n", name);
+        }
+        free(name);
+    }
+#endif
+
+    krb5_get_init_creds_opt_init(&gic_options);
+    krb5_get_init_creds_opt_set_forwardable(&gic_options, 0);
+    krb5_get_init_creds_opt_set_proxiable(&gic_options, 0);
+    krb5_get_init_creds_opt_set_renew_life(&gic_options, 0);
+
+    memset(creds, 0, sizeof(krb5_creds));
+    
+    code = krb5_get_init_creds_password(
+        context, creds, principal,
+        (char *)password, NULL, NULL, 0,
+        (char *)service, &gic_options
+    );
+    if (code) {
+        set_pwchange_error(context, code);
+        goto end;
+    }
+    ret = 1; /* success */
+
+end:
+    return ret;
+}
+
+int change_user_krb5pwd(
+    const char *user, const char* oldpswd, const char *newpswd
+) {
+    krb5_context    kcontext = NULL;
+    krb5_error_code code;
+    krb5_principal  client = NULL;
+    krb5_creds      creds;
+    int             ret = 0;
+    int             bytes = 0;
+    char            *name = NULL;
+
+    const char* service = "kadmin/changepw";
+    int result_code;
+    krb5_data result_code_string, result_string;
+
+    code = krb5_init_context(&kcontext);
+    if (code) {
+        PyErr_SetObject(
+            PwdChangeException_class,
+            Py_BuildValue(
+                "((s:i))", "Cannot initialize Kerberos5 context", code
+            )
+        );
+        return 0;
+    }
+
+    name = (char *)malloc(256);
+    if (name == NULL)
+    {
+        PyErr_NoMemory();
+        goto end;
+    }
+    snprintf(name, 256, "%s", user);
+        
+    code = krb5_parse_name(kcontext, name, &client);
+    if (code) {
+        set_pwchange_error(kcontext, code);
+        goto end;
+    }
+
+    code = verify_krb5_user(kcontext, client, oldpswd, service, &creds);
+    if (! code) {  /* exception set by verify_krb5_user */
+        goto end;
+    }
+
+    code = krb5_change_password(kcontext, &creds, (char*)newpswd,
+                                &result_code, &result_code_string, &result_string);
+    if (code) {
+        set_pwchange_error(kcontext, code);
+        goto end;
+    }
+    if (result_code) {
+        char *message = NULL;
+        bytes = asprintf(
+            &message, "%.*s: %.*s",
+            (int) result_code_string.length,
+            (char *) result_code_string.data,
+            (int) result_string.length,
+            (char *) result_string.data
+        );
+        if (bytes == -1)
+        {
+            PyErr_NoMemory();
+        }
+        else
+        {
+            PyErr_SetObject(
+                PwdChangeException_class,
+                Py_BuildValue("((s:i))", message, result_code)
+            );
+            free(message);
+        }
+        goto end;
+    }
+
+    ret = 1; /* success */
+
+end:
+#ifdef PRINTFS
+    printf("%s: ret=%d user=%s\n", __FUNCTION__, ret, name);
+#endif
+
+    if (name) {
+        free(name);
+    }
+    if (client) {
+        krb5_free_principal(kcontext, client);
+    }
+    krb5_free_context(kcontext);
+
+    return ret;
+}
diff --git a/shell/ext-py/kerberos-1.3.1/src/kerberospw.h b/shell/ext-py/kerberos-1.3.1/src/kerberospw.h
new file mode 100644
index 000000000..250f2d534
--- /dev/null
+++ b/shell/ext-py/kerberos-1.3.1/src/kerberospw.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008 Guido Guenther <ag...@sigxcpu.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **/
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+
+#define krb5_get_err_text(context,code) error_message(code)
+
+int change_user_krb5pwd(
+    const char *user, const char* oldpswd, const char *newpswd
+);