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:56 UTC

[impala] branch master updated (0d55a4a54 -> b867f4c4f)

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

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


    from 0d55a4a54 IMPALA-11269: Combine log entries of column accesses in the same table
     new f4b2ef5a0 IMPALA-11275: log thread info during minidump
     new 7a7934ffb IMPALA-11283: Push-down IS_NULL and NOT_NULL predicates to iceberg
     new b867f4c4f IMPALA-10745 (part 2): Support Kerberos over HTTP for impala-shell

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 be/src/util/CMakeLists.txt                         |    2 +
 be/src/util/debug-util-test.cc                     |   15 +
 be/src/util/debug-util.cc                          |   14 +
 be/src/util/debug-util.h                           |    9 +-
 be/src/util/minidump-test.cc                       |   83 ++
 be/src/util/minidump.cc                            |   80 +-
 bin/rat_exclude_files.txt                          |    1 +
 .../org/apache/impala/planner/IcebergScanNode.java |   19 +
 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 +++++++++
 .../setup.cfg                                      |    1 -
 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 +
 .../iceberg-is-null-predicate-push-down.test       |  403 ++++++++
 tests/custom_cluster/test_breakpad.py              |    4 +
 tests/query_test/test_iceberg.py                   |    5 +
 30 files changed, 4216 insertions(+), 18 deletions(-)
 create mode 100644 be/src/util/minidump-test.cc
 create mode 100644 shell/ext-py/kerberos-1.3.1/MANIFEST.in
 create mode 100644 shell/ext-py/kerberos-1.3.1/PKG-INFO
 create mode 100644 shell/ext-py/kerberos-1.3.1/README.md
 create mode 100644 shell/ext-py/kerberos-1.3.1/kerberos.egg-info/PKG-INFO
 create mode 100644 shell/ext-py/kerberos-1.3.1/kerberos.egg-info/SOURCES.txt
 create mode 100644 shell/ext-py/kerberos-1.3.1/kerberos.egg-info/dependency_links.txt
 create mode 100644 shell/ext-py/kerberos-1.3.1/kerberos.egg-info/top_level.txt
 create mode 100644 shell/ext-py/kerberos-1.3.1/pysrc/kerberos.py
 copy shell/ext-py/{prettytable-0.7.2 => kerberos-1.3.1}/setup.cfg (64%)
 create mode 100644 shell/ext-py/kerberos-1.3.1/setup.py
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/base64.c
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/base64.h
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberos.c
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberosbasic.c
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberosbasic.h
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberosgss.c
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberosgss.h
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberospw.c
 create mode 100644 shell/ext-py/kerberos-1.3.1/src/kerberospw.h
 create mode 100644 testdata/workloads/functional-query/queries/QueryTest/iceberg-is-null-predicate-push-down.test


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

Posted by wz...@apache.org.
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
+);


[impala] 01/03: IMPALA-11275: log thread info during minidump

Posted by wz...@apache.org.
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 f4b2ef5a00ac414017f26e038b17740f3807a337
Author: Michael Smith <mi...@cloudera.com>
AuthorDate: Mon May 9 16:24:28 2022 -0700

    IMPALA-11275: log thread info during minidump
    
    Writes ThreadDebugInfo to stdout/stderr when a minidump is generated to
    capture thread and query details related to the dump. Example message:
    > Minidump in thread [1790536]async-exec-thread running query
      1a47cc1e2df94cb4:88dfa08200000000, fragment instance
      0000000000000000:0000000000000000
    
    Refactors DumpCallback so that repeated writes to STDOUT/STDERR are less
    redundant.
    
    Adds unit tests to run with ThreadDebugInfo. Removes the 'static' prefix
    from DumpCallback so it can be invoked from unit tests, but doesn't add
    it to the header as it's intended to be for internal use.
    
    Testing:
    - Added crash to Coordinator::Exec and manually tested dump handling.
    - Added a new unit test for DumpCallback.
    - Ran tests/custom_cluster/test_breakpad.py to verify nothing broke in
      refactor. Those tests don't have ThreadDebugInfo available.
    
    Change-Id: Iea2bdf10db29a0f8ccbe5e767b708781d42a9b8a
    Reviewed-on: http://gerrit.cloudera.org:8080/18508
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/util/CMakeLists.txt            |  2 +
 be/src/util/debug-util-test.cc        | 15 +++++++
 be/src/util/debug-util.cc             | 14 ++++++
 be/src/util/debug-util.h              |  9 +++-
 be/src/util/minidump-test.cc          | 83 +++++++++++++++++++++++++++++++++++
 be/src/util/minidump.cc               | 80 ++++++++++++++++++++++++++-------
 tests/custom_cluster/test_breakpad.py |  4 ++
 7 files changed, 190 insertions(+), 17 deletions(-)

