1//go:build linux && (amd64 || arm64)
2
3package pam
4
5import (
6 "fmt"
7 "unsafe"
8
9 "github.com/bnema/purego"
10)
11
12// libraryFunctions is the test seam; tests use Mockery, never replace functions.
13type libraryFunctions interface {
14 Start(service, user, conversation, output unsafe.Pointer) Status
15 Authenticate(handle unsafe.Pointer, flags Flags) Status
16 AcctMgmt(handle unsafe.Pointer, flags Flags) Status
17 End(handle unsafe.Pointer, status Status) Status
18}
19
20type boundFunctions struct {
21 start func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) Status
22 authenticate func(unsafe.Pointer, Flags) Status
23 acctMgmt func(unsafe.Pointer, Flags) Status
24 end func(unsafe.Pointer, Status) Status
25}
26
27func (b *boundFunctions) Start(s, u, c, o unsafe.Pointer) Status { return b.start(s, u, c, o) }
28func (b *boundFunctions) Authenticate(h unsafe.Pointer, f Flags) Status { return b.authenticate(h, f) }
29func (b *boundFunctions) AcctMgmt(h unsafe.Pointer, f Flags) Status { return b.acctMgmt(h, f) }
30func (b *boundFunctions) End(h unsafe.Pointer, s Status) Status { return b.end(h, s) }
31
32// Open loads the system libpam and libc with eager, local symbol resolution.
33// It performs no PAM transaction or authentication. Library operations must be
34// serialized by the caller; Close is rejected while transactions are active.
35func Open() (*Library, error) {
36 pamHandle, err := purego.Dlopen("libpam.so.0", purego.RTLD_NOW|purego.RTLD_LOCAL)
37 if err != nil {
38 return nil, fmt.Errorf("pam: load libpam: %w", err)
39 }
40 libcHandle, err := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_LOCAL)
41 if err != nil {
42 return nil, fmt.Errorf("pam: load libc: %w", errorsWithClose(err, pamHandle))
43 }
44 functions := &boundFunctions{}
45 memory := &nativeAllocator{}
46 bindings := []struct {
47 handle uintptr
48 name string
49 target any
50 }{
51 {pamHandle, "pam_start", &functions.start},
52 {pamHandle, "pam_authenticate", &functions.authenticate},
53 {pamHandle, "pam_acct_mgmt", &functions.acctMgmt},
54 {pamHandle, "pam_end", &functions.end},
55 {libcHandle, "malloc", &memory.malloc},
56 {libcHandle, "free", &memory.freeFn},
57 }
58 for _, binding := range bindings {
59 if err := register(binding.target, binding.handle, binding.name); err != nil {
60 return nil, errorsWithClose(errorsWithClose(err, libcHandle), pamHandle)
61 }
62 }
63 return &Library{calls: functions, memory: memory, pamHandle: pamHandle, libcHandle: libcHandle}, nil
64}
65
66// RegisterLibFunc panics on lookup/registration failure; turn that boundary into
67// an Open error so a partially bound library is never exposed.
68func register(target any, handle uintptr, name string) (err error) {
69 defer func() {
70 if recovered := recover(); recovered != nil {
71 err = fmt.Errorf("pam: bind %s: %v", name, recovered)
72 }
73 }()
74 purego.RegisterLibFunc(target, handle, name)
75 return nil
76}