msteinert-go-pam/README.md

4.4 KiB

GoDoc codecov Go Report Card

Go PAM

This is a Go wrapper for the PAM application API.

Package path changed for ease of use, and to avoid conflicts with the original

Originally created by Mike Steinert Updated and modified by Marco Trevisan

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

Other tests can instead run as user without any setup with normal go test ./...