summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--my-service2
-rw-r--r--transaction.c9
-rw-r--r--transaction.go47
-rw-r--r--transaction_test.go44
4 files changed, 95 insertions, 7 deletions
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.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
+}
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