From bc958bdbd745ac622cf332152816669cc4fb108a Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 16 Sep 2022 08:09:26 +0200 Subject: Allow to define confdir PAM has a pam_start_confdir() which allows to define the configuration directory where all services are located. This is useful to define your own service on tests in particular, so that you can control your stack and be independant of the host when running them. Allow defining this configuration directory, with a new StartConfDir function. Also, allow pre-checking for the API availability with CheckPamHasStartConfdir(). --- transaction.c | 9 +++++++++ transaction.go | 47 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/transaction.c b/transaction.c index c8ca4d2..07155e1 100644 --- a/transaction.c +++ b/transaction.c @@ -50,3 +50,12 @@ void init_pam_conv(struct pam_conv *conv, long c) conv->conv = cb_pam_conv; conv->appdata_ptr = (void *)c; } + +// pam_start_confdir is a recent PAM api to declare a confdir (mostly for tests) +// weaken the linking dependency to detect if it’s present. +int pam_start_confdir(const char *service_name, const char *user, const struct pam_conv *pam_conversation, const char *confdir, pam_handle_t **pamh) __attribute__ ((weak)); +int check_pam_start_confdir(void) { + if (pam_start_confdir == NULL) + return 1; + return 0; +} diff --git a/transaction.go b/transaction.go index cda848e..c8b0960 100644 --- a/transaction.go +++ b/transaction.go @@ -6,9 +6,12 @@ package pam //#cgo CFLAGS: -Wall -std=c99 //#cgo LDFLAGS: -lpam //void init_pam_conv(struct pam_conv *conv, long c); +//int pam_start_confdir(const char *service_name, const char *user, const struct pam_conv *pam_conversation, const char *confdir, pam_handle_t **pamh) __attribute__ ((weak)); +//int check_pam_start_confdir(void); import "C" import ( + "errors" "runtime" "strings" "unsafe" @@ -85,9 +88,33 @@ func transactionFinalizer(t *Transaction) { // Start initiates a new PAM transaction. Service is treated identically to // how pam_start treats it internally. // -// All application calls to PAM begin with Start (or StartFunc). The returned +// All application calls to PAM begin with Start*. The returned // transaction provides an interface to the remainder of the API. func Start(service, user string, handler ConversationHandler) (*Transaction, error) { + return start(service, user, handler, "") +} + +// StartFunc registers the handler func as a conversation handler. +func StartFunc(service, user string, handler func(Style, string) (string, error)) (*Transaction, error) { + return Start(service, user, ConversationFunc(handler)) +} + +// StartConfDir initiates a new PAM transaction. Service is treated identically to +// how pam_start treats it internally. +// confdir allows to define where all pam services are defined. This is used to provide +// custom paths for tests. +// +// All application calls to PAM begin with Start*. The returned +// transaction provides an interface to the remainder of the API. +func StartConfDir(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) { + if !CheckPamHasStartConfdir() { + return nil, errors.New("StartConfDir() was used, but the pam version on the system is not recent enough") + } + + return start(service, user, handler, confDir) +} + +func start(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) { t := &Transaction{ conv: &C.struct_pam_conv{}, c: cbAdd(handler), @@ -101,18 +128,19 @@ func Start(service, user string, handler ConversationHandler) (*Transaction, err u = C.CString(user) defer C.free(unsafe.Pointer(u)) } - t.status = C.pam_start(s, u, t.conv, &t.handle) + if confDir == "" { + t.status = C.pam_start(s, u, t.conv, &t.handle) + } else { + c := C.CString(confDir) + defer C.free(unsafe.Pointer(c)) + t.status = C.pam_start_confdir(s, u, t.conv, c, &t.handle) + } if t.status != C.PAM_SUCCESS { return nil, t } return t, nil } -// StartFunc registers the handler func as a conversation handler. -func StartFunc(service, user string, handler func(Style, string) (string, error)) (*Transaction, error) { - return Start(service, user, ConversationFunc(handler)) -} - func (t *Transaction) Error() string { return C.GoString(C.pam_strerror(t.handle, C.int(t.status))) } @@ -304,3 +332,8 @@ func (t *Transaction) GetEnvList() (map[string]string, error) { C.free(unsafe.Pointer(p)) return env, nil } + +// CheckPamHasStartConfdir return if pam on system supports pam_system_confdir +func CheckPamHasStartConfdir() bool { + return C.check_pam_start_confdir() == 0 +} -- cgit v1.2.3 From 376af17c468ef14f63444c5d1e5c157c7bb7ce1c Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 16 Sep 2022 08:49:02 +0200 Subject: Integration test for confdir handling. Add tests to cover StartConfDir with custom services path. --- my-service | 2 ++ transaction_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 my-service diff --git a/my-service b/my-service new file mode 100644 index 0000000..2dfbc5a --- /dev/null +++ b/my-service @@ -0,0 +1,2 @@ +# Custom stack to always permit, independent of the user name/pass +auth required pam_permit.so diff --git a/transaction_test.go b/transaction_test.go index 2da45ab..c56edf2 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -166,6 +166,50 @@ func TestPAM_007(t *testing.T) { } } +func TestPAM_ConfDir(t *testing.T) { + u, _ := user.Current() + if u.Uid != "0" { + t.Skip("run this test as root") + } + c := Credentials{ + // the custom service always permits even with wrong password. + Password: "wrongsecret", + } + tx, err := StartConfDir("my-service", "test", c, ".") + if !CheckPamHasStartConfdir() { + if err == nil { + t.Fatalf("start should have errored out as pam_start_confdir is not available: %v", err) + } + // nothing else we do, we don't support it. + return + } + if err != nil { + t.Fatalf("start #error: %v", err) + } + err = tx.Authenticate(0) + if err != nil { + t.Fatalf("authenticate #error: %v", err) + } +} + +func TestPAM_ConfDir_FailNoServiceOrUnsupported(t *testing.T) { + u, _ := user.Current() + if u.Uid != "0" { + t.Skip("run this test as root") + } + c := Credentials{ + Password: "secret", + } + _, err := StartConfDir("does-not-exists", "test", c, ".") + if err == nil { + t.Fatalf("authenticate #expected an error") + } + s := err.Error() + if len(s) == 0 { + t.Fatalf("error #expected an error message") + } +} + func TestItem(t *testing.T) { tx, _ := StartFunc("passwd", "test", func(s Style, msg string) (string, error) { return "", nil -- cgit v1.2.3