diff options
author | Ken Rockot <[email protected]> | 2011-05-15 18:13:01 +0000 |
---|---|---|
committer | Ken Rockot <[email protected]> | 2011-05-15 18:13:01 +0000 |
commit | c897eb79b3d20ed44ea4b970bf80a8ee035e00d3 (patch) | |
tree | d68594f12435afd6b54aa3b94a4b319d16c874f0 |
Initial commit
-rw-r--r-- | Makefile | 20 | ||||
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | examples/Makefile | 11 | ||||
-rw-r--r-- | examples/fakelogin.go | 74 | ||||
-rw-r--r-- | pam/Makefile | 16 | ||||
-rw-r--r-- | pam/gopam.c | 68 | ||||
-rw-r--r-- | pam/gopam.h | 11 | ||||
-rw-r--r-- | pam/pam.go | 185 | ||||
-rw-r--r-- | pam/pamdefs.c | 74 | ||||
-rw-r--r-- | pam/pamdefs.go | 63 |
10 files changed, 536 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..730a2c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +include $(GOROOT)/src/Make.inc + +.PHONY: all pam install examples clean + +all: install examples + +pam: + gomake -C pam + +install: pam + gomake -C pam install + +examples: + gomake -C examples + +clean: + gomake -C pam clean + gomake -C examples clean + + @@ -0,0 +1,14 @@ +It's Go! It's PAM (Pluggable Authentication Modules)! It's GoPAM! + +This is a Go wrapper for the PAM application API. There's not much +else to be said. PAM is a simple API and now it's available for use in Go +applications. + +There's an example of a "fake login" program in the examples +directory. Look at the pam module's godocs for details about the Go +API; for a more general PAM application API reference, peep + +http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html + +In the future, maybe the module API will be wrapped too. I don't know! + diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..fe7cd10 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.inc + +TARG=fakelogin +GOFILES=\ + fakelogin.go + +include $(GOROOT)/src/Make.cmd diff --git a/examples/fakelogin.go b/examples/fakelogin.go new file mode 100644 index 0000000..479e6ac --- /dev/null +++ b/examples/fakelogin.go @@ -0,0 +1,74 @@ +// 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: +// +// (!WARNING!) It echos your password to the terminal (!WARNING!) +// It doesn't switch users. +// It's not a real login. +// +// It does however demonstrate a simple but powerful use of Go PAM. + +package main + +import( + "fmt" + "pam" + "os" + "bufio" +) + +func GetLine(prompt string) (string,bool) { + fmt.Print(prompt) + in := bufio.NewReader(os.Stdin) + input,err := in.ReadString('\n') + if err != nil { + return "",false + } + return input[:len(input)-1],true +} + +// Echo on/off is ignored; echo will always happen. +func DumbPrompter(style int, msg string) (string,bool) { + switch style { + case pam.PROMPT_ECHO_OFF: + return GetLine(msg) + case pam.PROMPT_ECHO_ON: + return GetLine(msg) + case pam.ERROR_MSG: + fmt.Fprintf(os.Stderr, "Error: %s\n", msg) + return "",true + case pam.TEXT_INFO: + fmt.Println(msg) + return "",true + } + return "",false +} + +func main() { + t,status := pam.Start("", "", pam.ResponseFunc(DumbPrompter)) + if status != pam.SUCCESS { + fmt.Fprintf(os.Stderr, "Start() failed: %s\n", t.Error(status)) + return + } + defer func(){ t.End(status) }() + + status = t.Authenticate(0) + if status != pam.SUCCESS { + fmt.Fprintf(os.Stderr, "Auth failed: %s\n", t.Error(status)) + return + } + + fmt.Printf("Authentication succeeded!\n") + fmt.Printf("Goodbye, friend.\n") +} + diff --git a/pam/Makefile b/pam/Makefile new file mode 100644 index 0000000..9239acf --- /dev/null +++ b/pam/Makefile @@ -0,0 +1,16 @@ +include $(GOROOT)/src/Make.inc +TARG=pam +GOFILES:=pamdefs.go +CGOFILES:=pam.go +CGO_LDFLAGS:=-lpam +CGO_OFILES:=gopam.o + +include $(GOROOT)/src/Make.pkg + +DOLLAR:="$" + +pamdefs.go: pamdefs.c + godefs `echo -n $(CGO_FLAGS) | sed 's/\(^ ^$(DOLLAR)]*\)/-f \1/g'` -g pam pamdefs.c > pamdefs.go + gofmt -w pamdefs.go + + diff --git a/pam/gopam.c b/pam/gopam.c new file mode 100644 index 0000000..90d3de3 --- /dev/null +++ b/pam/gopam.c @@ -0,0 +1,68 @@ +#include <security/_pam_types.h> +#include <security/pam_appl.h> +#include <stdlib.h> +#include <string.h> +#include "gopam.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 = 0; 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/pam/gopam.h b/pam/gopam.h new file mode 100644 index 0000000..a9c7b99 --- /dev/null +++ b/pam/gopam.h @@ -0,0 +1,11 @@ +#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/pam.go b/pam/pam.go new file mode 100644 index 0000000..9c97cab --- /dev/null +++ b/pam/pam.go @@ -0,0 +1,185 @@ +// Package pam provides a wrapper for the application layer of the +// Pluggable Authentication Modules library. +package pam + +import ( + //#include "gopam.h" + "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 +} + +// 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{} + conv := newConversation(handler) + var status C.int + if len(user) == 0 { + status = C.pam_start(C.CString(serviceName), nil, conv.cconv, &t.handle) + } else { + status = C.pam_start(C.CString(serviceName), C.CString(user), conv.cconv, &t.handle) + } + 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. +func (t *Transaction) End(status int) { + C.pam_end(t.handle, C.int(status)) +} + +// 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.Split(nameval, "=", 2) + env[chunks[0]] = chunks[1] + list += (uintptr)(unsafe.Sizeof(list)) + } + return env +} + diff --git a/pam/pamdefs.c b/pam/pamdefs.c new file mode 100644 index 0000000..ef0bbc3 --- /dev/null +++ b/pam/pamdefs.c @@ -0,0 +1,74 @@ +#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, + $BAD_ITEM = PAM_BAD_ITEM, + $CONV_AGAIN = PAM_CONV_AGAIN, + $INCOMPLETE = PAM_INCOMPLETE +}; + +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/pam/pamdefs.go b/pam/pamdefs.go new file mode 100644 index 0000000..06fbd79 --- /dev/null +++ b/pam/pamdefs.go @@ -0,0 +1,63 @@ +// 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 |