pam-moduler: Add first implementation of a Go PAM Module generator
A PAM module can be generated using pam-moduler and implemented fully in go without having to manually deal with the C setup. Module can be compiled using go generate, so go:generate directives can be used to make this process automatic, with a single go generate call as shown in the example.
This commit is contained in:
3
.codecov.yml
Normal file
3
.codecov.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ignore:
|
||||||
|
# Ignore pam-moduler generated files
|
||||||
|
- "**/pam_module.go"
|
||||||
6
.github/workflows/test.yaml
vendored
6
.github/workflows/test.yaml
vendored
@@ -18,6 +18,12 @@ jobs:
|
|||||||
run: sudo useradd -d /tmp/test -p '$1$Qd8H95T5$RYSZQeoFbEB.gS19zS99A0' -s /bin/false test
|
run: sudo useradd -d /tmp/test -p '$1$Qd8H95T5$RYSZQeoFbEB.gS19zS99A0' -s /bin/false test
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Generate example module
|
||||||
|
run: |
|
||||||
|
rm -f example-module/pam_go.so
|
||||||
|
go generate -C example-module -v
|
||||||
|
test -e example-module/pam_go.so
|
||||||
|
git diff --exit-code example-module
|
||||||
- name: Test
|
- name: Test
|
||||||
run: sudo go test -v -cover -coverprofile=coverage.out ./...
|
run: sudo go test -v -cover -coverprofile=coverage.out ./...
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
coverage.out
|
coverage.out
|
||||||
|
example-module/*.so
|
||||||
|
example-module/*.h
|
||||||
|
|||||||
117
README.md
117
README.md
@@ -6,6 +6,123 @@
|
|||||||
|
|
||||||
This is a Go wrapper for the PAM application API.
|
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](cmd/pam-moduler/moduler.go) and
|
||||||
|
an example how to use it using `go generate` create them is available as an
|
||||||
|
[example module](example-module/module.go).
|
||||||
|
|
||||||
|
### 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
|
||||||
|
`fork`s, 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:
|
||||||
|
|
||||||
|
```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
|
## Testing
|
||||||
|
|
||||||
To run the full suite, the tests must be run as the root user. To setup your
|
To run the full suite, the tests must be run as the root user. To setup your
|
||||||
|
|||||||
305
cmd/pam-moduler/moduler.go
Normal file
305
cmd/pam-moduler/moduler.go
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
// pam-moduler is a tool to automate the creation of PAM Modules in go
|
||||||
|
//
|
||||||
|
// The file is created in the same package and directory as the package that
|
||||||
|
// creates the module
|
||||||
|
//
|
||||||
|
// The module implementation should define a pamModuleHandler object that
|
||||||
|
// implements the pam.ModuleHandler type and that will be used for each callback
|
||||||
|
//
|
||||||
|
// Otherwise it's possible to provide a typename from command line that will
|
||||||
|
// be used for this purpose
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// //go:generate go run github.com/msteinert/pam/pam-moduler
|
||||||
|
// //go:generate go generate --skip="pam_module"
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import "github.com/msteinert/pam/v2"
|
||||||
|
//
|
||||||
|
// type ExampleHandler struct{}
|
||||||
|
// var pamModuleHandler pam.ModuleHandler = &ExampleHandler{}
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) AcctMgmt(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) Authenticate(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) ChangeAuthTok(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) OpenSession(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) CloseSession(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (h *ExampleHandler) SetCred(pam.ModuleTransaction, pam.Flags, []string) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Package main provides the module shared library.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const toolName = "pam-moduler"
|
||||||
|
|
||||||
|
var (
|
||||||
|
output = flag.String("output", "", "output file name; default srcdir/pam_module.go")
|
||||||
|
libName = flag.String("libname", "", "output library name; default pam_go.so")
|
||||||
|
typeName = flag.String("type", "", "type name to be used as pam.ModuleHandler")
|
||||||
|
buildTags = flag.String("tags", "", "build tags expression to append to use in the go:build directive")
|
||||||
|
skipGenerator = flag.Bool("no-generator", false, "whether to add go:generator directives to the generated source")
|
||||||
|
moduleBuildFlags = flag.String("build-flags", "", "comma-separated list of go build flags to use when generating the module")
|
||||||
|
moduleBuildTags = flag.String("build-tags", "", "comma-separated list of build tags to use when generating the module")
|
||||||
|
noMain = flag.Bool("no-main", false, "whether to add an empty main to generated file")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Usage is a replacement usage function for the flags package.
|
||||||
|
func Usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", toolName)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s [flags] [-output O] [-libname pam_go] [-type N]\n", toolName)
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix(toolName + ": ")
|
||||||
|
flag.Usage = Usage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *skipGenerator {
|
||||||
|
if *libName != "" {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"Generator directives disabled, libname will have no effect\n")
|
||||||
|
}
|
||||||
|
if *moduleBuildTags != "" {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"Generator directives disabled, build-tags will have no effect\n")
|
||||||
|
}
|
||||||
|
if *moduleBuildFlags != "" {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"Generator directives disabled, build-flags will have no effect\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lib := *libName
|
||||||
|
if lib == "" {
|
||||||
|
lib = "pam_go"
|
||||||
|
} else {
|
||||||
|
lib, _ = strings.CutSuffix(lib, ".so")
|
||||||
|
lib, _ = strings.CutPrefix(lib, "lib")
|
||||||
|
}
|
||||||
|
|
||||||
|
outputName, _ := strings.CutSuffix(*output, ".go")
|
||||||
|
if outputName == "" {
|
||||||
|
baseName := "pam_module"
|
||||||
|
outputName = filepath.Join(".", strings.ToLower(baseName))
|
||||||
|
}
|
||||||
|
outputName = outputName + ".go"
|
||||||
|
|
||||||
|
var tags string
|
||||||
|
if *buildTags != "" {
|
||||||
|
tags = *buildTags
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateTags []string
|
||||||
|
if len(*moduleBuildTags) > 0 {
|
||||||
|
generateTags = strings.Split(*moduleBuildTags, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildFlags []string
|
||||||
|
if *moduleBuildFlags != "" {
|
||||||
|
buildFlags = strings.Split(*moduleBuildFlags, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
g := Generator{
|
||||||
|
outputName: outputName,
|
||||||
|
libName: lib,
|
||||||
|
tags: tags,
|
||||||
|
buildFlags: buildFlags,
|
||||||
|
generateTags: generateTags,
|
||||||
|
noMain: *noMain,
|
||||||
|
typeName: *typeName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the header and package clause.
|
||||||
|
g.printf("// Code generated by \"%s %s\"; DO NOT EDIT.\n",
|
||||||
|
toolName, strings.Join(os.Args[1:], " "))
|
||||||
|
g.printf("\n")
|
||||||
|
|
||||||
|
// Generate the code
|
||||||
|
g.generate()
|
||||||
|
|
||||||
|
// Format the output.
|
||||||
|
src := g.format()
|
||||||
|
|
||||||
|
// Write to file.
|
||||||
|
err := os.WriteFile(outputName, src, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("writing output: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generator holds the state of the analysis. Primarily used to buffer
|
||||||
|
// the output for format.Source.
|
||||||
|
type Generator struct {
|
||||||
|
buf bytes.Buffer // Accumulated output.
|
||||||
|
|
||||||
|
libName string
|
||||||
|
outputName string
|
||||||
|
typeName string
|
||||||
|
tags string
|
||||||
|
generateTags []string
|
||||||
|
buildFlags []string
|
||||||
|
noMain bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) printf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(&g.buf, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate produces the String method for the named type.
|
||||||
|
func (g *Generator) generate() {
|
||||||
|
if g.tags != "" {
|
||||||
|
g.printf("//go:build %s\n", g.tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildTagsArg string
|
||||||
|
if len(g.generateTags) > 0 {
|
||||||
|
buildTagsArg = fmt.Sprintf("-tags %s", strings.Join(g.generateTags, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a slice since we want to keep order, for reproducible builds.
|
||||||
|
vFuncs := []struct {
|
||||||
|
cName string
|
||||||
|
goName string
|
||||||
|
}{
|
||||||
|
{"authenticate", "Authenticate"},
|
||||||
|
{"setcred", "SetCred"},
|
||||||
|
{"acct_mgmt", "AcctMgmt"},
|
||||||
|
{"open_session", "OpenSession"},
|
||||||
|
{"close_session", "CloseSession"},
|
||||||
|
{"chauthtok", "ChangeAuthTok"},
|
||||||
|
}
|
||||||
|
|
||||||
|
g.printf(`//go:generate go build "-ldflags=-extldflags -Wl,-soname,%[2]s.so" `+
|
||||||
|
`-buildmode=c-shared -o %[2]s.so %[3]s %[4]s
|
||||||
|
`,
|
||||||
|
g.outputName, g.libName, buildTagsArg, strings.Join(g.buildFlags, " "))
|
||||||
|
|
||||||
|
g.printf(`
|
||||||
|
// Package main is the package for the PAM module library.
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lpam -fPIC
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
|
||||||
|
typedef const char _const_char_t;
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
"github.com/msteinert/pam/v2"
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
if g.typeName != "" {
|
||||||
|
g.printf(`
|
||||||
|
var pamModuleHandler pam.ModuleHandler = &%[1]s{}
|
||||||
|
`, g.typeName)
|
||||||
|
} else {
|
||||||
|
g.printf(`
|
||||||
|
// Do a typecheck at compile time
|
||||||
|
var _ pam.ModuleHandler = pamModuleHandler;
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.printf(`
|
||||||
|
// sliceFromArgv returns a slice of strings given to the PAM module.
|
||||||
|
func sliceFromArgv(argc C.int, argv **C._const_char_t) []string {
|
||||||
|
r := make([]string, 0, argc)
|
||||||
|
for _, s := range unsafe.Slice(argv, argc) {
|
||||||
|
r = append(r, C.GoString(s))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePamCall is the function that translates C pam requests to Go.
|
||||||
|
func handlePamCall(pamh *C.pam_handle_t, flags C.int, argc C.int,
|
||||||
|
argv **C._const_char_t, moduleFunc pam.ModuleHandlerFunc) C.int {
|
||||||
|
if pamModuleHandler == nil {
|
||||||
|
return C.int(pam.ErrNoModuleData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleFunc == nil {
|
||||||
|
return C.int(pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := moduleFunc(pam.NewModuleTransaction(pam.NativeHandle(pamh)),
|
||||||
|
pam.Flags(flags), sliceFromArgv(argc, argv))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pam.Flags(flags) & pam.Silent) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "module returned error: %%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pamErr pam.Error
|
||||||
|
if errors.As(err, &pamErr) {
|
||||||
|
return C.int(pamErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return C.int(pam.ErrSystem)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for _, f := range vFuncs {
|
||||||
|
g.printf(`
|
||||||
|
//export pam_sm_%[1]s
|
||||||
|
func pam_sm_%[1]s(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.%[2]s)
|
||||||
|
}
|
||||||
|
`, f.cName, f.goName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.noMain {
|
||||||
|
g.printf("\nfunc main() {}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format returns the gofmt-ed contents of the Generator's buffer.
|
||||||
|
func (g *Generator) format() []byte {
|
||||||
|
src, err := format.Source(g.buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, but can arise when developing this code.
|
||||||
|
// The user can compile the output to see the error.
|
||||||
|
log.Printf("warning: internal error: invalid Go generated: %s", err)
|
||||||
|
log.Printf("warning: compile the package to analyze the error")
|
||||||
|
return g.buf.Bytes()
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
}
|
||||||
50
example-module/module.go
Normal file
50
example-module/module.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// These go:generate directive allow to generate the module by just using
|
||||||
|
// `go generate` once in the module directory.
|
||||||
|
// This is not strictly needed
|
||||||
|
|
||||||
|
//go:generate go run github.com/msteinert/pam/v2/cmd/pam-moduler
|
||||||
|
//go:generate go generate --skip="pam_module.go"
|
||||||
|
|
||||||
|
// Package main provides the module shared library.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/msteinert/pam/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type exampleHandler struct{}
|
||||||
|
|
||||||
|
var pamModuleHandler pam.ModuleHandler = &exampleHandler{}
|
||||||
|
var _ = pamModuleHandler
|
||||||
|
|
||||||
|
// AcctMgmt is the module handle function for account management.
|
||||||
|
func (h *exampleHandler) AcctMgmt(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return fmt.Errorf("AcctMgmt not implemented: %w", pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate is the module handle function for authentication.
|
||||||
|
func (h *exampleHandler) Authenticate(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return pam.ErrAuthinfoUnavail
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeAuthTok is the module handle function for changing authentication token.
|
||||||
|
func (h *exampleHandler) ChangeAuthTok(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return fmt.Errorf("ChangeAuthTok not implemented: %w", pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenSession is the module handle function for open session.
|
||||||
|
func (h *exampleHandler) OpenSession(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return fmt.Errorf("OpenSession not implemented: %w", pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseSession is the module handle function for close session.
|
||||||
|
func (h *exampleHandler) CloseSession(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return fmt.Errorf("CloseSession not implemented: %w", pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCred is the module handle function for set credentials.
|
||||||
|
func (h *exampleHandler) SetCred(mt pam.ModuleTransaction, flags pam.Flags, args []string) error {
|
||||||
|
return fmt.Errorf("SetCred not implemented: %w", pam.ErrIgnore)
|
||||||
|
}
|
||||||
96
example-module/pam_module.go
Normal file
96
example-module/pam_module.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Code generated by "pam-moduler "; DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:generate go build "-ldflags=-extldflags -Wl,-soname,pam_go.so" -buildmode=c-shared -o pam_go.so
|
||||||
|
|
||||||
|
// Package main is the package for the PAM module library.
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lpam -fPIC
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
|
||||||
|
typedef const char _const_char_t;
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/msteinert/pam/v2"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Do a typecheck at compile time
|
||||||
|
var _ pam.ModuleHandler = pamModuleHandler
|
||||||
|
|
||||||
|
// sliceFromArgv returns a slice of strings given to the PAM module.
|
||||||
|
func sliceFromArgv(argc C.int, argv **C._const_char_t) []string {
|
||||||
|
r := make([]string, 0, argc)
|
||||||
|
for _, s := range unsafe.Slice(argv, argc) {
|
||||||
|
r = append(r, C.GoString(s))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePamCall is the function that translates C pam requests to Go.
|
||||||
|
func handlePamCall(pamh *C.pam_handle_t, flags C.int, argc C.int,
|
||||||
|
argv **C._const_char_t, moduleFunc pam.ModuleHandlerFunc) C.int {
|
||||||
|
if pamModuleHandler == nil {
|
||||||
|
return C.int(pam.ErrNoModuleData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleFunc == nil {
|
||||||
|
return C.int(pam.ErrIgnore)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := moduleFunc(pam.NewModuleTransaction(pam.NativeHandle(pamh)),
|
||||||
|
pam.Flags(flags), sliceFromArgv(argc, argv))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pam.Flags(flags) & pam.Silent) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "module returned error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pamErr pam.Error
|
||||||
|
if errors.As(err, &pamErr) {
|
||||||
|
return C.int(pamErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return C.int(pam.ErrSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_authenticate
|
||||||
|
func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.Authenticate)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_setcred
|
||||||
|
func pam_sm_setcred(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.SetCred)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_acct_mgmt
|
||||||
|
func pam_sm_acct_mgmt(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.AcctMgmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_open_session
|
||||||
|
func pam_sm_open_session(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.OpenSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_close_session
|
||||||
|
func pam_sm_close_session(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.CloseSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export pam_sm_chauthtok
|
||||||
|
func pam_sm_chauthtok(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int {
|
||||||
|
return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.ChangeAuthTok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {}
|
||||||
@@ -15,7 +15,9 @@ type ModuleTransaction interface {
|
|||||||
type ModuleHandlerFunc func(ModuleTransaction, Flags, []string) error
|
type ModuleHandlerFunc func(ModuleTransaction, Flags, []string) error
|
||||||
|
|
||||||
// ModuleTransaction is the module-side handle for a PAM transaction.
|
// ModuleTransaction is the module-side handle for a PAM transaction.
|
||||||
type moduleTransaction = transactionBase
|
type moduleTransaction struct {
|
||||||
|
transactionBase
|
||||||
|
}
|
||||||
|
|
||||||
// ModuleHandler is an interface for objects that can be used to create
|
// ModuleHandler is an interface for objects that can be used to create
|
||||||
// PAM modules from go.
|
// PAM modules from go.
|
||||||
@@ -27,3 +29,9 @@ type ModuleHandler interface {
|
|||||||
OpenSession(ModuleTransaction, Flags, []string) error
|
OpenSession(ModuleTransaction, Flags, []string) error
|
||||||
SetCred(ModuleTransaction, Flags, []string) error
|
SetCred(ModuleTransaction, Flags, []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewModuleTransaction allows initializing a transaction invoker from
|
||||||
|
// the module side.
|
||||||
|
func NewModuleTransaction(handle NativeHandle) ModuleTransaction {
|
||||||
|
return &moduleTransaction{transactionBase{handle: handle}}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user