Skip to content

Latest commit

 

History

History
947 lines (929 loc) · 31.3 KB

20200623-minutes.md

File metadata and controls

947 lines (929 loc) · 31.3 KB

Authentication Working Group Minutes

Tuesday, 2020/06/23

10:00 am ET

Attendees:

Terrell Russell, Jason Coposky, Jaspreet Gill, Alan King, Kory Draughn, Stefan Wolfsheimer (SURFsara), Claudio Cacciari (SURFsara), Brett Hartley (Sanger), John Constable (Sanger), Lazlo Westerhof (Utrecht University), Ton Smeele (Utrecht University)

Minutes

SUMMARY:

Stefan (SURF) has contributed poc_surf to demonstrate their proposed flow.

  • Built against existing iRODS, not the new framework yet
  • Still includes a few changes to the iRODS server and icommands, hoping to remove that with new framework.
  • Still has a standalone auxiliary PAM service

DISCUSSION:

  • Demonstrated with 'required' and 'sufficient', interleaved.
  • Should work with multiple factors as sufficient (webdav/icommands/token)
  • Should work with OIDC tokens as well, even though they are not reusable b/c server can retry/reauth.

ACTION ITEM:

Next POCs:

  • PAM
  • OpenID Connect
  • Kerberos

Stefan diff with iCommands:

