From e0e1d2de2c0da901e5078f0fbfcbb5ca96d96eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 20 Nov 2023 21:29:55 +0100 Subject: [PATCH] transaction: Move PAM app side function only to app-transaction In this way all these features not even compiled when creating modules, avoiding generating unused code. --- app-transaction.go | 218 ++++++++++++++++++++++++++++++++++++++++++++- transaction.go | 210 ------------------------------------------- 2 files changed, 215 insertions(+), 213 deletions(-) diff --git a/app-transaction.go b/app-transaction.go index 2ddfaaa..671b48e 100644 --- a/app-transaction.go +++ b/app-transaction.go @@ -3,12 +3,46 @@ package pam /* -#include -#include +#include "transaction.h" */ import "C" -import "runtime/cgo" +import ( + "fmt" + "runtime/cgo" + "sync/atomic" + "unsafe" +) + +// ConversationHandler is an interface for objects that can be used as +// conversation callbacks during PAM authentication. +type ConversationHandler interface { + // 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. + RespondPAM(Style, string) (string, error) +} + +// 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. +type ConversationFunc func(Style, string) (string, error) + +// RespondPAM is a conversation callback adapter. +func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) { + return f(s, msg) +} // _go_pam_conv_handler is a C wrapper for the conversation callback function. // @@ -22,3 +56,181 @@ func _go_pam_conv_handler(msg *C.struct_pam_message, c C.uintptr_t, outMsg **C.c *outMsg = replyMsg return r } + +// pamConvHandler is a Go wrapper for the conversation callback function. +func pamConvHandler(style Style, msg *C.char, handler ConversationHandler) (*C.char, C.int) { + var r string + var err error + switch cb := handler.(type) { + case BinaryConversationHandler: + if style == BinaryPrompt { + bytes, err := cb.RespondPAMBinary(BinaryPointer(msg)) + if err != nil { + return nil, C.int(ErrConv) + } + if bytes == nil { + return nil, success + } + return (*C.char)(C.CBytes(bytes)), success + } + handler = cb + case ConversationHandler: + if style == BinaryPrompt { + return nil, C.int(ErrConv) + } + handler = cb + default: + return nil, C.int(ErrConv) + } + r, err = handler.RespondPAM(style, C.GoString(msg)) + if err != nil { + return nil, C.int(ErrConv) + } + return C.CString(r), success +} + +// Transaction is the application's handle for a PAM transaction. +type Transaction struct { + transactionBase + + conv *C.struct_pam_conv + c cgo.Handle +} + +// 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. +// +// It's responsibility of the Transaction owner to release all the resources +// allocated underneath by PAM by calling End() once done. +// +// It's not advised to End the transaction using a runtime.SetFinalizer unless +// you're absolutely sure that your stack is multi-thread friendly (normally it +// is not!) and using a LockOSThread/UnlockOSThread pair. +func Start(service, user string, handler ConversationHandler) (*Transaction, error) { + return start(service, user, handler, "") +} + +// StartFunc registers the handler func as a conversation handler and starts +// the transaction (see Start() documentation). +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. +// +// It's responsibility of the Transaction owner to release all the resources +// allocated underneath by PAM by calling End() once done. +// +// It's not advised to End the transaction using a runtime.SetFinalizer unless +// you're absolutely sure that your stack is multi-thread friendly (normally it +// is not!) and using a LockOSThread/UnlockOSThread pair. +func StartConfDir(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) { + if !CheckPamHasStartConfdir() { + return nil, fmt.Errorf( + "%w: StartConfDir was used, but the pam version on the system is not recent enough", + ErrSystem) + } + + 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, fmt.Errorf("%w: BinaryConversationHandler was used, but it is not supported by this platform", + ErrSystem) + } + } + t := &Transaction{ + conv: &C.struct_pam_conv{}, + c: cgo.NewHandle(handler), + } + + C.init_pam_conv(t.conv, C.uintptr_t(t.c)) + 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(u)) + } + var err error + if confDir == "" { + err = t.handlePamStatus(C.pam_start(s, u, t.conv, &t.handle)) + } else { + c := C.CString(confDir) + defer C.free(unsafe.Pointer(c)) + err = t.handlePamStatus(C.pam_start_confdir(s, u, t.conv, c, &t.handle)) + } + if err != nil { + var _ = t.End() + return nil, err + } + return t, nil +} + +// Authenticate is used to authenticate the user. +// +// Valid flags: Silent, DisallowNullAuthtok +func (t *Transaction) Authenticate(f Flags) error { + return t.handlePamStatus(C.pam_authenticate(t.handle, C.int(f))) +} + +// SetCred is used to establish, maintain and delete the credentials of a +// user. +// +// Valid flags: EstablishCred, DeleteCred, ReinitializeCred, RefreshCred +func (t *Transaction) SetCred(f Flags) error { + return t.handlePamStatus(C.pam_setcred(t.handle, C.int(f))) +} + +// AcctMgmt is used to determine if the user's account is valid. +// +// Valid flags: Silent, DisallowNullAuthtok +func (t *Transaction) AcctMgmt(f Flags) error { + return t.handlePamStatus(C.pam_acct_mgmt(t.handle, C.int(f))) +} + +// ChangeAuthTok is used to change the authentication token. +// +// Valid flags: Silent, ChangeExpiredAuthtok +func (t *Transaction) ChangeAuthTok(f Flags) error { + return t.handlePamStatus(C.pam_chauthtok(t.handle, C.int(f))) +} + +// OpenSession sets up a user session for an authenticated user. +// +// Valid flags: Slient +func (t *Transaction) OpenSession(f Flags) error { + return t.handlePamStatus(C.pam_open_session(t.handle, C.int(f))) +} + +// CloseSession closes a previously opened session. +// +// Valid flags: Silent +func (t *Transaction) CloseSession(f Flags) error { + return t.handlePamStatus(C.pam_close_session(t.handle, C.int(f))) +} + +// End cleans up the PAM handle and deletes the callback function. +// It must be called when done with the transaction. +func (t *Transaction) End() error { + handle := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&t.handle)), nil) + if handle == nil { + return nil + } + + defer t.c.Delete() + return t.handlePamStatus(C.pam_end((*C.pam_handle_t)(handle), + C.int(t.lastStatus.Load()))) +} diff --git a/transaction.go b/transaction.go index 600f0cf..8b32704 100644 --- a/transaction.go +++ b/transaction.go @@ -8,8 +8,6 @@ package pam import "C" import ( - "fmt" - "runtime/cgo" "strings" "sync/atomic" "unsafe" @@ -40,73 +38,11 @@ const ( BinaryPrompt Style = C.PAM_BINARY_PROMPT ) -// ConversationHandler is an interface for objects that can be used as -// conversation callbacks during PAM authentication. -type ConversationHandler interface { - // 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. - 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. -type ConversationFunc func(Style, string) (string, error) - -// RespondPAM is a conversation callback adapter. -func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) { - return f(s, msg) -} - -// pamConvHandler is a Go wrapper for the conversation callback function. -func pamConvHandler(style Style, msg *C.char, handler ConversationHandler) (*C.char, C.int) { - var r string - var err error - switch cb := handler.(type) { - case BinaryConversationHandler: - if style == BinaryPrompt { - bytes, err := cb.RespondPAMBinary(BinaryPointer(msg)) - if err != nil { - return nil, C.int(ErrConv) - } - if bytes == nil { - return nil, success - } - return (*C.char)(C.CBytes(bytes)), success - } - handler = cb - case ConversationHandler: - if style == BinaryPrompt { - return nil, C.int(ErrConv) - } - handler = cb - default: - return nil, C.int(ErrConv) - } - r, err = handler.RespondPAM(style, C.GoString(msg)) - if err != nil { - return nil, C.int(ErrConv) - } - return C.CString(r), success -} - // NativeHandle is the type of the native PAM handle for a transaction so that // it can be exported type NativeHandle = *C.pam_handle_t @@ -119,27 +55,6 @@ type transactionBase struct { lastStatus atomic.Int32 } -// Transaction is the application's handle for a PAM transaction. -type Transaction struct { - transactionBase - - conv *C.struct_pam_conv - c cgo.Handle -} - -// End cleans up the PAM handle and deletes the callback function. -// It must be called when done with the transaction. -func (t *Transaction) End() error { - handle := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&t.handle)), nil) - if handle == nil { - return nil - } - - defer t.c.Delete() - return t.handlePamStatus(C.pam_end((*C.pam_handle_t)(handle), - C.int(t.lastStatus.Load()))) -} - // Allows to call pam functions managing return status func (t *transactionBase) handlePamStatus(cStatus C.int) error { t.lastStatus.Store(int32(cStatus)) @@ -149,88 +64,6 @@ func (t *transactionBase) handlePamStatus(cStatus C.int) error { return nil } -// 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. -// -// It's responsibility of the Transaction owner to release all the resources -// allocated underneath by PAM by calling End() once done. -// -// It's not advised to End the transaction using a runtime.SetFinalizer unless -// you're absolutely sure that your stack is multi-thread friendly (normally it -// is not!) and using a LockOSThread/UnlockOSThread pair. -func Start(service, user string, handler ConversationHandler) (*Transaction, error) { - return start(service, user, handler, "") -} - -// StartFunc registers the handler func as a conversation handler and starts -// the transaction (see Start() documentation). -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. -// -// It's responsibility of the Transaction owner to release all the resources -// allocated underneath by PAM by calling End() once done. -// -// It's not advised to End the transaction using a runtime.SetFinalizer unless -// you're absolutely sure that your stack is multi-thread friendly (normally it -// is not!) and using a LockOSThread/UnlockOSThread pair. -func StartConfDir(service, user string, handler ConversationHandler, confDir string) (*Transaction, error) { - if !CheckPamHasStartConfdir() { - return nil, fmt.Errorf( - "%w: StartConfDir was used, but the pam version on the system is not recent enough", - ErrSystem) - } - - 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, fmt.Errorf("%w: BinaryConversationHandler was used, but it is not supported by this platform", - ErrSystem) - } - } - t := &Transaction{ - conv: &C.struct_pam_conv{}, - c: cgo.NewHandle(handler), - } - - C.init_pam_conv(t.conv, C.uintptr_t(t.c)) - 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(u)) - } - var err error - if confDir == "" { - err = t.handlePamStatus(C.pam_start(s, u, t.conv, &t.handle)) - } else { - c := C.CString(confDir) - defer C.free(unsafe.Pointer(c)) - err = t.handlePamStatus(C.pam_start_confdir(s, u, t.conv, c, &t.handle)) - } - if err != nil { - var _ = t.End() - return nil, err - } - return t, nil -} - // Item is a an PAM information type. type Item int @@ -307,49 +140,6 @@ const ( ChangeExpiredAuthtok Flags = C.PAM_CHANGE_EXPIRED_AUTHTOK ) -// Authenticate is used to authenticate the user. -// -// Valid flags: Silent, DisallowNullAuthtok -func (t *Transaction) Authenticate(f Flags) error { - return t.handlePamStatus(C.pam_authenticate(t.handle, C.int(f))) -} - -// SetCred is used to establish, maintain and delete the credentials of a -// user. -// -// Valid flags: EstablishCred, DeleteCred, ReinitializeCred, RefreshCred -func (t *Transaction) SetCred(f Flags) error { - return t.handlePamStatus(C.pam_setcred(t.handle, C.int(f))) -} - -// AcctMgmt is used to determine if the user's account is valid. -// -// Valid flags: Silent, DisallowNullAuthtok -func (t *Transaction) AcctMgmt(f Flags) error { - return t.handlePamStatus(C.pam_acct_mgmt(t.handle, C.int(f))) -} - -// ChangeAuthTok is used to change the authentication token. -// -// Valid flags: Silent, ChangeExpiredAuthtok -func (t *Transaction) ChangeAuthTok(f Flags) error { - return t.handlePamStatus(C.pam_chauthtok(t.handle, C.int(f))) -} - -// OpenSession sets up a user session for an authenticated user. -// -// Valid flags: Slient -func (t *Transaction) OpenSession(f Flags) error { - return t.handlePamStatus(C.pam_open_session(t.handle, C.int(f))) -} - -// CloseSession closes a previously opened session. -// -// Valid flags: Silent -func (t *Transaction) CloseSession(f Flags) error { - return t.handlePamStatus(C.pam_close_session(t.handle, C.int(f))) -} - // PutEnv adds or changes the value of PAM environment variables. // // NAME=value will set a variable to a value.