Marco Trevisan (Treviño) 883dc86533 module-transaction: Add support for initiating PAM Conversations
Modules have the ability to start PAM conversations, so while the
transaction code can handle them we did not have a way to init them.
Yet.

So add some APIs allowing this, making it easier from the go side to
handle the conversations.

In this commit we only support text-based conversations, but code is
designed with the idea of supporting binary cases too.

Added the integration tests using the module that is now able to both
start conversation and handle them using Go only.
2023-12-14 22:07:50 +01:00
2023-11-30 01:16:38 +01:00
2023-11-30 12:41:22 -06:00
2023-11-30 12:41:22 -06:00
2023-04-04 15:44:38 -05:00
2015-04-01 21:53:50 -05:00

GoDoc codecov Go Report Card

Go PAM

This is a Go wrapper for the PAM application API.

Module support

Go PAM can also used to create PAM modules in a simple way, using the go.

The code can be generated using pam-moduler and an example how to use it using go generate create them is available as an example module.

Modules and PAM applications

The modules generated with go can be used by any PAM application, however there are some caveats, in fact a Go shared library could misbehave when loaded improperly. In particular if a Go shared library is loaded and then the program forks, the library will have an undefined behavior.

This is the case of SSHd that loads a pam library before forking, making any go PAM library to make it hang.

To solve this case, we can use a little workaround: to ensure that the go library is loaded only after the program has forked, we can just dload it once a PAM library is called, in this way go code will be loaded only after that the PAM application has fork'ed.

To do this, we can use a very simple wrapper written in C:

#include <dlfcn.h>
#include <limits.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>

typedef int (*PamHandler)(pam_handle_t *,
                          int          flags,
                          int          argc,
                          const char **argv);

static void
on_go_module_removed (pam_handle_t *pamh,
                      void         *go_module,
                      int           error_status)
{
  dlclose (go_module);
}

static void *
load_module (pam_handle_t *pamh,
             const char   *module_path)
{
  void *go_module;

  if (pam_get_data (pamh, "go-module", (const void **) &go_module) == PAM_SUCCESS)
    return go_module;

  go_module = dlopen (module_path, RTLD_LAZY);
  if (!go_module)
    return NULL;

  pam_set_data (pamh, "go-module", go_module, on_go_module_removed);

  return go_module;
}

static inline int
call_pam_function (pam_handle_t *pamh,
                   const char   *function,
                   int           flags,
                   int           argc,
                   const char  **argv)
{
  char module_path[PATH_MAX] = {0};
  const char *sub_module;
  PamHandler func;
  void *go_module;

  if (argc < 1)
    {
      pam_error (pamh, "%s: no module provided", function);
      return PAM_MODULE_UNKNOWN;
    }

  sub_module = argv[0];
  argc -= 1;
  argv = (argc == 0) ? NULL : &argv[1];

  strncpy (module_path, sub_module, PATH_MAX - 1);

  go_module = load_module (pamh, module_path);
  if (!go_module)
    {
      pam_error (pamh, "Impossible to load module %s", module_path);
      return PAM_OPEN_ERR;
    }

  *(void **) (&func) = dlsym (go_module, function);
  if (!func)
    {
      pam_error (pamh, "Symbol %s not found in %s", function, module_path);
      return PAM_OPEN_ERR;
    }

  return func (pamh, flags, argc, argv);
}

#define DEFINE_PAM_WRAPPER(name) \
  PAM_EXTERN int \
    (pam_sm_ ## name) (pam_handle_t * pamh, int flags, int argc, const char **argv) \
  { \
    return call_pam_function (pamh, "pam_sm_" #name, flags, argc, argv); \
  }

DEFINE_PAM_WRAPPER (authenticate)
DEFINE_PAM_WRAPPER (chauthtok)
DEFINE_PAM_WRAPPER (close_session)
DEFINE_PAM_WRAPPER (open_session)
DEFINE_PAM_WRAPPER (setcred)

Testing

To run the full suite, the tests must be run as the root user. To setup your system for testing, create a user named "test" with the password "secret". For example:

$ sudo useradd test \
    -d /tmp/test \
    -p '$1$Qd8H95T5$RYSZQeoFbEB.gS19zS99A0' \
    -s /bin/false

Then execute the tests:

$ sudo GOPATH=$GOPATH $(which go) test -v
Description
No description provided
Readme 208 KiB
Languages
Go 98.6%
C 1.4%