diff --git a/src/iinit.cpp b/src/iinit.cpp
index 9607c07..7032430 100644
--- a/src/iinit.cpp
+++ b/src/iinit.cpp
@@ -279,7 +279,9 @@ main( int argc, char **argv ) {
     if ( irods::AUTH_PAM_SCHEME == lower_scheme ) {
         doPassword = 0;
     }
-
+    if ( lower_scheme == "pam_interactive") {
+        doPassword = 0;
+    }
     if ( strcmp( my_env.rodsUserName, ANONYMOUS_USER ) == 0 ) {
         doPassword = 0;
     }
@@ -346,8 +348,39 @@ main( int argc, char **argv ) {
         // if this succeeded, do the regular login below to check
         // that the generated password works properly.
     } // if pam
-
-    if ( strcmp( my_env.rodsAuthScheme, AUTH_OPENID_SCHEME ) == 0 ) {
+    if ( "pam_interactive" == lower_scheme ) {
+      irods::kvp_map_t ctx_map;
+      if(myRodsArgs.verbose == True)
+      {
+        ctx_map["VERBOSE"] = "true";
+      }
+      else
+      {
+        ctx_map["VERBOSE"] = "false";
+      }
+      if(myRodsArgs.veryVerbose == True)
+      {
+        ctx_map["VVERBOSE"] = "true";
+      }
+      else
+      {
+        ctx_map["VVERBOSE"] = "false";
+      }
+      // tell the plugin that we are using iinit
+      ctx_map["ECHO"] = "true";
+
+      std::string ctx_str = irods::escaped_kvp_string(ctx_map);
+      // =-=-=-=-=-=-=-
+      // pass the context with the ttl as well as an override which
+      // demands the pam authentication plugin
+      status = clientLogin( Conn, ctx_str.c_str(), "pam_interactive" );
+      if ( status != 0 )
+      {
+        rcDisconnect( Conn );
+        return 7;
+      }
+    }
+    else if ( strcmp( my_env.rodsAuthScheme, AUTH_OPENID_SCHEME ) == 0 ) {
         irods::kvp_map_t ctx_map;
         try {
             std::string client_provider_cfg = irods::get_environment_property<std::string&>( "openid_provider" );

Stefan diff with core:

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c74f74a..a41fabf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -886,6 +886,7 @@ set(
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_network_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_osauth_auth_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pam_auth_object.cpp
+  ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pam_interactive_auth_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_ssl_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_tcp_object.cpp
   )
@@ -1104,6 +1105,7 @@ set(
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_osauth_auth_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pack_table.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pam_auth_object.cpp
+  ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pam_interactive_auth_object.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_parse_command_line_options.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_path_recursion.cpp
   ${CMAKE_SOURCE_DIR}/lib/core/src/irods_pluggable_auth_scheme.cpp
diff --git a/lib/core/include/irods_pam_interactive_auth_object.hpp b/lib/core/include/irods_pam_interactive_auth_object.hpp
new file mode 100644
index 0000000..d744773
--- /dev/null
+++ b/lib/core/include/irods_pam_interactive_auth_object.hpp
@@ -0,0 +1,52 @@
+#pragma once
+#include "irods_error.hpp"
+#include "irods_auth_object.hpp"
+
+#include "rcConnect.h"
+
+// =-=-=-=-=-=-=-
+// boost includes
+#include <boost/shared_ptr.hpp>
+
+namespace irods {
+
+/// =-=-=-=-=-=-=-
+/// @brief constant defining the native auth scheme string
+    const std::string AUTH_PAM_INTERACTIVE_SCHEME( "pam_interactive" );
+
+/// =-=-=-=-=-=-=-
+/// @brief object for a native irods authenticaion sceheme
+    class pam_interactive_auth_object : public auth_object {
+        public:
+            /// =-=-=-=-=-=-=-
+            /// @brief Ctor
+            pam_interactive_auth_object( rError_t* _r_error );
+            virtual ~pam_interactive_auth_object();
+            pam_interactive_auth_object( const pam_interactive_auth_object& );
+
+            /// =-=-=-=-=-=-=-
+            /// @brief assignment operator
+            virtual pam_interactive_auth_object&  operator=( const pam_interactive_auth_object& );
+
+            /// =-=-=-=-=-=-=-
+            /// @brief Comparison operator
+            virtual bool operator==( const pam_interactive_auth_object& ) const;
+
+            /// =-=-=-=-=-=-=-
+            /// @brief Plugin resolution operation
+            virtual error resolve(
+                const std::string&, // interface for which to resolve
+                plugin_ptr& );      // ptr to resolved plugin
+
+            /// =-=-=-=-=-=-=-
+            /// @brief serialize object to key-value pairs
+            virtual error get_re_vars( rule_engine_vars_t& );
+
+        private:
+
+    }; // class pam_auth_object
+
+/// @brief Helpful typedef
+    typedef boost::shared_ptr<pam_interactive_auth_object> pam_interactive_auth_object_ptr;
+
+}; // namespace irods
diff --git a/lib/core/src/clientLogin.cpp b/lib/core/src/clientLogin.cpp
index 9b5111a..8bb3b9f 100644
--- a/lib/core/src/clientLogin.cpp
+++ b/lib/core/src/clientLogin.cpp
@@ -20,6 +20,7 @@
 #include "irods_auth_constants.hpp"
 #include "irods_native_auth_object.hpp"
 #include "irods_pam_auth_object.hpp"
+#include "irods_pam_interactive_auth_object.hpp"
 #include "authPluginRequest.h"
 #include "irods_configuration_parser.hpp"
 #include "irods_configuration_keywords.hpp"
@@ -274,6 +275,9 @@ int clientLogin(
             if ( irods::AUTH_PAM_SCHEME == auth_scheme ) {
                 auth_scheme = irods::AUTH_NATIVE_SCHEME;
             }
+            if ( irods::AUTH_PAM_INTERACTIVE_SCHEME == auth_scheme ) {
+                auth_scheme = irods::AUTH_NATIVE_SCHEME;
+            }
         } // if _scheme_override
     } // if client side auth
 
diff --git a/lib/core/src/irods_auth_factory.cpp b/lib/core/src/irods_auth_factory.cpp
index ba29ba4..d259094 100644
--- a/lib/core/src/irods_auth_factory.cpp
+++ b/lib/core/src/irods_auth_factory.cpp
@@ -2,6 +2,7 @@
 #include "irods_auth_factory.hpp"
 #include "irods_native_auth_object.hpp"
 #include "irods_pam_auth_object.hpp"
+#include "irods_pam_interactive_auth_object.hpp"
 #include "irods_osauth_auth_object.hpp"
 #include "irods_gsi_object.hpp"
 #include "irods_krb_object.hpp"
@@ -26,6 +27,9 @@ namespace irods {
         else if ( AUTH_PAM_SCHEME == scheme ) {
             _ptr.reset( new pam_auth_object( _r_error ) );
         }
+        else if ( AUTH_PAM_INTERACTIVE_SCHEME == scheme ) {
+          _ptr.reset( new pam_interactive_auth_object( _r_error ) );
+        }
         else if ( AUTH_OSAUTH_SCHEME == scheme ) {
             _ptr.reset( new osauth_auth_object( _r_error ) );
         }
diff --git a/lib/core/src/irods_pam_interactive_auth_object.cpp b/lib/core/src/irods_pam_interactive_auth_object.cpp
new file mode 100644
index 0000000..4983a95
--- /dev/null
+++ b/lib/core/src/irods_pam_interactive_auth_object.cpp
@@ -0,0 +1,124 @@
+// =-=-=-=-=-=-=-
+#include "irods_pam_interactive_auth_object.hpp"
+#include "irods_auth_manager.hpp"
+#include "irods_auth_plugin.hpp"
+
+extern int ProcessType;
+
+// =-=-=-=-=-=-=-
+// irods includes
+#include "rcMisc.h"
+
+namespace irods {
+
+// =-=-=-=-=-=-=-
+// public - ctor
+    pam_interactive_auth_object::pam_interactive_auth_object(
+        rError_t* _r_error ) :
+        auth_object( _r_error ) {
+    } // ctor
+
+// =-=-=-=-=-=-=-
+// public - dtor
+    pam_interactive_auth_object::~pam_interactive_auth_object() {
+    } // dtor
+
+// =-=-=-=-=-=-=-
+// public - assignment operator
+    pam_interactive_auth_object::pam_interactive_auth_object(
+        const pam_interactive_auth_object& _rhs ) :
+        auth_object( _rhs ) {
+        user_name_   = _rhs.user_name_;
+        zone_name_   = _rhs.zone_name_;
+        context_     = _rhs.context_;
+    }
+
+// =-=-=-=-=-=-=-
+// public - assignment operator
+    pam_interactive_auth_object& pam_interactive_auth_object::operator=(
+        const pam_interactive_auth_object& _rhs ) {
+        auth_object::operator=( _rhs );
+        user_name_   = _rhs.user_name_;
+        zone_name_   = _rhs.zone_name_;
+        return *this;
+    }
+
+// =-=-=-=-=-=-=-
+// public - equality operator
+    bool pam_interactive_auth_object::operator==(
+        const pam_interactive_auth_object& ) const {
+        return false;
+    }
+
+// =-=-=-=-=-=-=-
+// public - resolve a plugin given an interface
+    error pam_interactive_auth_object::resolve(
+        const std::string& _interface,
+        plugin_ptr&        _ptr ) {
+        // =-=-=-=-=-=-=-
+        // check the interface type and error out if it
+        // isnt a auth interface
+        if ( AUTH_INTERFACE != _interface ) {
+            std::stringstream msg;
+            msg << "pam_interactive_auth_object does not support a [";
+            msg << _interface;
+            msg << "] plugin interface";
+            return ERROR( SYS_INVALID_INPUT_PARAM, msg.str() );
+
+        }
+
+        // =-=-=-=-=-=-=-
+        // ask the auth manager for a native auth plugin
+        auth_ptr a_ptr;
+        error ret = auth_mgr.resolve(
+                        AUTH_PAM_INTERACTIVE_SCHEME,
+                        a_ptr );
+        if ( !ret.ok() ) {
+            // =-=-=-=-=-=-=-
+            // attempt to load the plugin, in this case the type,
+            // instance name, key etc are all native as there is only
+            // the need for one instance of a native object, etc.
+            std::string empty_context( "" );
+            ret = auth_mgr.init_from_type(
+                      ProcessType,
+                      AUTH_PAM_INTERACTIVE_SCHEME,
+                      AUTH_PAM_INTERACTIVE_SCHEME,
+                      AUTH_PAM_INTERACTIVE_SCHEME,
+                      empty_context,
+                      a_ptr );
+            if ( !ret.ok() ) {
+                return PASS( ret );
+
+            }
+            else {
+                // =-=-=-=-=-=-=-
+                // upcast for out variable
+                _ptr = boost::dynamic_pointer_cast< plugin_base >( a_ptr );
+                return SUCCESS();
+
+            }
+
+        } // if !ok
+
+        // =-=-=-=-=-=-=-
+        // upcast for out variable
+        _ptr = boost::dynamic_pointer_cast< plugin_base >( a_ptr );
+
+        return SUCCESS();
+
+    } // resolve
+
+// =-=-=-=-=-=-=-
+// public - serialize object to kvp
+    error pam_interactive_auth_object::get_re_vars(
+        rule_engine_vars_t& _kvp ) {
+        // =-=-=-=-=-=-=-
+        // all we have in this object is the auth results
+        _kvp["zone_name"] = zone_name_.c_str();
+        _kvp["user_name"] = user_name_.c_str();
+        
+        return SUCCESS();
+
+    } // get_re_vars
+
+}; // namespace irods
diff --git a/plugins/auth/CMakeLists.txt b/plugins/auth/CMakeLists.txt
index a8f8104..a884d91 100644
--- a/plugins/auth/CMakeLists.txt
+++ b/plugins/auth/CMakeLists.txt
@@ -16,10 +16,16 @@ set(
   )
 
 set(
+  IRODS_AUTH_PLUGIN_pam_interactive_SOURCES
+  ${CMAKE_SOURCE_DIR}/plugins/auth/pam/libpam_interactive.cpp
+  )
+
+set(
   IRODS_AUTH_PLUGINS
   native
   osauth
   pam
+  pam_interactive
   )
 
 set(
diff --git a/plugins/auth/pam/libpam_interactive.cpp b/plugins/auth/pam/libpam_interactive.cpp
new file mode 100644
index 0000000..b1b9920
--- /dev/null
+++ b/plugins/auth/pam/libpam_interactive.cpp
@@ -0,0 +1,565 @@
+// =-=-=-=-=-=-=-
+// irods includes
+#define USE_SSL 1
+#include "sslSockComm.h"
+
+#include "rodsDef.h"
+#include "msParam.h"
+#include "rcConnect.h"
+#include "authRequest.h"
+#include "authResponse.h"
+#include "authCheck.h"
+#include "miscServerFunct.hpp"
+#include "authPluginRequest.h"
+#include "icatHighLevelRoutines.hpp"
+
+// =-=-=-=-=-=-=-
+#include "irods_auth_plugin.hpp"
+#include "irods_auth_constants.hpp"
+#include "irods_pam_interactive_auth_object.hpp"
+#include "irods_stacktrace.hpp"
+#include "irods_kvp_string_parser.hpp"
+#include "irods_client_server_negotiation.hpp"
+
+// =-=-=-=-=-=-=-
+// boost includes
+#include "boost/lexical_cast.hpp"
+
+// =-=-=-=-=-=-=-
+// stl includes
+#include <sstream>
+#include <string>
+#include <iostream>
+#include <termios.h>
+#include <unistd.h>
+
+// =-=-=-=-=-=-=-
+// system includes
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+int get64RandomBytes( char *buf );
+
+// =-=-=-=-=-=-=-
+// establish context - take the auth request results and massage them
+// for the auth response call
+irods::error pam_auth_client_start(
+    irods::plugin_context& _ctx,
+    rcComm_t*                    _comm,
+    const char*                  _context ) {
+    irods::error result = SUCCESS();
+    irods::error ret;
+
+    // =-=-=-=-=-=-=-
+    // validate incoming parameters
+    ret = _ctx.valid< irods::pam_interactive_auth_object >();
+    if ( ( result = ASSERT_PASS( ret, "Invalid plugin context." ) ).ok() ) {
+        if ( ( result = ASSERT_ERROR( _comm, SYS_INVALID_INPUT_PARAM, "Null comm pointer." ) ).ok() ) {
+            if ( ( result = ASSERT_ERROR( _context, SYS_INVALID_INPUT_PARAM, "Null context pointer." ) ).ok() ) {
+                // =-=-=-=-=-=-=-
+                // parse the kvp out of the _resp->username string
+                irods::kvp_map_t kvp;
+                irods::error ret = irods::parse_escaped_kvp_string( _context, kvp );
+                if ( ( result = ASSERT_PASS( ret, "Failed to parse the key-value pairs." ) ).ok() ) {
+                    // =-=-=-=-=-=-=-
+                    // simply cache the context string for a rainy day...
+                    // or to pass to the auth client call later.
+                    irods::pam_interactive_auth_object_ptr ptr = boost::dynamic_pointer_cast<
+                                                         irods::pam_interactive_auth_object>(
+                                                             _ctx.fco() );
+                    ptr->context(_context);
+
+                    std::string password = kvp[ irods::AUTH_PASSWORD_KEY ];
+                    std::string ttl_str  = kvp[ irods::AUTH_TTL_KEY ];
+
+                    // =-=-=-=-=-=-=-
+                    // prompt for a password if necessary
+                    char new_password[ MAX_PASSWORD_LEN + 2 ];
+                    if ( password.empty() ) {
+#ifdef WIN32
+                        HANDLE hStdin = GetStdHandle( STD_INPUT_HANDLE );
+                        DWORD mode;
+                        GetConsoleMode( hStdin, &mode );
+                        DWORD lastMode = mode;
+                        mode &= ~ENABLE_ECHO_INPUT;
+                        BOOL error = !SetConsoleMode( hStdin, mode );
+                        int errsv = -1;
+#else
+                        struct termios tty;
+                        tcgetattr( STDIN_FILENO, &tty );
+                        tcflag_t oldflag = tty.c_lflag;
+                        tty.c_lflag &= ~ECHO;
+                        int error = tcsetattr( STDIN_FILENO, TCSANOW, &tty );
+                        int errsv = errno;
+#endif
+                        if ( error ) {
+                            printf( "WARNING: Error %d disabling echo mode. Password will be displayed in plaintext.", errsv );
+                        }
+                        printf( "Enter your current PAM password:" );
+                        std::string password = "";
+                        getline( std::cin, password );
+                        strncpy( new_password, password.c_str(), MAX_PASSWORD_LEN );
+                        printf( "\n" );
+#ifdef WIN32
+                        if ( !SetConsoleMode( hStdin, lastMode ) ) {
+                            printf( "Error reinstating echo mode." );
+                        }
+#else
+                        tty.c_lflag = oldflag;
+                        if ( tcsetattr( STDIN_FILENO, TCSANOW, &tty ) ) {
+                            printf( "Error reinstating echo mode." );
+                        }
+#endif
+
+                        // =-=-=-=-=-=-=-
+                        // rebuilt and reset context string
+                        irods::kvp_map_t ctx_map;
+                        ctx_map[irods::AUTH_TTL_KEY] = ttl_str;
+                        ctx_map[irods::AUTH_PASSWORD_KEY] = new_password;
+                        std::string ctx_str = irods::escaped_kvp_string(
+                                                  ctx_map);
+                        ptr->context( ctx_str );
+
+                    }
+
+
+                    // =-=-=-=-=-=-=-
+                    // set the user name from the conn
+                    ptr->user_name( _comm->proxyUser.userName );
+
+                    // =-=-=-=-=-=-=-
+                    // set the zone name from the conn
+                    ptr->zone_name( _comm->proxyUser.rodsZone );
+                }
+            }
+        }
+    }
+
+    return result;
+
+} // pam_auth_client_start
+
+// =-=-=-=-=-=-=-
+// handle an agent-side auth request call
+irods::error pam_auth_client_request(
+    irods::plugin_context& _ctx,
+    rcComm_t*                    _comm ) {
+    // =-=-=-=-=-=-=-
+    // validate incoming parameters
+    if ( !_ctx.valid< irods::pam_interactive_auth_object >().ok() ) {
+        return ERROR(
+                   SYS_INVALID_INPUT_PARAM,
+                   "invalid plugin context" );
+
+    }
+    else if ( !_comm ) {
+        return ERROR(
+                   SYS_INVALID_INPUT_PARAM,
+                   "null comm ptr" );
+
+    }
+
+    // =-=-=-=-=-=-=-
+    // get the auth object
+    irods::pam_interactive_auth_object_ptr ptr = boost::dynamic_pointer_cast <
+                                     irods::pam_interactive_auth_object > ( _ctx.fco() );
+    // =-=-=-=-=-=-=-
+    // get the context string
+    std::string context = ptr->context( );
+    if ( context.empty() ) {
+        return ERROR(
+                   SYS_INVALID_INPUT_PARAM,
+                   "empty plugin context string" );
+    }
+
+    // =-=-=-=-=-=-=-
+    // expand the context string then append the auth scheme
+    // and user name, then reencode into a string
+    irods::kvp_map_t ctx_map;
+    irods::error ret = irods::parse_escaped_kvp_string(
+                           context,
+                           ctx_map);
+    if( !ret.ok() ) {
+        return PASS(ret);
+    }
+
+    ctx_map[irods::AUTH_USER_KEY]=ptr->user_name();
+    std::string ctx_str = irods::escaped_kvp_string(
+                              ctx_map);
+
+    // =-=-=-=-=-=-=-
+    // error check string size against MAX_NAME_LEN
+    if ( context.size() > MAX_NAME_LEN ) {
+        return ERROR(
+                   -1,
+                   "context string > max name len" );
+    }
+
+    // =-=-=-=-=-=-=-
+    // copy the context to the req in struct
+    authPluginReqInp_t req_in;
+    strncpy(
+        req_in.context_,
+        ctx_str.c_str(),
+        ctx_str.size() + 1 );
+
+    // =-=-=-=-=-=-=-
+    // copy the auth scheme to the req in struct
+    strncpy(
+        req_in.auth_scheme_,
+        irods::AUTH_PAM_INTERACTIVE_SCHEME.c_str(),
+        irods::AUTH_PAM_INTERACTIVE_SCHEME.size() + 1 );
+
+    // =-=-=-=-=-=-=-
+    // check to see if SSL is currently in place
+    bool using_ssl = ( irods::CS_NEG_USE_SSL == _comm->negotiation_results );
+
+    // =-=-=-=-=-=-=-
+    // warm up SSL if it is not already in use
+    if ( !using_ssl ) {
+        int err = sslStart( _comm );
+        if ( err ) {
+            return ERROR( err, "failed to enable ssl" );
+        }
+    }
+
+    // =-=-=-=-=-=-=-
+    // make the call to our auth request
+    authPluginReqOut_t* req_out = 0;
+    int status = rcAuthPluginRequest( _comm, &req_in, &req_out );
+
+    // =-=-=-=-=-=-=-
+    // shut down SSL if it was not already in use
+    if ( !using_ssl )  {
+        sslEnd( _comm );
+    }
+
+    // =-=-=-=-=-=-=-
+    // handle errors and exit
+    if ( status < 0 ) {
+        return ERROR( status, "call to rcAuthRequest failed." );
+    }
+    else {
+        // =-=-=-=-=-=-=-
+        // copy over the resulting irods pam pasword
+        // and cache the result in our auth object
+        ptr->request_result( req_out->result_ );
+        status = obfSavePw( 0, 0, 0, req_out->result_ );
+        free( req_out );
+        return SUCCESS();
+
+    }
+
+} // pam_auth_client_request
+
+/// =-=-=-=-=-=-=-
+/// @brief function to run the local exec which will
+///        actually do the auth check for us
+#ifndef PAM_AUTH_CHECK_PROG
+#define PAM_AUTH_CHECK_PROG  "./irodsPamAuthCheck"
+#endif
+int run_pam_auth_check(
+    const std::string& _username,
+    const std::string& _password ) {
+
+    int p2cp[2]; /* parent to child pipe */
+    int pid, i;
+    int status;
+
+    if ( pipe( p2cp ) < 0 ) {
+        return SYS_PIPE_ERROR;
+    }
+    pid = fork();
+    if ( pid == -1 ) {
+        return SYS_FORK_ERROR;
+    }
+
+    if ( pid )  {
+        /*
+           This is still the parent.  Write the message to the child and
+           then wait for the exit and status.
+        */
+        if ( write( p2cp[1], _password.c_str(), _password.size() ) == -1 ) {
+            int errsv = errno;
+            irods::log( ERROR( errsv, "Error writing from parent to child." ) );
+        }
+        close( p2cp[1] );
+        waitpid( pid, &status, 0 );
+        return status;
+    }
+    else {
+        /* This is the child */
+        if ( dup2( p2cp[0], STDIN_FILENO ) == -1 ) { /* Make stdin come from read end of the pipe */
+            int errsv = errno;
+            irods::log( ERROR( errsv, "Error duplicating the file descriptor." ) );
+        }
+        close( p2cp[1] );
+        i = execl( PAM_AUTH_CHECK_PROG, PAM_AUTH_CHECK_PROG, _username.c_str(),
+                   ( char * )NULL );
+        perror( "execl" );
+        printf( "execl failed %d\n", i );
+    }
+    return ( SYS_FORK_ERROR ); /* avoid compiler warning */
+
+} // run_pam_auth_check
+
+#ifdef RODS_SERVER
+// =-=-=-=-=-=-=-
+// handle an agent-side auth request call
+irods::error pam_auth_agent_request(
+    irods::plugin_context& _ctx ) {
+    // =-=-=-=-=-=-=-
+    // validate incoming parameters
+    if ( !_ctx.valid< irods::pam_interactive_auth_object >().ok() ) {
+        return ERROR( SYS_INVALID_INPUT_PARAM, "invalid plugin context" );
+    }
+
+    // =-=-=-=-=-=-=-
+    // get the server host handle
+    rodsServerHost_t* server_host = 0;
+    int status = getAndConnRcatHost(
+                     _ctx.comm(),
+                     MASTER_RCAT,
+                     ( const char* )_ctx.comm()->clientUser.rodsZone,
+                     &server_host );
+    if ( status < 0 ) {
+        return ERROR( status, "getAndConnRcatHost failed." );
+    }
+
+    // =-=-=-=-=-=-=-
+    // simply cache the context string for a rainy day...
+    // or to pass to the auth client call later.
+    irods::pam_interactive_auth_object_ptr ptr = boost::dynamic_pointer_cast <
+                                         irods::pam_interactive_auth_object > ( _ctx.fco() );
+    std::string context = ptr->context( );
+
+    // =-=-=-=-=-=-=-
+    // if we are not the catalog server, redirect the call
+    // to there
+    if ( server_host->localFlag != LOCAL_HOST ) {
+        // =-=-=-=-=-=-=-
+        // protect the PAM plain text password by
+        // using an SSL connection to the remote ICAT
+        status = sslStart( server_host->conn );
+        if ( status ) {
+            return ERROR( status, "could not establish SSL connection" );
+        }
+
+        // =-=-=-=-=-=-=-
+        // manufacture structures for the redirected call
+        authPluginReqOut_t* req_out = 0;
+        authPluginReqInp_t  req_inp;
+        strncpy( req_inp.auth_scheme_, irods::AUTH_PAM_INTERACTIVE_SCHEME.c_str(), irods::AUTH_PAM_INTERACTIVE_SCHEME.size() + 1 );
+        strncpy( req_inp.context_, context.c_str(), context.size() + 1 );
+
+        // =-=-=-=-=-=-=-
+        // make the redirected call
+        status = rcAuthPluginRequest( server_host->conn, &req_inp, &req_out );
+
+        // =-=-=-=-=-=-=-
+        // shut down ssl on the connection
+        sslEnd( server_host->conn );
+
+        // =-=-=-=-=-=-=-
+        // disconnect
+        rcDisconnect( server_host->conn );
+        server_host->conn = NULL;
+        if ( !req_out || status < 0 ) {
+            return ERROR( status, "redirected rcAuthPluginRequest failed." );
+        }
+        else {
+            // =-=-=-=-=-=-=-
+            // set the result for communication back to the client
+            ptr->request_result( req_out->result_ );
+            if ( _ctx.comm()->auth_scheme != NULL ) {
+                free( _ctx.comm()->auth_scheme );
+            }
+            _ctx.comm()->auth_scheme = strdup( irods::AUTH_PAM_INTERACTIVE_SCHEME.c_str() );
+            return SUCCESS();
+
+        }
+
+    } // if !localhost
+
+    // =-=-=-=-=-=-=-
+    // parse the kvp out of the _resp->username string
+    irods::kvp_map_t kvp;
+    irods::error ret = irods::parse_escaped_kvp_string(
+                           context,
+                           kvp);
+    if ( !ret.ok() ) {
+        return PASS( ret );
+    }
+
+    if ( kvp.find( irods::AUTH_USER_KEY ) == kvp.end() ||
+            kvp.find( irods::AUTH_TTL_KEY ) == kvp.end() ||
+            kvp.find( irods::AUTH_PASSWORD_KEY ) == kvp.end() ) {
+        return ERROR( SYS_INVALID_INPUT_PARAM, "user or ttl or password key missing" );
+    }
+
+    std::string user_name = kvp[ irods::AUTH_USER_KEY     ];
+    std::string password  = kvp[ irods::AUTH_PASSWORD_KEY ];
+    std::string ttl_str   = kvp[ irods::AUTH_TTL_KEY      ];
+    int ttl = 0;
+    if ( !ttl_str.empty() ) {
+        ttl = boost::lexical_cast<int>( ttl_str );
+    }
+
+    // =-=-=-=-=-=-=-
+    // Normal mode, fork/exec setuid program to do the Pam check
+    status = run_pam_auth_check( user_name, password );
+    if ( status == 256 ) {
+        return ERROR( PAM_AUTH_PASSWORD_FAILED, "pam auth check failed" );
+    }
+    else if ( status ) {
+        return ERROR( status, "pam auth check failed" );
+    }
+
+    // =-=-=-=-=-=-=-
+    // request the resulting irods password after the handshake
+    char password_out[ MAX_NAME_LEN ];
+    char* pw_ptr = &password_out[0];
+    status = chlUpdateIrodsPamPassword( _ctx.comm(), const_cast< char* >( user_name.c_str() ), ttl, NULL, &pw_ptr );
+
+    // =-=-=-=-=-=-=-
+    // set the result for communication back to the client
+    ptr->request_result( password_out );
+
+    // =-=-=-=-=-=-=-
+    // win!
+    if ( _ctx.comm()->auth_scheme != NULL ) {
+        free( _ctx.comm()->auth_scheme );
+    }
+    _ctx.comm()->auth_scheme = strdup( "pam" );
+    return SUCCESS();
+
+} // pam_auth_agent_request
+#endif
+
+// =-=-=-=-=-=-=-
+// establish context - take the auth request results and massage them
+// for the auth response call
+irods::error pam_auth_establish_context(
+    irods::plugin_context& _ctx ) {
+    // =-=-=-=-=-=-=-
+    // validate incoming parameters
+    if ( !_ctx.valid< irods::pam_interactive_auth_object >().ok() ) {
+        return ERROR(
+                   SYS_INVALID_INPUT_PARAM,
+                   "invalid plugin context" );
+
+    }
+
+    return SUCCESS();
+
+} // pam_auth_establish_context
+
+#ifdef RODS_SERVER
+// =-=-=-=-=-=-=-
+// stub for ops that the native plug does
+// not need to support
+irods::error pam_auth_agent_start(
+    irods::plugin_context&,
+    const char*) {
+    return SUCCESS();
+
+} // native_auth_success_stub
+
+irods::error pam_auth_agent_response(
+    irods::plugin_context& _ctx,
+    authResponseInp_t*           _resp ) {
+    return SUCCESS();
+}
+
+irods::error pam_auth_agent_verify(
+    irods::plugin_context& ,
+    const char* ,
+    const char* ,
+    const char* ) {
+    return SUCCESS();
+
+}
+#endif
+
+irods::error pam_auth_client_response(
+    irods::plugin_context& _ctx,
+    rcComm_t*                    _comm ) {
+    return SUCCESS();
+}
+
+// =-=-=-=-=-=-=-
+// derive a new pam_auth auth plugin from
+// the auth plugin base class for handling
+// native authentication
+class pam_interactive_auth_plugin : public irods::auth {
+    public:
+        pam_interactive_auth_plugin(
+            const std::string& _nm,
+            const std::string& _ctx ) :
+            irods::auth(
+                _nm,
+                _ctx ) {
+        } // ctor
+
+        ~pam_interactive_auth_plugin() {
+        }
+
+}; // class pam_auth_plugin
+
+// =-=-=-=-=-=-=-
+// factory function to provide instance of the plugin
+extern "C"
+irods::auth* plugin_factory(
+    const std::string& _inst_name,
+    const std::string& _context ) {
+    // =-=-=-=-=-=-=-
+    // create an auth object
+    pam_interactive_auth_plugin* pam = new pam_interactive_auth_plugin(
+        _inst_name,
+        _context );
+
+    // =-=-=-=-=-=-=-
+    // fill in the operation table mapping call
+    // names to function names
+    using namespace irods;
+    using namespace std;
+    pam->add_operation(
+        AUTH_ESTABLISH_CONTEXT,
+        function<error(plugin_context&)>(
+            pam_auth_establish_context ) );
+    pam->add_operation<rcComm_t*,const char*>(
+        AUTH_CLIENT_START,
+        function<error(plugin_context&,rcComm_t*,const char*)>(
+            pam_auth_client_start ) );
+    pam->add_operation<rcComm_t*>(
+        AUTH_CLIENT_AUTH_REQUEST,
+        function<error(plugin_context&,rcComm_t*)>(
+            pam_auth_client_request ) );
+    pam->add_operation<rcComm_t*>(
+        AUTH_CLIENT_AUTH_RESPONSE,
+        function<error(plugin_context&,rcComm_t*)>(
+            pam_auth_client_response ) );
+#ifdef RODS_SERVER
+    pam->add_operation<const char*>(
+        AUTH_AGENT_START,
+        function<error(plugin_context&,const char*)>(
+            pam_auth_agent_start ) );
+    pam->add_operation(
+        AUTH_AGENT_AUTH_REQUEST,
+        function<error(plugin_context&)>(
+            pam_auth_agent_request )  );
+    pam->add_operation<authResponseInp_t*>(
+        AUTH_AGENT_AUTH_RESPONSE,
+        function<error(plugin_context&,authResponseInp_t*)>(
+            pam_auth_agent_response ) );
+    pam->add_operation<const char*,const char*,const char*>(
+        AUTH_AGENT_AUTH_VERIFY,
+        function<error(plugin_context&,const char*,const char*,const char*)>(
+            pam_auth_agent_verify ) );
+#endif
+    irods::auth* auth = dynamic_cast< irods::auth* >( pam );
+
+    return auth;
+
+} // plugin_factory