summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen Rockot <[email protected]>2011-05-15 18:13:01 +0000
committerKen Rockot <[email protected]>2011-05-15 18:13:01 +0000
commitc897eb79b3d20ed44ea4b970bf80a8ee035e00d3 (patch)
treed68594f12435afd6b54aa3b94a4b319d16c874f0
Initial commit
-rw-r--r--Makefile20
-rw-r--r--README14
-rw-r--r--examples/Makefile11
-rw-r--r--examples/fakelogin.go74
-rw-r--r--pam/Makefile16
-rw-r--r--pam/gopam.c68
-rw-r--r--pam/gopam.h11
-rw-r--r--pam/pam.go185
-rw-r--r--pam/pamdefs.c74
-rw-r--r--pam/pamdefs.go63
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
+
+
diff --git a/README b/README
new file mode 100644
index 0000000..52cf817
--- /dev/null
+++ b/README
@@ -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