diff options
author | Michael Steinert <[email protected]> | 2015-03-27 18:59:29 -0500 |
---|---|---|
committer | Michael Steinert <[email protected]> | 2015-03-28 17:00:58 -0500 |
commit | d67bb86e54c08eb5ae75148195a0c840758ee43c (patch) | |
tree | 3f47649d11107635110fa9238536d7a2eb963200 | |
parent | a6dbe3c2bf85196d35238b0a6edb696963184315 (diff) |
Update interface
-rw-r--r-- | example/.gitignore | 1 | ||||
-rw-r--r-- | example/example.go | 62 | ||||
-rw-r--r-- | golang-pam.c | 66 | ||||
-rw-r--r-- | golang-pam.h | 11 | ||||
-rw-r--r-- | pam.go | 197 | ||||
-rw-r--r-- | pamdefs.c | 90 | ||||
-rw-r--r-- | pamdefs.go | 63 | ||||
-rw-r--r-- | transaction.c | 42 | ||||
-rw-r--r-- | transaction.go | 280 |
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); - @@ -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 +} |