summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Steinert <[email protected]>2015-03-27 18:59:29 -0500
committerMichael Steinert <[email protected]>2015-03-28 17:00:58 -0500
commitd67bb86e54c08eb5ae75148195a0c840758ee43c (patch)
tree3f47649d11107635110fa9238536d7a2eb963200
parenta6dbe3c2bf85196d35238b0a6edb696963184315 (diff)
Update interface
-rw-r--r--example/.gitignore1
-rw-r--r--example/example.go62
-rw-r--r--golang-pam.c66
-rw-r--r--golang-pam.h11
-rw-r--r--pam.go197
-rw-r--r--pamdefs.c90
-rw-r--r--pamdefs.go63
-rw-r--r--transaction.c42
-rw-r--r--transaction.go280
9 files changed, 385 insertions, 427 deletions
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000..6f30a3a
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1 @@
+/example
diff --git a/example/example.go b/example/example.go
new file mode 100644
index 0000000..efe0d79
--- /dev/null
+++ b/example/example.go
@@ -0,0 +1,62 @@
+// This is a fake login implementation. It uses whatever default
+// PAM service configuration is available on the system, and tries
+// to authenticate any user. This should cause PAM to ask its
+// conversation handler for a username and password, in sequence.
+//
+// This application will handle those requests by displaying the
+// PAM-provided prompt and sending back the first line of stdin input
+// it can read for each.
+//
+// Keep in mind that unless run as root (or setuid root), the only
+// user's authentication that can succeed is that of the process owner.
+//
+// It's not a real login for several reasons:
+//
+// It doesn't switch users.
+// It's not a real login.
+//
+// It does however demonstrate a simple but powerful use of PAM.
+
+package main
+
+import (
+ "bufio"
+ "code.google.com/p/gopass"
+ "errors"
+ "fmt"
+ "github.com/msteinert/pam"
+ "log"
+ "os"
+)
+
+func main() {
+ t, err := pam.StartFunc("", "", func(s pam.Style, msg string) (string, error) {
+ switch s {
+ case pam.PromptEchoOff:
+ return gopass.GetPass(msg)
+ case pam.PromptEchoOn:
+ fmt.Print(msg)
+ bio := bufio.NewReader(os.Stdin)
+ input, err := bio.ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+ return input[:len(input)-1], nil
+ case pam.ErrorMsg:
+ log.Print(msg)
+ return "", nil
+ case pam.TextInfo:
+ fmt.Println(msg)
+ return "", nil
+ }
+ return "", errors.New("Unrecognized message style")
+ })
+ if err != nil {
+ log.Fatalf("Start: %s", err.Error())
+ }
+ err = t.Authenticate(0)
+ if err != nil {
+ log.Fatalf("Authenticate: %s", err.Error())
+ }
+ log.Print("Authentication succeeded!")
+}
diff --git a/golang-pam.c b/golang-pam.c
deleted file mode 100644
index dc107f9..0000000
--- a/golang-pam.c
+++ /dev/null
@@ -1,66 +0,0 @@
-#include <security/pam_appl.h>
-#include <stdlib.h>
-#include <string.h>
-#include "_cgo_export.h"
-
-/* Simplification of pam_get_item to remove type ambiguity. Will never
- be called (promise) with a type that returns anything other than a string */
-get_item_result pam_get_item_string(pam_handle_t *handle, int type) {
- get_item_result result;
- result.status = pam_get_item(handle, type, (const void **)&result.str);
- return result;
-}
-
-/* The universal conversation callback for gopam transactions. appdata_ptr
- is always taken as a raw pointer to a Go-side pam.conversation object.
- In order to avoid nightmareish unsafe pointer math all over the Go
- implementation, this universal callback deals with memory allocation of
- response buffers, as well as unpacking and repackaging incoming messages
- and responses, calling a Go-side callback that handles each one on an
- individual basis. */
-int gopam_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
-{
- int i, ok = 1;
- struct pam_response *responses = (struct pam_response*)malloc(sizeof(struct pam_response) * num_msg);
- memset(responses, 0, sizeof(struct pam_response) * num_msg);
-
- for(i = 1; i < num_msg; ++i) {
- const struct pam_message *m = msg[i];
- struct goPAMConv_return result = goPAMConv(m->msg_style, (char*)m->msg, appdata_ptr);
- if(result.r1 == PAM_SUCCESS)
- responses[i].resp = result.r0;
- else {
- ok = 0;
- break;
- }
- }
-
- if(ok) {
- *resp = responses;
- return PAM_SUCCESS;
- }
-
- /* In the case of failure PAM will never see these responses. The
- resp strings that have been allocated by Go-side C.CString calls
- must be freed lest we leak them. */
- for(i = 0; i < num_msg; ++i)
- if(responses[i].resp != NULL)
- free(responses[i].resp);
-
- free(responses);
- return PAM_CONV_ERR;
-}
-
-/* This allocates a new pam_conv struct and fills in its fields:
- The conv function pointer always points to the universal gopam_conv.
- The appdata_ptr will be set to the incoming void* argument, which
- is always a Go-side *pam.conversation whose handler was given
- to pam.Start(). */
-struct pam_conv* make_gopam_conv(void *goconv)
-{
- struct pam_conv* conv = (struct pam_conv*)malloc(sizeof(struct pam_conv));
- conv->conv = gopam_conv;
- conv->appdata_ptr = goconv;
- return conv;
-}
-
diff --git a/golang-pam.h b/golang-pam.h
deleted file mode 100644
index a9c7b99..0000000
--- a/golang-pam.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#include <security/pam_appl.h>
-#include <stdlib.h>
-
-typedef struct {
- const char *str;
- int status;
-} get_item_result;
-
-get_item_result pam_get_item_string(pam_handle_t *handle, int type);
-struct pam_conv* make_gopam_conv(void *goconv);
-
diff --git a/pam.go b/pam.go
deleted file mode 100644
index 82de43e..0000000
--- a/pam.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Package pam provides a wrapper for the application layer of the
-// Pluggable Authentication Modules library.
-package pam
-
-import (
- //#include "golang-pam.h"
- //#cgo LDFLAGS: -lpam
- "C"
- "unsafe"
- "strings"
-)
-
-// Objects implementing the ConversationHandler interface can
-// be registered as conversation callbacks to be used during
-// PAM authentication. RespondPAM receives a message style
-// (one of PROMPT_ECHO_OFF, PROMPT_ECHO_ON, ERROR_MSG, or
-// TEXT_INFO) and a message string. It is expected to return
-// a response string and a bool indicating success or failure.
-type ConversationHandler interface {
- RespondPAM(msg_style int, msg string) (string,bool)
-}
-
-// ResponseFunc is an adapter to allow the use of ordinary
-// functions as conversation callbacks. ResponseFunc(f) is
-// a ConversationHandler that calls f, where f must have
-// the signature func(int,string)(string,bool).
-type ResponseFunc func(int,string) (string,bool)
-func (f ResponseFunc) RespondPAM(style int, msg string) (string,bool) {
- return f(style,msg)
-}
-
-// Internal conversation structure
-type conversation struct {
- handler ConversationHandler
- cconv *C.struct_pam_conv
-}
-
-// Cosntructs a new conversation object with a given handler and a newly
-// allocated pam_conv struct that uses this object as its appdata_ptr
-func newConversation(handler ConversationHandler) *conversation {
- conv := &conversation{}
- conv.handler = handler
- conv.cconv = C.make_gopam_conv(unsafe.Pointer(conv))
- return conv
-}
-
-//export goPAMConv
-// Go-side function for processing a single conversational message. Ultimately
-// this calls the associated ConversationHandler's ResponsePAM callback with data
-// coming in from a C-side call.
-func goPAMConv(msg_style C.int, msg *C.char, appdata unsafe.Pointer) (*C.char,int) {
- conv := (*conversation)(appdata)
- resp,ok := conv.handler.RespondPAM(int(msg_style), C.GoString(msg))
- if ok {
- return C.CString(resp),SUCCESS
- }
- return nil,CONV_ERR
-}
-
-// Transaction is the application's handle for a single PAM transaction.
-type Transaction struct {
- handle *C.pam_handle_t
- conv *conversation
-}
-
-// Start initiates a new PAM transaction. serviceName is treated identically
-// to how pam_start internally treats it. The same applies to user, except that
-// the empty string is passed to PAM as nil; therefore the empty string should be
-// used to signal that no username is being provided.
-//
-// All application calls to PAM begin with Start(). The returned *Transaction
-// provides an interface to the remainder of the API.
-//
-// The returned status int may be ABORT, BUF_ERR, SUCCESS, or SYSTEM_ERR, as per
-// the official PAM documentation.
-func Start(serviceName, user string, handler ConversationHandler) (*Transaction,int) {
- t := &Transaction{}
- t.conv = newConversation(handler)
- var status C.int
- if len(user) == 0 {
- status = C.pam_start(C.CString(serviceName), nil, t.conv.cconv, &t.handle)
- } else {
- status = C.pam_start(C.CString(serviceName), C.CString(user), t.conv.cconv, &t.handle)
- }
-
- if status != SUCCESS {
- C.free(unsafe.Pointer(t.conv.cconv))
- return nil,int(status)
- }
-
- return t, int(status)
-}
-
-// Ends a PAM transaction. From Linux-PAM documentation: "The [status] argument
-// should be set to the value returned by the last PAM library call."
-//
-// This may return SUCCESS, or SYSTEM_ERR.
-//
-// This *must* be called on any Transaction successfully returned by Start() or
-// you will leak memory.
-func (t *Transaction) End(status int) {
- C.pam_end(t.handle, C.int(status))
- C.free(unsafe.Pointer(t.conv.cconv))
-}
-
-// Sets a PAM informational item. Legal values of itemType are listed here (excluding Linux extensions):
-//
-// http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_set_item
-//
-// the CONV item type is also not supported in order to simplify the Go API (and due to
-// the fact that it is completely unnecessary).
-func (t *Transaction) SetItem(itemType int, item string) int {
- if itemType == CONV { return BAD_ITEM }
- cs := unsafe.Pointer(C.CString(item))
- defer C.free(cs)
- return int(C.pam_set_item(t.handle, C.int(itemType), cs))
-}
-
-// Gets a PAM item. Legal values of itemType are as specified by the documentation of SetItem.
-func (t *Transaction) GetItem(itemType int) (string,int) {
- if itemType == CONV { return "",BAD_ITEM }
- result := C.pam_get_item_string(t.handle, C.int(itemType))
- return C.GoString(result.str),int(result.status)
-}
-
-// Error returns a PAM error string from a PAM error code
-func (t *Transaction) Error(errnum int) string {
- return C.GoString(C.pam_strerror(t.handle, C.int(errnum)))
-}
-
-// pam_authenticate
-func (t *Transaction) Authenticate(flags int) int {
- return int(C.pam_authenticate(t.handle, C.int(flags)))
-}
-
-// pam_setcred
-func (t* Transaction) SetCred(flags int) int {
- return int(C.pam_setcred(t.handle, C.int(flags)))
-}
-
-// pam_acctmgmt
-func (t* Transaction) AcctMgmt(flags int) int {
- return int(C.pam_acct_mgmt(t.handle, C.int(flags)))
-}
-
-// pam_chauthtok
-func (t* Transaction) ChangeAuthTok(flags int) int {
- return int(C.pam_chauthtok(t.handle, C.int(flags)))
-}
-
-// pam_open_session
-func (t* Transaction) OpenSession(flags int) int {
- return int(C.pam_open_session(t.handle, C.int(flags)))
-}
-
-// pam_close_session
-func (t* Transaction) CloseSession(flags int) int {
- return int(C.pam_close_session(t.handle, C.int(flags)))
-}
-
-// pam_putenv
-func (t* Transaction) PutEnv(nameval string) int {
- cs := C.CString(nameval)
- defer C.free(unsafe.Pointer(cs))
- return int(C.pam_putenv(t.handle, cs))
-}
-
-// pam_getenv. Returns an additional argument indicating
-// the actual existence of the given environment variable.
-func (t* Transaction) GetEnv(name string) (string,bool) {
- cs := C.CString(name)
- defer C.free(unsafe.Pointer(cs))
- value := C.pam_getenv(t.handle, cs)
- if value != nil {
- return C.GoString(value),true
- }
- return "",false
-}
-
-// GetEnvList internally calls pam_getenvlist and then uses some very
-// dangerous code to pull out the returned environment data and mash
-// it into a map[string]string. This call may be safe, but it hasn't
-// been tested on enough platforms/architectures/PAM-implementations to
-// be sure.
-func (t* Transaction) GetEnvList() map[string]string {
- env := make(map[string]string)
- list := (uintptr)(unsafe.Pointer(C.pam_getenvlist(t.handle)))
- for *(*uintptr)(unsafe.Pointer(list)) != 0 {
- entry := *(*uintptr)(unsafe.Pointer(list))
- nameval := C.GoString((*C.char)(unsafe.Pointer(entry)))
- chunks := strings.SplitN(nameval, "=", 2)
- env[chunks[0]] = chunks[1]
- list += (uintptr)(unsafe.Sizeof(list))
- }
- return env
-}
-
diff --git a/pamdefs.c b/pamdefs.c
deleted file mode 100644
index 81a0fbd..0000000
--- a/pamdefs.c
+++ /dev/null
@@ -1,90 +0,0 @@
-#include <security/pam_appl.h>
-
-enum {
- $SUCCESS = PAM_SUCCESS,
- $OPEN_ERR = PAM_OPEN_ERR,
- $SYMBOL_ERR = PAM_SYMBOL_ERR,
- $SERVICE_ERR = PAM_SERVICE_ERR,
- $SYSTEM_ERR = PAM_SYSTEM_ERR,
- $BUF_ERR = PAM_BUF_ERR,
- $PERM_DENIED = PAM_PERM_DENIED,
- $AUTH_ERR = PAM_AUTH_ERR,
- $CRED_INSUFFICIENT = PAM_CRED_INSUFFICIENT,
- $AUTHINFO_UNAVAIL = PAM_AUTHINFO_UNAVAIL,
- $USER_UNKNOWN = PAM_USER_UNKNOWN,
- $MAXTRIES = PAM_MAXTRIES,
- $NEW_AUTHOTK_REQD = PAM_NEW_AUTHTOK_REQD,
- $ACCT_EXPIRED = PAM_ACCT_EXPIRED,
- $SESSION_ERR = PAM_SESSION_ERR,
- $CRED_UNAVAIL = PAM_CRED_UNAVAIL,
- $CRED_EXPIRED = PAM_CRED_EXPIRED,
- $CRED_ERR = PAM_CRED_ERR,
- $NO_MODULE_DATA = PAM_NO_MODULE_DATA,
- $CONV_ERR = PAM_CONV_ERR,
- $AUTHTOK_ERR = PAM_AUTHTOK_ERR,
- $AUTHTOK_RECOVERY_ERR = PAM_AUTHTOK_RECOVERY_ERR,
- $AUTHTOK_LOCK_BUSY = PAM_AUTHTOK_LOCK_BUSY,
- $AUTHTOK_DISABLE_AGING = PAM_AUTHTOK_DISABLE_AGING,
- $TRY_AGAIN = PAM_TRY_AGAIN,
- $IGNORE = PAM_IGNORE,
- $ABORT = PAM_ABORT,
- $AUTHTOK_EXPIRED = PAM_AUTHTOK_EXPIRED,
- $MODULE_UNKNOWN = PAM_MODULE_UNKNOWN,
-
-#if !defined(PAM_BAD_ITEM)
- $BAD_ITEM = PAM_SYMBOL_ERR,
-#else
- $BAD_ITEM = PAM_BAD_ITEM,
-#endif
-
-#if !defined(PAM_CONV_AGAIN)
- $CONV_AGAIN = PAM_SYMBOL_ERR,
-#else
- $CONV_AGAIN = PAM_CONV_AGAIN,
-#endif
-
-#if !defined(PAM_INCOMPLETE)
- $INCOMPLETE = PAM_SYMBOL_ERR
-#else
- $INCOMPLETE = PAM_INCOMPLETE
-#endif
-
-};
-
-enum {
- $SILENT = PAM_SILENT,
- $DISALLOW_NULL_AUTHTOK = PAM_DISALLOW_NULL_AUTHTOK,
- $ESTABLISH_CRED = PAM_ESTABLISH_CRED,
- $DELETE_CRED = PAM_DELETE_CRED,
- $REINITIALIZE_CRED = PAM_REINITIALIZE_CRED,
- $REFRESH_CRED = PAM_REFRESH_CRED,
- $CHANGE_EXPIRED_AUTHTOK = PAM_CHANGE_EXPIRED_AUTHTOK
-};
-
-enum {
- $SERVICE = PAM_SERVICE,
- $USER = PAM_USER,
- $TTY = PAM_TTY,
- $RHOST = PAM_RHOST,
- $CONV = PAM_CONV,
- $AUTHTOK = PAM_AUTHTOK,
- $OLDAUTHTOK = PAM_OLDAUTHTOK,
- $RUSER = PAM_RUSER,
- $USER_PROMPT = PAM_USER_PROMPT,
-
- /* Linux-PAM extensions. Leaving these out, for now...
- $FAIL_DELAY = PAM_FAIL_DELAY,
- $XDISPLAY = PAM_XDISPLAY,
- $XAUTHDATA = PAM_XAUTHDATA,
- $AUTHTOK_TYPE = PAM_AUTHTOK_TYPE
- */
-};
-
-enum {
- $PROMPT_ECHO_OFF = PAM_PROMPT_ECHO_OFF,
- $PROMPT_ECHO_ON = PAM_PROMPT_ECHO_ON,
- $ERROR_MSG = PAM_ERROR_MSG,
- $TEXT_INFO = PAM_TEXT_INFO
-};
-
-
diff --git a/pamdefs.go b/pamdefs.go
deleted file mode 100644
index 06fbd79..0000000
--- a/pamdefs.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// godefs -g pam pamdefs.c
-
-// MACHINE GENERATED - DO NOT EDIT.
-
-package pam
-
-// Constants
-const (
- SUCCESS = 0
- OPEN_ERR = 0x1
- SYMBOL_ERR = 0x2
- SERVICE_ERR = 0x3
- SYSTEM_ERR = 0x4
- BUF_ERR = 0x5
- PERM_DENIED = 0x6
- AUTH_ERR = 0x7
- CRED_INSUFFICIENT = 0x8
- AUTHINFO_UNAVAIL = 0x9
- USER_UNKNOWN = 0xa
- MAXTRIES = 0xb
- NEW_AUTHOTK_REQD = 0xc
- ACCT_EXPIRED = 0xd
- SESSION_ERR = 0xe
- CRED_UNAVAIL = 0xf
- CRED_EXPIRED = 0x10
- CRED_ERR = 0x11
- NO_MODULE_DATA = 0x12
- CONV_ERR = 0x13
- AUTHTOK_ERR = 0x14
- AUTHTOK_RECOVERY_ERR = 0x15
- AUTHTOK_LOCK_BUSY = 0x16
- AUTHTOK_DISABLE_AGING = 0x17
- TRY_AGAIN = 0x18
- IGNORE = 0x19
- ABORT = 0x1a
- AUTHTOK_EXPIRED = 0x1b
- MODULE_UNKNOWN = 0x1c
- BAD_ITEM = 0x1d
- CONV_AGAIN = 0x1e
- INCOMPLETE = 0x1f
- SILENT = 0x8000
- DISALLOW_NULL_AUTHTOK = 0x1
- ESTABLISH_CRED = 0x2
- DELETE_CRED = 0x4
- REINITIALIZE_CRED = 0x8
- REFRESH_CRED = 0x10
- CHANGE_EXPIRED_AUTHTOK = 0x20
- SERVICE = 0x1
- USER = 0x2
- TTY = 0x3
- RHOST = 0x4
- CONV = 0x5
- AUTHTOK = 0x6
- OLDAUTHTOK = 0x7
- RUSER = 0x8
- USER_PROMPT = 0x9
- PROMPT_ECHO_OFF = 0x1
- PROMPT_ECHO_ON = 0x2
- ERROR_MSG = 0x3
- TEXT_INFO = 0x4
-)
-
-// Types
diff --git a/transaction.c b/transaction.c
new file mode 100644
index 0000000..e8eb824
--- /dev/null
+++ b/transaction.c
@@ -0,0 +1,42 @@
+#include "_cgo_export.h"
+#include <security/pam_appl.h>
+
+int cb_pam_conv(
+ int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *appdata_ptr)
+{
+ *resp = calloc(num_msg, sizeof **resp);
+ if (!*resp) {
+ return PAM_BUF_ERR;
+ }
+ for (size_t i = 0; i < num_msg; ++i) {
+ const struct pam_message *m = msg[i];
+ struct cbPAMConv_return result =
+ cbPAMConv(m->msg_style, (char *)m->msg, appdata_ptr);
+ if (result.r1 != PAM_SUCCESS) {
+ goto error;
+ }
+ (*resp)[i].resp = result.r0;
+ }
+ return PAM_SUCCESS;
+error:
+ for (size_t i = 0; i < num_msg; ++i) {
+ free((*resp)[i].resp);
+ }
+ free(*resp);
+ *resp = NULL;
+ return PAM_CONV_ERR;
+}
+
+struct pam_conv *make_pam_conv(void *appdata_ptr)
+{
+ struct pam_conv* conv = malloc(sizeof *conv);
+ if (!conv) {
+ return NULL;
+ }
+ conv->conv = cb_pam_conv;
+ conv->appdata_ptr = appdata_ptr;
+ return conv;
+}
diff --git a/transaction.go b/transaction.go
new file mode 100644
index 0000000..404aca8
--- /dev/null
+++ b/transaction.go
@@ -0,0 +1,280 @@
+package pam
+
+//#include <security/pam_appl.h>
+//#include <stdlib.h>
+//#cgo CFLAGS: -Wall -std=c99
+//#cgo LDFLAGS: -lpam
+//struct pam_conv *make_pam_conv(void *);
+import "C"
+
+import (
+ "runtime"
+ "strings"
+ "unsafe"
+)
+
+type Style int
+
+const (
+ PromptEchoOff Style = C.PAM_PROMPT_ECHO_OFF
+ PromptEchoOn = C.PAM_PROMPT_ECHO_ON
+ ErrorMsg = C.PAM_ERROR_MSG
+ TextInfo = C.PAM_TEXT_INFO
+)
+
+// Objects implementing the ConversationHandler interface can
+// be registered as conversation callbacks to be used during
+// PAM authentication. RespondPAM receives a message style
+// (one of PROMPT_ECHO_OFF, PROMPT_ECHO_ON, ERROR_MSG, or
+// TEXT_INFO) and a message string. It is expected to return
+// a response string and a bool indicating success or failure.
+type ConversationHandler interface {
+ RespondPAM(Style, string) (string, error)
+}
+
+// ConversationFunc is an adapter to allow the use of ordinary
+// functions as conversation callbacks. ConversationFunc(f) is
+// a ConversationHandler that calls f, where f must have
+// the signature func(int,string)(string,bool).
+type ConversationFunc func(Style, string) (string, error)
+
+func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) {
+ return f(s, msg)
+}
+
+// Internal conversation structure
+type Conversation struct {
+ handler ConversationHandler
+ conv *C.struct_pam_conv
+}
+
+// Constructs a new conversation object with a given handler and a newly
+// allocated pam_conv struct that uses this object as its appdata_ptr
+func NewConversation(handler ConversationHandler) (*Conversation, C.int) {
+ c := &Conversation{}
+ c.handler = handler
+ c.conv = C.make_pam_conv(unsafe.Pointer(c))
+ if c.conv == nil {
+ return nil, C.PAM_BUF_ERR
+ }
+ return c, C.PAM_SUCCESS
+}
+
+// Go-side function for processing a single conversational message. Ultimately
+// this calls the associated ConversationHandler's ResponsePAM callback with data
+// coming in from a C-side call.
+//export cbPAMConv
+func cbPAMConv(s C.int, msg *C.char, appdata unsafe.Pointer) (*C.char, C.int) {
+ c := (*Conversation)(appdata)
+ r, err := c.handler.RespondPAM(Style(s), C.GoString(msg))
+ if err != nil {
+ return nil, C.PAM_CONV_ERR
+ }
+ return C.CString(r), C.PAM_SUCCESS
+}
+
+// Transaction is the application's handle for a single PAM transaction.
+type Transaction struct {
+ handle *C.pam_handle_t
+ conv *Conversation
+ status C.int
+}
+
+// Ends a PAM transaction. From Linux-PAM documentation: "The [status] argument
+// should be set to the value returned by the last PAM library call."
+func TransactionFinalizer(t *Transaction) {
+ C.pam_end(t.handle, t.status)
+ C.free(unsafe.Pointer(t.conv.conv))
+}
+
+// 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(). The returned *Transaction
+// provides an interface to the remainder of the API.
+//
+// The returned status int may be ABORT, BUF_ERR, SUCCESS, or SYSTEM_ERR, as per
+// the official PAM documentation.
+func Start(service, user string, handler ConversationHandler) (*Transaction, error) {
+ t := &Transaction{}
+ t.conv, t.status = NewConversation(handler)
+ if t.status != C.PAM_SUCCESS {
+ return nil, t
+ }
+ s := C.CString(service)
+ defer C.free(unsafe.Pointer(s))
+ var u *C.char
+ if len(user) != 0 {
+ u = C.CString(user)
+ defer C.free(unsafe.Pointer(s))
+ }
+ t.status = C.pam_start(s, u, t.conv.conv, &t.handle)
+ if t.status != C.PAM_SUCCESS {
+ C.free(unsafe.Pointer(t.conv.conv))
+ return nil, t
+ }
+ runtime.SetFinalizer(t, TransactionFinalizer)
+ return t, nil
+}
+
+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)))
+}
+
+type Item int
+
+const (
+ Service Item = C.PAM_SERVICE
+ User = C.PAM_USER
+ Tty = C.PAM_TTY
+ Rhost = C.PAM_RHOST
+ Authtok = C.PAM_AUTHTOK
+ Oldauthtok = C.PAM_OLDAUTHTOK
+ Ruser = C.PAM_RUSER
+ UserPrompt = C.PAM_USER_PROMPT
+)
+
+// Sets a PAM informational item. Legal values of i are listed here
+// (excluding Linux extensions):
+//
+// http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_set_item
+//
+// PAM_CONV is not supported in order to simplify the Go API
+// (and due to the fact that it is completely unnecessary).
+func (t *Transaction) SetItem(i Item, item string) error {
+ cs := unsafe.Pointer(C.CString(item))
+ defer C.free(cs)
+ t.status = C.pam_set_item(t.handle, C.int(i), cs)
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// Gets a PAM item. Legal values of itemType are as specified by the
+// documentation of SetItem.
+func (t *Transaction) GetItem(i Item) (string, error) {
+ var s unsafe.Pointer
+ t.status = C.pam_get_item(t.handle, C.int(i), &s)
+ if t.status != C.PAM_SUCCESS {
+ return "", t
+ }
+ return C.GoString((*C.char)(s)), nil
+}
+
+type Flags int
+
+const (
+ Silent Flags = C.PAM_SILENT
+ DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK
+ EstablishCred = C.PAM_ESTABLISH_CRED
+ DeleteCred = C.PAM_DELETE_CRED
+ ReinitializeCred = C.PAM_REINITIALIZE_CRED
+ RefreshCred = C.PAM_REFRESH_CRED
+ ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK
+)
+
+// pam_authenticate
+func (t *Transaction) Authenticate(f Flags) error {
+ t.status = C.pam_authenticate(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_setcred
+func (t *Transaction) SetCred(f Flags) error {
+ t.status = C.pam_setcred(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_acctmgmt
+func (t *Transaction) AcctMgmt(f Flags) error {
+ t.status = C.pam_acct_mgmt(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_chauthtok
+func (t *Transaction) ChangeAuthTok(f Flags) error {
+ t.status = C.pam_chauthtok(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_open_session
+func (t *Transaction) OpenSession(f Flags) error {
+ t.status = C.pam_open_session(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_close_session
+func (t *Transaction) CloseSession(f Flags) error {
+ t.status = C.pam_close_session(t.handle, C.int(f))
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_putenv
+func (t *Transaction) PutEnv(nameval string) error {
+ cs := C.CString(nameval)
+ defer C.free(unsafe.Pointer(cs))
+ t.status = C.pam_putenv(t.handle, cs)
+ if t.status != C.PAM_SUCCESS {
+ return t
+ }
+ return nil
+}
+
+// pam_getenv
+func (t *Transaction) GetEnv(name string) string {
+ cs := C.CString(name)
+ defer C.free(unsafe.Pointer(cs))
+ value := C.pam_getenv(t.handle, cs)
+ if value == nil {
+ return ""
+ }
+ return C.GoString(value)
+}
+
+// GetEnvList internally calls pam_getenvlist and then uses some very
+// dangerous code to pull out the returned environment data and mash
+// it into a map[string]string. This call may be safe, but it hasn't
+// been tested on enough platforms/architectures/PAM-implementations to
+// be sure.
+func (t *Transaction) GetEnvList() (map[string]string, error) {
+ env := make(map[string]string)
+ p := C.pam_getenvlist(t.handle)
+ if p == nil {
+ t.status = C.PAM_BUF_ERR
+ return nil, t
+ }
+ list := (uintptr)(unsafe.Pointer(p))
+ for *(*uintptr)(unsafe.Pointer(list)) != 0 {
+ entry := *(*uintptr)(unsafe.Pointer(list))
+ nameval := C.GoString((*C.char)(unsafe.Pointer(entry)))
+ chunks := strings.SplitN(nameval, "=", 2)
+ if len(chunks) == 2 {
+ env[chunks[0]] = chunks[1]
+ list += (uintptr)(unsafe.Sizeof(list))
+ }
+ }
+ return env, nil
+}