diff --git a/be/src/util/CMakeLists.txt b/be/src/util/CMakeLists.txt
index 69622a5b6..8aa4386de 100644
--- a/be/src/util/CMakeLists.txt
+++ b/be/src/util/CMakeLists.txt
@@ -164,6 +164,7 @@ add_library(UtilTests STATIC
   lru-multi-cache-test.cc
   metrics-test.cc
   min-max-filter-test.cc
+  minidump-test.cc
   openssl-util-test.cc
   os-info-test.cc
   os-util-test.cc
@@ -237,6 +238,7 @@ ADD_UNIFIED_BE_LSAN_TEST(lru-multi-cache-test "LruMultiCache.*")
 ADD_UNIFIED_BE_LSAN_TEST(logging-support-test "LoggingSupport.*")
 ADD_UNIFIED_BE_LSAN_TEST(metrics-test "MetricsTest.*")
 ADD_UNIFIED_BE_LSAN_TEST(min-max-filter-test "MinMaxFilterTest.*")
+ADD_UNIFIED_BE_LSAN_TEST(minidump-test "Minidump.*")
 ADD_UNIFIED_BE_LSAN_TEST(openssl-util-test "OpenSSLUtilTest.*")
 ADD_UNIFIED_BE_LSAN_TEST(os-info-test "OsInfo.*")
 ADD_UNIFIED_BE_LSAN_TEST(os-util-test "OsUtil.*")
diff --git a/be/src/util/debug-util-test.cc b/be/src/util/debug-util-test.cc
index 0b92c2785..417330d51 100644
--- a/be/src/util/debug-util-test.cc
+++ b/be/src/util/debug-util-test.cc
@@ -37,6 +37,21 @@ TEST(DebugUtil, UniqueID) {
   EXPECT_EQ("feedbeeff00d7777:0000000000000020", PrintId(unique_id));
 }
 
+TEST(DebugUtil, UniqueIDCompromised) {
+  TUniqueId unique_id;
+  unique_id.hi = 0xfeedbeeff00d7777ULL;
+  unique_id.lo = 0x2020202020202020ULL;
+  char out[TUniqueIdBufferSize+1];
+  out[TUniqueIdBufferSize] = '\0';
+
+  PrintIdCompromised(unique_id, out);
+  EXPECT_EQ(string("feedbeeff00d7777:2020202020202020"), out);
+
+  unique_id.lo = 0x20ULL;
+  PrintIdCompromised(unique_id, out);
+  EXPECT_EQ(string("feedbeeff00d7777:0000000000000020"), out);
+}
+
 string RecursionStack(int level) {
   if (level == 0) return GetStackTrace();
   return RecursionStack(level - 1);
diff --git a/be/src/util/debug-util.cc b/be/src/util/debug-util.cc
index 734476486..59d61c8b0 100644
--- a/be/src/util/debug-util.cc
+++ b/be/src/util/debug-util.cc
@@ -120,6 +120,20 @@ string PrintId(const UniqueIdPB& id, const string& separator) {
   return out.str();
 }
 
+static void my_i64tohex(int64_t w, char out[16]) {
+  static const char* digits = "0123456789abcdef";
+  for (size_t i = 0, j=60; i < 16; ++i, j -= 4) {
+    out[i] = digits[(w>>j) & 0x0f];
+  }
+}
+
+void PrintIdCompromised(const TUniqueId& id, char out[TUniqueIdBufferSize],
+    const char separator) {
+  my_i64tohex(id.hi, out);
+  out[16] = separator;
+  my_i64tohex(id.lo, out+17);
+}
+
 bool ParseId(const string& s, TUniqueId* id) {
   // For backwards compatibility, this method parses two forms of query ID from text:
   //  - <hex-int64_t><colon><hex-int64_t> - this format is the standard going forward
diff --git a/be/src/util/debug-util.h b/be/src/util/debug-util.h
index 5ae707444..e25ee92ad 100644
--- a/be/src/util/debug-util.h
+++ b/be/src/util/debug-util.h
@@ -87,11 +87,18 @@ std::string PrintThriftEnum(const TParquetBloomFilterWrite::type& value);
 std::string PrintTuple(const Tuple* t, const TupleDescriptor& d);
 std::string PrintRow(TupleRow* row, const RowDescriptor& d);
 std::string PrintBatch(RowBatch* batch);
-/// Converts id to a string represantation. If necessary, the gdb equivalent is:
+/// Converts id to a string representation. If necessary, the gdb equivalent is:
 ///    printf "%lx:%lx\n", id.hi, id.lo
 std::string PrintId(const TUniqueId& id, const std::string& separator = ":");
 std::string PrintId(const UniqueIdPB& id, const std::string& separator = ":");
 
+/// Converts id to a string representation without using any shared library calls.
+/// Follows Breakpad's guidance for compromised contexts, see
+/// https://github.com/google/breakpad/blob/main/docs/linux_starter_guide.md
+constexpr int TUniqueIdBufferSize = 33;
+void PrintIdCompromised(const TUniqueId& id, char out[TUniqueIdBufferSize],
+    const char separator = ':');
+
 inline ostream& operator<<(ostream& os, const UniqueIdPB& id) {
   return os << PrintId(id);
 }
diff --git a/be/src/util/minidump-test.cc b/be/src/util/minidump-test.cc
new file mode 100644
index 000000000..afcf4a883
--- /dev/null
+++ b/be/src/util/minidump-test.cc
@@ -0,0 +1,83 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "util/minidump.h"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
+#include <gtest/gtest.h>
+
+#include "client/linux/handler/minidump_descriptor.h"
+#include "common/thread-debug-info.h"
+
+namespace impala {
+
+bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
+    bool succeeded);
+
+TEST(Minidump, DumpCallback) {
+  testing::internal::CaptureStdout();
+  testing::internal::CaptureStderr();
+
+  google_breakpad::MinidumpDescriptor descriptor("/tmp/arbitrary/path");
+  descriptor.UpdatePath();
+  DumpCallback(descriptor, nullptr, true);
+
+  std::string stdout = testing::internal::GetCapturedStdout();
+  std::string stderr = testing::internal::GetCapturedStderr();
+  boost::regex wrote_minidump("Wrote minidump to /tmp/arbitrary/path/.*\\.dmp");
+
+  for (std::string output : {stdout, stderr}) {
+    std::vector<std::string> lines;
+    boost::split(lines, output, boost::is_any_of("\n\r"), boost::token_compress_on);
+    EXPECT_EQ(3, lines.size());
+    EXPECT_EQ("Minidump with no thread info available.", lines[0]);
+    EXPECT_TRUE(boost::regex_match(lines[1], wrote_minidump));
+    EXPECT_EQ("", lines[2]);
+  }
+}
+
+TEST(Minidump, DumpCallbackWithThread) {
+  testing::internal::CaptureStdout();
+  testing::internal::CaptureStderr();
+
+  ThreadDebugInfo tdi;
+  TUniqueId query, instance;
+  std::tie(query.hi, query.lo, instance.hi, instance.lo) = std::make_tuple(1, 2, 3, 4);
+  tdi.SetQueryId(query);
+  tdi.SetInstanceId(instance);
+  google_breakpad::MinidumpDescriptor descriptor("/tmp/arbitrary/path");
+  descriptor.UpdatePath();
+  DumpCallback(descriptor, nullptr, true);
+
+  std::string stdout = testing::internal::GetCapturedStdout();
+  std::string stderr = testing::internal::GetCapturedStderr();
+  boost::regex minidump_in_thread("Minidump in thread \\[.*\\] running query "
+      "0{15}1:0{15}2, fragment instance 0{15}3:0{15}4");
+  boost::regex wrote_minidump("Wrote minidump to /tmp/arbitrary/path/.*\\.dmp");
+
+  for (std::string output : {stdout, stderr}) {
+    std::vector<std::string> lines;
+    boost::split(lines, output, boost::is_any_of("\n\r"), boost::token_compress_on);
+    EXPECT_EQ(3, lines.size());
+    EXPECT_TRUE(boost::regex_match(lines[0], minidump_in_thread));
+    EXPECT_TRUE(boost::regex_match(lines[1], wrote_minidump));
+    EXPECT_EQ("", lines[2]);
+  }
+}
+
+} // namespace impala
diff --git a/be/src/util/minidump.cc b/be/src/util/minidump.cc
index f09439f70..25a329f80 100644
--- a/be/src/util/minidump.cc
+++ b/be/src/util/minidump.cc
@@ -32,7 +32,9 @@
 #include <sstream>
 
 #include "common/logging.h"
+#include "common/thread-debug-info.h"
 #include "common/version.h"
+#include "util/debug-util.h"
 #include "util/filesystem-util.h"
 #include "util/time.h"
 
@@ -64,31 +66,77 @@ static bool FilterCallback(void* context) {
   return minidumps_enabled;
 }
 
+static void write_dump_threadinfo(int fd, ThreadDebugInfo* thread_info) {
+  constexpr char thread_msg[] = "Minidump in thread ";
+  // pid_t is signed, but valid process IDs must always be positive.
+  const int64_t thread_id = thread_info->GetSystemThreadId();
+  // 20 characters needed for UINT64_MAX.
+  char thread_id_str[20];
+  const unsigned int thread_id_len = my_uint_len(thread_id);
+  my_uitos(thread_id_str, thread_id, thread_id_len);
+  const char* thread_name = thread_info->GetThreadName();
+
+  constexpr char query_msg[] = " running query ";
+  const TUniqueId& query_id = thread_info->GetQueryId();
+  char query_id_str[TUniqueIdBufferSize];
+  PrintIdCompromised(query_id, query_id_str);
+
+  constexpr char instance_msg[] = ", fragment instance ";
+  const TUniqueId& instance_id = thread_info->GetInstanceId();
+  // Format TUniqueId according to PrintId from util/debug-util.h
+  char instance_id_str[TUniqueIdBufferSize];
+  PrintIdCompromised(instance_id, instance_id_str);
+
+  // Example:
+  // > Minidump in thread [1790536]async-exec-thread running query 1a47cc1e2df94cb4:
+  //   88dfa08200000000, fragment instance 0000000000000000:0000000000000000
+  sys_write(fd, thread_msg, sizeof(thread_msg) / sizeof(thread_msg[0]) - 1);
+  sys_write(fd, "[", 1);
+  sys_write(fd, thread_id_str, thread_id_len);
+  sys_write(fd, "]", 1);
+  sys_write(fd, thread_name, my_strlen(thread_name));
+  sys_write(fd, query_msg, sizeof(query_msg) / sizeof(query_msg[0]) - 1);
+  sys_write(fd, query_id_str, TUniqueIdBufferSize);
+  sys_write(fd, instance_msg, sizeof(instance_msg) / sizeof(instance_msg[0]) - 1);
+  sys_write(fd, instance_id_str, TUniqueIdBufferSize);
+  sys_write(fd, "\n", 1);
+}
+
+static void write_dump_path(int fd, const char* path) {
+  // We use the linux syscall support methods from chromium here as per the
+  // recommendation of the breakpad docs to avoid calling into other shared libraries.
+  const char msg[] = "Wrote minidump to ";
+  sys_write(fd, msg, sizeof(msg) / sizeof(msg[0]) - 1);
+    // We use breakpad's reimplementation of strlen to avoid calling into libc.
+  sys_write(fd, path, my_strlen(path));
+  sys_write(fd, "\n", 1);
+}
+
 /// Callback for breakpad. It is called by breakpad whenever a minidump file has been
 /// written and should not be called directly. It logs the event before breakpad crashes
 /// the process. Due to the process being in a failed state we write to stdout/stderr and
 /// let the surrounding redirection make sure the output gets logged. The calls might
 /// still fail in unknown scenarios as the process is in a broken state. However we don't
 /// rely on them as the minidump file has been written already.
-static bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
-    void* context, bool succeeded) {
+bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
+    bool succeeded) {
   // See if a file was written successfully.
   if (succeeded) {
-    // Write message to stdout/stderr, which will usually be captured in the INFO/ERROR
-    // log.
-    const char msg[] = "Wrote minidump to ";
-    const int msg_len = sizeof(msg) / sizeof(msg[0]) - 1;
+    // Write to stdout/stderr, which will usually be captured in the INFO/ERROR log.
+    ThreadDebugInfo* thread_info = GetThreadDebugInfo();
+    if (thread_info != nullptr) {
+      write_dump_threadinfo(STDOUT_FILENO, thread_info);
+      write_dump_threadinfo(STDERR_FILENO, thread_info);
+    } else {
+      const char msg[] = "Minidump with no thread info available.\n";
+      const int msg_len = sizeof(msg) / sizeof(msg[0]) - 1;
+      sys_write(STDOUT_FILENO, msg, msg_len);
+      sys_write(STDERR_FILENO, msg, msg_len);
+    }
+
     const char* path = descriptor.path();
-    // We use breakpad's reimplementation of strlen to avoid calling into libc.
-    const int path_len = my_strlen(path);
-    // We use the linux syscall support methods from chromium here as per the
-    // recommendation of the breakpad docs to avoid calling into other shared libraries.
-    sys_write(STDOUT_FILENO, msg, msg_len);
-    sys_write(STDOUT_FILENO, path, path_len);
-    sys_write(STDOUT_FILENO, "\n", 1);
-    sys_write(STDERR_FILENO, msg, msg_len);
-    sys_write(STDERR_FILENO, path, path_len);
-    sys_write(STDERR_FILENO, "\n", 1);
+    write_dump_path(STDOUT_FILENO, path);
+    write_dump_path(STDERR_FILENO, path);
   }
   // Return the value received in the call as described in the minidump documentation. If
   // this values is true, then no other handlers will be called. Breakpad will still crash
diff --git a/tests/custom_cluster/test_breakpad.py b/tests/custom_cluster/test_breakpad.py
index 033e4790d..d106093ef 100644
--- a/tests/custom_cluster/test_breakpad.py
+++ b/tests/custom_cluster/test_breakpad.py
@@ -143,6 +143,10 @@ class TestBreakpadBase(CustomClusterTestSuite):
         expected_count=expected_count)
     self.assert_impalad_log_contains('ERROR', 'Wrote minidump to ',
         expected_count=expected_count)
+    self.assert_impalad_log_contains('INFO', 'Minidump with no thread info available.',
+        expected_count=expected_count)
+    self.assert_impalad_log_contains('ERROR', 'Minidump with no thread info available.',
+        expected_count=expected_count)
 
 
 class TestBreakpadCore(TestBreakpadBase):


[impala] 02/03: IMPALA-11283: Push-down IS_NULL and NOT_NULL predicates to iceberg

Posted by wz...@apache.org.
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 7a7934ffba70a1448be081b288cfc37e0c846d93
Author: LPL <li...@sensorsdata.cn>
AuthorDate: Sat May 7 16:29:21 2022 +0800

    IMPALA-11283: Push-down IS_NULL and NOT_NULL predicates to iceberg
    
    This patch implements pushing the IS_NULL and NOT_NULL predicates down
    to Iceberg.
    
    Testing:
      - Added end-to-end test
    
    Change-Id: I9c3608af67b552bebc55dcc5526f61f5439967bf
    Reviewed-on: http://gerrit.cloudera.org:8080/18504
    Reviewed-by: Zoltan Borok-Nagy <bo...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 .../org/apache/impala/planner/IcebergScanNode.java |  19 +
 .../iceberg-is-null-predicate-push-down.test       | 403 +++++++++++++++++++++
 tests/query_test/test_iceberg.py                   |   5 +
 3 files changed, 427 insertions(+)

diff --git a/fe/src/main/java/org/apache/impala/planner/IcebergScanNode.java b/fe/src/main/java/org/apache/impala/planner/IcebergScanNode.java
index 1035cd367..f3d521d13 100644
--- a/fe/src/main/java/org/apache/impala/planner/IcebergScanNode.java
+++ b/fe/src/main/java/org/apache/impala/planner/IcebergScanNode.java
@@ -36,6 +36,7 @@ import org.apache.impala.analysis.BoolLiteral;
 import org.apache.impala.analysis.DateLiteral;
 import org.apache.impala.analysis.Expr;
 import org.apache.impala.analysis.InPredicate;
+import org.apache.impala.analysis.IsNullPredicate;
 import org.apache.impala.analysis.LiteralExpr;
 import org.apache.impala.analysis.MultiAggregateInfo;
 import org.apache.impala.analysis.NumericLiteral;
@@ -216,6 +217,8 @@ public class IcebergScanNode extends HdfsScanNode {
       convertIcebergPredicate(analyzer, (BinaryPredicate) expr);
     } else if (expr instanceof InPredicate) {
       convertIcebergPredicate(analyzer, (InPredicate) expr);
+    } else if (expr instanceof IsNullPredicate) {
+      convertIcebergPredicate((IsNullPredicate) expr);
     }
   }
 
