Update interface
This commit is contained in:
1
example/.gitignore
vendored
Normal file
1
example/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/example
|
||||
62
example/example.go
Normal file
62
example/example.go
Normal file
@@ -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!")
|
||||
}
|
||||
66
golang-pam.c
66
golang-pam.c
@@ -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;
|
||||
}
|
||||
|
||||
11
golang-pam.h
11
golang-pam.h
@@ -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);
|
||||
|
||||
197
pam.go
197
pam.go
@@ -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
|
||||
}
|
||||
|
||||
90
pamdefs.c
90
pamdefs.c
@@ -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
|
||||
};
|
||||
|
||||
|
||||
63
pamdefs.go
63
pamdefs.go
@@ -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
|
||||
42
transaction.c
Normal file
42
transaction.c
Normal file
@@ -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;
|
||||
}
|
||||
280
transaction.go
Normal file
280
transaction.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user