Initial commit

This commit is contained in:
Ken Rockot
2011-05-15 18:13:01 +00:00
commit c897eb79b3
10 changed files with 536 additions and 0 deletions

20
Makefile Normal file
View File

@@ -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

14
README Normal file
View File

@@ -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!

11
examples/Makefile Normal file
View File

@@ -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

74
examples/fakelogin.go Normal file
View File

@@ -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")
}

16
pam/Makefile Normal file
View File

@@ -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

68
pam/gopam.c Normal file
View File

@@ -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;
}

11
pam/gopam.h Normal file
View File

@@ -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);

185
pam/pam.go Normal file
View File

@@ -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
}

74
pam/pamdefs.c Normal file
View File

@@ -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
};

63
pam/pamdefs.go Normal file
View File

@@ -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