Files
msteinert-go-pam/transaction.go

407 lines
12 KiB
Go
Raw Normal View History

2015-12-04 10:05:44 -06:00
// Package pam provides a wrapper for the PAM application API.
2015-03-27 18:59:29 -05:00
package pam
//#cgo CFLAGS: -Wall -std=c99
//#cgo LDFLAGS: -lpam
//
2015-03-27 18:59:29 -05:00
//#include <security/pam_appl.h>
//#include <stdlib.h>
//#include <stdint.h>
//
//#ifdef PAM_BINARY_PROMPT
//#define BINARY_PROMPT_IS_SUPPORTED 1
//#else
//#include <limits.h>
//#define PAM_BINARY_PROMPT INT_MAX
//#define BINARY_PROMPT_IS_SUPPORTED 0
//#endif
//
//void init_pam_conv(struct pam_conv *conv, uintptr_t);
//int pam_start_confdir(const char *service_name, const char *user, const struct pam_conv *pam_conversation, const char *confdir, pam_handle_t **pamh) __attribute__ ((weak));
//int check_pam_start_confdir(void);
2015-03-27 18:59:29 -05:00
import "C"
import (
"errors"
2015-03-27 18:59:29 -05:00
"runtime"
"runtime/cgo"
2015-03-27 18:59:29 -05:00
"strings"
"unsafe"
)
// success indicates a successful function return.
const success = C.PAM_SUCCESS
2015-04-10 15:04:52 -05:00
// Style is the type of message that the conversation handler should display.
2015-03-27 18:59:29 -05:00
type Style int
2015-04-10 15:04:52 -05:00
// Coversation handler style types.
2015-03-27 18:59:29 -05:00
const (
2015-04-10 15:04:52 -05:00
// PromptEchoOff indicates the conversation handler should obtain a
// string without echoing any text.
2015-03-27 18:59:29 -05:00
PromptEchoOff Style = C.PAM_PROMPT_ECHO_OFF
2015-04-10 15:04:52 -05:00
// PromptEchoOn indicates the conversation handler should obtain a
// string while echoing text.
PromptEchoOn = C.PAM_PROMPT_ECHO_ON
// ErrorMsg indicates the conversation handler should display an
// error message.
ErrorMsg = C.PAM_ERROR_MSG
// TextInfo indicates the conversation handler should display some
// text.
TextInfo = C.PAM_TEXT_INFO
// BinaryPrompt indicates the conversation handler that should implement
// the private binary protocol
BinaryPrompt = C.PAM_BINARY_PROMPT
2015-03-27 18:59:29 -05:00
)
2015-04-10 15:04:52 -05:00
// ConversationHandler is an interface for objects that can be used as
// conversation callbacks during PAM authentication.
2015-03-27 18:59:29 -05:00
type ConversationHandler interface {
2015-04-10 15:04:52 -05:00
// RespondPAM receives a message style and a message string. If the
// message Style is PromptEchoOff or PromptEchoOn then the function
// should return a response string.
2015-03-27 18:59:29 -05:00
RespondPAM(Style, string) (string, error)
}
// BinaryPointer exposes the type used for the data in a binary conversation
// it represents a pointer to data that is produced by the module and that
// must be parsed depending on the protocol in use
type BinaryPointer unsafe.Pointer
// BinaryConversationHandler is an interface for objects that can be used as
// conversation callbacks during PAM authentication if binary protocol is going
// to be supported.
type BinaryConversationHandler interface {
ConversationHandler
// RespondPAMBinary receives a pointer to the binary message. It's up to
// the receiver to parse it according to the protocol specifications.
// The function can return a byte array that will passed as pointer back
// to the module.
RespondPAMBinary(BinaryPointer) ([]byte, error)
}
// ConversationFunc is an adapter to allow the use of ordinary functions as
// conversation callbacks.
2015-03-27 18:59:29 -05:00
type ConversationFunc func(Style, string) (string, error)
2015-04-10 15:04:52 -05:00
// RespondPAM is a conversation callback adapter.
2015-03-27 18:59:29 -05:00
func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) {
return f(s, msg)
}
2015-12-04 09:21:38 -06:00
// cbPAMConv is a wrapper for the conversation callback function.
//
2015-03-27 18:59:29 -05:00
//export cbPAMConv
func cbPAMConv(s C.int, msg *C.char, c C.uintptr_t) (*C.char, C.int) {
2015-12-03 14:59:51 -06:00
var r string
2015-12-04 09:46:42 -06:00
var err error
v := cgo.Handle(c).Value()
style := Style(s)
2023-11-07 11:51:27 +02:00
var handler ConversationHandler
2015-12-03 14:59:51 -06:00
switch cb := v.(type) {
case BinaryConversationHandler:
if style == BinaryPrompt {
bytes, err := cb.RespondPAMBinary(BinaryPointer(msg))
if err != nil {
return nil, C.int(ErrConv)
}
return (*C.char)(C.CBytes(bytes)), success
}
2023-11-07 11:51:27 +02:00
handler = cb
2015-12-03 14:59:51 -06:00
case ConversationHandler:
if style == BinaryPrompt {
return nil, C.int(ErrConv)
}
2023-11-07 11:51:27 +02:00
handler = cb
2015-12-03 14:59:51 -06:00
}
2023-11-07 11:51:27 +02:00
if handler == nil {
return nil, C.int(ErrConv)
2023-11-07 11:51:27 +02:00
}
r, err = handler.RespondPAM(style, C.GoString(msg))
2015-03-27 18:59:29 -05:00
if err != nil {
return nil, C.int(ErrConv)
2015-03-27 18:59:29 -05:00
}
return C.CString(r), success
2015-03-27 18:59:29 -05:00
}
// Transaction is the application's handle for a PAM transaction.
2023-11-07 11:51:27 +02:00
//
//nolint:errname
2015-03-27 18:59:29 -05:00
type Transaction struct {
handle *C.pam_handle_t
2015-12-03 14:59:51 -06:00
conv *C.struct_pam_conv
2015-03-27 18:59:29 -05:00
status C.int
c cgo.Handle
2015-03-27 18:59:29 -05:00
}
2015-12-04 09:21:38 -06:00
// transactionFinalizer cleans up the PAM handle and deletes the callback
// function.
2015-04-10 15:04:52 -05:00
func transactionFinalizer(t *Transaction) {
2015-03-27 18:59:29 -05:00
C.pam_end(t.handle, t.status)
t.c.Delete()
2015-03-27 18:59:29 -05:00
}
2015-04-10 15:04:52 -05:00
// Start initiates a new PAM transaction. Service is treated identically to
// how pam_start treats it internally.
2015-03-27 18:59:29 -05:00
//
// All application calls to PAM begin with Start*. The returned
2015-04-10 15:04:52 -05:00
// transaction provides an interface to the remainder of the API.
2015-03-27 18:59:29 -05:00
func Start(service, user string, handler ConversationHandler) (*Transaction, error) {
return start(service, user, handler, "")
}
// StartFunc registers the handler func as a conversation handler.
func StartFunc(service, user string, handler func(Style, string) (string, error)) (*Transaction, error) {
return Start(service, user, ConversationFunc(handler))
}
// StartConfDir initiates a new PAM transaction. Service is treated identically to
// how pam_start treats it internally.
// confdir allows to define where all pam services are defined. This is used to provide
// custom paths for tests.
//
// All application calls to PAM begin with Start*. The returned
// transaction provides an interface to the remainder of the API.
func StartConfDir(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) {
if !CheckPamHasStartConfdir() {
return nil, errors.New("StartConfDir() was used, but the pam version on the system is not recent enough")
}
return start(service, user, handler, confDir)
}
func start(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) {
switch handler.(type) {
case BinaryConversationHandler:
if !CheckPamHasBinaryProtocol() {
return nil, errors.New("BinaryConversationHandler() was used, but it is not supported by this platform")
}
}
2015-12-04 09:21:38 -06:00
t := &Transaction{
conv: &C.struct_pam_conv{},
c: cgo.NewHandle(handler),
2015-03-27 18:59:29 -05:00
}
C.init_pam_conv(t.conv, C.uintptr_t(t.c))
2015-12-04 09:03:39 -06:00
runtime.SetFinalizer(t, transactionFinalizer)
2015-03-27 18:59:29 -05:00
s := C.CString(service)
defer C.free(unsafe.Pointer(s))
var u *C.char
if len(user) != 0 {
u = C.CString(user)
2015-03-29 11:25:00 -05:00
defer C.free(unsafe.Pointer(u))
2015-03-27 18:59:29 -05:00
}
if confDir == "" {
t.status = C.pam_start(s, u, t.conv, &t.handle)
} else {
c := C.CString(confDir)
defer C.free(unsafe.Pointer(c))
t.status = C.pam_start_confdir(s, u, t.conv, c, &t.handle)
}
if t.status != success {
2015-03-27 18:59:29 -05:00
return nil, t
}
return t, nil
}
func (t *Transaction) Error() string {
return Error(t.status).Error()
2015-03-27 18:59:29 -05:00
}
2015-04-10 15:04:52 -05:00
// Item is a an PAM information type.
2015-03-27 18:59:29 -05:00
type Item int
2015-04-10 15:04:52 -05:00
// PAM Item types.
2015-03-27 18:59:29 -05:00
const (
2015-04-10 15:04:52 -05:00
// Service is the name which identifies the PAM stack.
Service Item = C.PAM_SERVICE
// User identifies the username identity used by a service.
User = C.PAM_USER
// Tty is the terminal name.
Tty = C.PAM_TTY
// Rhost is the requesting host name.
Rhost = C.PAM_RHOST
// Authtok is the currently active authentication token.
Authtok = C.PAM_AUTHTOK
// Oldauthtok is the old authentication token.
Oldauthtok = C.PAM_OLDAUTHTOK
// Ruser is the requesting user name.
Ruser = C.PAM_RUSER
// UserPrompt is the string use to prompt for a username.
UserPrompt = C.PAM_USER_PROMPT
2015-03-27 18:59:29 -05:00
)
2015-04-10 15:04:52 -05:00
// SetItem sets a PAM information item.
2015-03-27 18:59:29 -05:00
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 != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// GetItem retrieves a PAM information item.
2015-03-27 18:59:29 -05:00
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 != success {
2015-03-27 18:59:29 -05:00
return "", t
}
return C.GoString((*C.char)(s)), nil
}
2015-04-10 15:04:52 -05:00
// Flags are inputs to various PAM functions than be combined with a bitwise
// or. Refer to the official PAM documentation for which flags are accepted
// by which functions.
2015-03-27 18:59:29 -05:00
type Flags int
2015-04-10 15:04:52 -05:00
// PAM Flag types.
2015-03-27 18:59:29 -05:00
const (
2015-04-10 15:04:52 -05:00
// Silent indicates that no messages should be emitted.
Silent Flags = C.PAM_SILENT
// DisallowNullAuthtok indicates that authorization should fail
// if the user does not have a registered authentication token.
DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK
// EstablishCred indicates that credentials should be established
// for the user.
EstablishCred = C.PAM_ESTABLISH_CRED
// DeleteCred inidicates that credentials should be deleted.
DeleteCred = C.PAM_DELETE_CRED
// ReinitializeCred indicates that credentials should be fully
// reinitialized.
ReinitializeCred = C.PAM_REINITIALIZE_CRED
// RefreshCred indicates that the lifetime of existing credentials
// should be extended.
RefreshCred = C.PAM_REFRESH_CRED
// ChangeExpiredAuthtok indicates that the authentication token
// should be changed if it has expired.
ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK
2015-03-27 18:59:29 -05:00
)
2015-04-10 15:04:52 -05:00
// Authenticate is used to authenticate the user.
//
// Valid flags: Silent, DisallowNullAuthtok
2015-03-27 18:59:29 -05:00
func (t *Transaction) Authenticate(f Flags) error {
t.status = C.pam_authenticate(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// SetCred is used to establish, maintain and delete the credentials of a
// user.
//
// Valid flags: EstablishCred, DeleteCred, ReinitializeCred, RefreshCred
2015-03-27 18:59:29 -05:00
func (t *Transaction) SetCred(f Flags) error {
t.status = C.pam_setcred(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// AcctMgmt is used to determine if the user's account is valid.
//
// Valid flags: Silent, DisallowNullAuthtok
2015-03-27 18:59:29 -05:00
func (t *Transaction) AcctMgmt(f Flags) error {
t.status = C.pam_acct_mgmt(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// ChangeAuthTok is used to change the authentication token.
//
// Valid flags: Silent, ChangeExpiredAuthtok
2015-03-27 18:59:29 -05:00
func (t *Transaction) ChangeAuthTok(f Flags) error {
t.status = C.pam_chauthtok(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// OpenSession sets up a user session for an authenticated user.
//
// Valid flags: Slient
2015-03-27 18:59:29 -05:00
func (t *Transaction) OpenSession(f Flags) error {
t.status = C.pam_open_session(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// CloseSession closes a previously opened session.
//
// Valid flags: Silent
2015-03-27 18:59:29 -05:00
func (t *Transaction) CloseSession(f Flags) error {
t.status = C.pam_close_session(t.handle, C.int(f))
if t.status != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// PutEnv adds or changes the value of PAM environment variables.
//
// NAME=value will set a variable to a value.
// NAME= will set a variable to an empty value.
// NAME (without an "=") will delete a variable.
2015-03-27 18:59:29 -05:00
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 != success {
2015-03-27 18:59:29 -05:00
return t
}
return nil
}
2015-04-10 15:04:52 -05:00
// GetEnv is used to retrieve a PAM environment variable.
2015-03-27 18:59:29 -05:00
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)
}
2015-03-30 18:20:09 -05:00
func next(p **C.char) **C.char {
return (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(p)))
}
2015-04-10 15:04:52 -05:00
// GetEnvList returns a copy of the PAM environment as a map.
2015-03-27 18:59:29 -05:00
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.int(ErrBuf)
2015-03-27 18:59:29 -05:00
return nil, t
}
2015-03-30 18:20:09 -05:00
for q := p; *q != nil; q = next(q) {
2015-03-30 18:13:10 -05:00
chunks := strings.SplitN(C.GoString(*q), "=", 2)
2015-03-27 18:59:29 -05:00
if len(chunks) == 2 {
env[chunks[0]] = chunks[1]
}
2015-03-30 18:13:10 -05:00
C.free(unsafe.Pointer(*q))
2015-03-27 18:59:29 -05:00
}
C.free(unsafe.Pointer(p))
2015-03-27 18:59:29 -05:00
return env, nil
}
// CheckPamHasStartConfdir return if pam on system supports pam_system_confdir
func CheckPamHasStartConfdir() bool {
return C.check_pam_start_confdir() == 0
}
// CheckPamHasBinaryProtocol return if pam on system supports PAM_BINARY_PROMPT
func CheckPamHasBinaryProtocol() bool {
return C.BINARY_PROMPT_IS_SUPPORTED != 0
}