@@ -272,6 +275,22 @@ public class IcebergScanNode extends HdfsScanNode {
     icebergPredicates_.add(Expressions.in(col.getName(), values));
   }
 
+  private void convertIcebergPredicate(IsNullPredicate predicate) {
+    // Do not convert if there is an implicit cast
+    if (!(predicate.getChild(0) instanceof SlotRef)) return;
+    SlotRef ref = (SlotRef) predicate.getChild(0);
+
+    // If predicate contains map/struct, this column would be null
+    Column col = ref.getDesc().getColumn();
+    if (col == null) return;
+
+    if (predicate.isNotNull()) {
+      icebergPredicates_.add(Expressions.notNull(col.getName()));
+    } else{
+      icebergPredicates_.add(Expressions.isNull(col.getName()));
+    }
+  }
+
   private Object getIcebergValue(Analyzer analyzer, SlotRef ref, LiteralExpr literal)
       throws ImpalaException {
     IcebergColumn iceCol = (IcebergColumn) ref.getDesc().getColumn();
diff --git a/testdata/workloads/functional-query/queries/QueryTest/iceberg-is-null-predicate-push-down.test b/testdata/workloads/functional-query/queries/QueryTest/iceberg-is-null-predicate-push-down.test
new file mode 100644
index 000000000..e9b01cfbf
--- /dev/null
+++ b/testdata/workloads/functional-query/queries/QueryTest/iceberg-is-null-predicate-push-down.test
@@ -0,0 +1,403 @@
+====
+---- QUERY
+# Test BIGINT/DOUBLE/STRING/TIMESTAMP/DATE/DECIMAL IsNullPredicate push-down
+create table ice_is_null_pred_pd (
+  col_i INT,
+  col_bi BIGINT,
+  col_db DOUBLE,
+  col_str STRING,
+  col_ts TIMESTAMP,
+  col_dt DATE,
+  col_dc1 DECIMAL(9, 3),
+  col_dc2 DECIMAL(18, 3),
+  col_dc3 DECIMAL(38, 3)
+) partitioned by spec (col_i) stored as iceberg tblproperties ('write.format.default' = 'parquet');
+---- RESULTS
+'Table has been created.'
+====
+---- QUERY
+insert into
+  ice_is_null_pred_pd
+values
+  (0, NULL, 2.71820, '_1700-01-01 00:00:00', '1400-01-01 00:00:00', DATE'1400-01-01', 123.450, 1234567890.120, 1234567890123456789.010),
+  (1, 12345678901, NULL, 'A1700-01-01 00:00:00', '1400-01-01 01:01:01', DATE'1400-01-01', 123.450, 1234567890.120, 1234567890123456789.010),
+  (2, 12345678902, 2.71822, NULL, '1400-01-01 02:02:02', DATE'1400-01-01', 123.450, 1234567890.120, 1234567890123456789.010),
+  (3, 12345678903, 2.71823, '1700-01-01 00:00:00', '1500-01-01 00:00:00', DATE'1500-01-01', 123.450, 1234567890.120, 1234567890123456789.010),
+  (4, 12345678904, 2.71824, '1700-01-01 00:00:00', NULL, DATE'1500-01-01', 123.450, 1234567890.120, 1234567890123456789.010),
+  (5, 12345678905, 2.71825, '1700-01-01 00:00:00', '1500-01-01 02:02:02', NULL, 123.450, 1234567890.120, 1234567890123456789.010),
+  (6, 12345678906, 2.71826, '1700-01-01 00:00:00', '1600-01-01 00:00:00', DATE'1600-01-01', NULL, 1234567890.120, 1234567890123456789.010),
+  (7, 12345678907, 2.71827, '1700-01-01 00:00:00', '1600-01-01 01:01:01', DATE'1600-01-01', 123.450, NULL, 1234567890123456789.010),
+  (8, 12345678908, 2.71828, '1700-01-01 00:00:00', '1600-01-01 02:02:02', DATE'1600-01-01', 123.450, 1234567890.120, NULL);
+select count(1) from ice_is_null_pred_pd;
+---- RESULTS
+9
+---- TYPES
+BIGINT
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 0
+aggregation(SUM, NumFileMetadataRead): 9
+====
+---- QUERY
+show files in ice_is_null_pred_pd;
+---- RESULTS
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=0/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=1/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=2/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=3/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=4/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=5/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=6/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=7/.*.0.parq','.*',''
+row_regex:'$NAMENODE/test-warehouse/$DATABASE.db/ice_is_null_pred_pd/data/col_i=8/.*.0.parq','.*',''
+---- TYPES
+STRING, STRING, STRING
+====
+---- QUERY
+# Start testing predicate push-down for bigint column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_bi is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_bi as double) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_bi as TIMESTAMP) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_bi + 1 is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_bi is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for double column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_db is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_db as decimal(6,5)) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_db is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for string column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_str is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  col_i
+from
+  ice_is_null_pred_pd
+where
+  cast(col_str as TIMESTAMP) is null
+order by 1;
+---- RESULTS
+0
+1
+2
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_str is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for timestamp column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_ts is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_ts as string) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_ts is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for date column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dt is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_dt as string) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dt is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for decimal(9,3) column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc1 is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_dc1 as double) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc1 is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for decimal(18,3) column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc2 is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_dc2 as double) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc2 is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
+---- QUERY
+# Start testing predicate push-down for decimal(38,3) column
+# The IS_NULL predicate matches 1 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc3 is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 1
+====
+---- QUERY
+# The IS_NULL predicate will not push down
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  cast(col_dc3 as string) is null;
+---- RESULTS
+1
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 9
+====
+---- QUERY
+# The NOT_NULL predicate matches 8 row group
+select
+  count(1)
+from
+  ice_is_null_pred_pd
+where
+  col_dc3 is not null;
+---- RESULTS
+8
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRowGroups): 8
+====
\ No newline at end of file
diff --git a/tests/query_test/test_iceberg.py b/tests/query_test/test_iceberg.py
index 3a64810d5..727540449 100644
--- a/tests/query_test/test_iceberg.py
+++ b/tests/query_test/test_iceberg.py
@@ -617,3 +617,8 @@ class TestIcebergTable(ImpalaTestSuite):
   def test_in_predicate_push_down(self, vector, unique_database):
     self.run_test_case('QueryTest/iceberg-in-predicate-push-down', vector,
                        use_db=unique_database)
+
+  @pytest.mark.execute_serially
+  def test_is_null_predicate_push_down(self, vector, unique_database):
+    self.run_test_case('QueryTest/iceberg-is-null-predicate-push-down', vector,
+                       use_db=unique_database)