628 lines
18 KiB
Go
628 lines
18 KiB
Go
// Package pam provides a wrapper for the PAM application API.
|
|
package pam
|
|
|
|
/*
|
|
#include "transaction.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"runtime/cgo"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
const maxNumMsg = C.PAM_MAX_NUM_MSG
|
|
|
|
// ModuleTransaction is an interface that a pam module transaction
|
|
// should implement.
|
|
type ModuleTransaction interface {
|
|
SetItem(Item, string) error
|
|
GetItem(Item) (string, error)
|
|
PutEnv(nameVal string) error
|
|
GetEnv(name string) string
|
|
GetEnvList() (map[string]string, error)
|
|
GetUser(prompt string) (string, error)
|
|
SetData(key string, data any) error
|
|
GetData(key string) (any, error)
|
|
StartStringConv(style Style, prompt string) (StringConvResponse, error)
|
|
StartStringConvf(style Style, format string, args ...interface{}) (
|
|
StringConvResponse, error)
|
|
StartBinaryConv([]byte) (BinaryConvResponse, error)
|
|
StartConv(ConvRequest) (ConvResponse, error)
|
|
StartConvMulti([]ConvRequest) ([]ConvResponse, error)
|
|
}
|
|
|
|
// ModuleHandlerFunc is a function type used by the ModuleHandler.
|
|
type ModuleHandlerFunc func(ModuleTransaction, Flags, []string) error
|
|
|
|
// ModuleTransaction is the module-side handle for a PAM transaction.
|
|
type moduleTransaction struct {
|
|
transactionBase
|
|
convMutex *sync.Mutex
|
|
}
|
|
|
|
// ModuleHandler is an interface for objects that can be used to create
|
|
// PAM modules from go.
|
|
type ModuleHandler interface {
|
|
AcctMgmt(ModuleTransaction, Flags, []string) error
|
|
Authenticate(ModuleTransaction, Flags, []string) error
|
|
ChangeAuthTok(ModuleTransaction, Flags, []string) error
|
|
CloseSession(ModuleTransaction, Flags, []string) error
|
|
OpenSession(ModuleTransaction, Flags, []string) error
|
|
SetCred(ModuleTransaction, Flags, []string) error
|
|
}
|
|
|
|
// ModuleTransactionInvoker is an interface that a pam module transaction
|
|
// should implement to redirect requests from C handlers to go,
|
|
type ModuleTransactionInvoker interface {
|
|
ModuleTransaction
|
|
InvokeHandler(handler ModuleHandlerFunc, flags Flags, args []string) error
|
|
}
|
|
|
|
// NewModuleTransactionParallelConv allows initializing a transaction from the
|
|
// module side. Conversations using this transaction can be multi-thread, but
|
|
// this requires the application loading the module to support this, otherwise
|
|
// we may just break their assumptions.
|
|
func NewModuleTransactionParallelConv(handle NativeHandle) ModuleTransaction {
|
|
return &moduleTransaction{transactionBase{handle: handle}, nil}
|
|
}
|
|
|
|
// NewModuleTransactionInvoker allows initializing a transaction invoker from the
|
|
// module side.
|
|
func NewModuleTransactionInvoker(handle NativeHandle) ModuleTransactionInvoker {
|
|
return &moduleTransaction{transactionBase{handle: handle}, &sync.Mutex{}}
|
|
}
|
|
|
|
// NewModuleTransactionInvokerParallelConv allows initializing a transaction invoker
|
|
// from the module side.
|
|
// Conversations using this transaction can be multi-thread, but this requires
|
|
// the application loading the module to support this, otherwise we may just
|
|
// break their assumptions.
|
|
func NewModuleTransactionInvokerParallelConv(handle NativeHandle) ModuleTransactionInvoker {
|
|
return &moduleTransaction{transactionBase{handle: handle}, nil}
|
|
}
|
|
|
|
func (m *moduleTransaction) InvokeHandler(handler ModuleHandlerFunc,
|
|
flags Flags, args []string) error {
|
|
invoker := func() error {
|
|
if handler == nil {
|
|
return ErrIgnore
|
|
}
|
|
err := handler(m, flags, args)
|
|
if err != nil {
|
|
service, _ := m.GetItem(Service)
|
|
|
|
var pamErr Error
|
|
if !errors.As(err, &pamErr) {
|
|
err = ErrSystem
|
|
}
|
|
|
|
if pamErr == ErrIgnore || service == "" {
|
|
return err
|
|
}
|
|
|
|
return fmt.Errorf("%s failed: %w", service, err)
|
|
}
|
|
return nil
|
|
}
|
|
err := invoker()
|
|
if errors.Is(err, Error(0)) {
|
|
err = nil
|
|
}
|
|
var status int32
|
|
if err != nil {
|
|
status = int32(ErrSystem)
|
|
|
|
var pamErr Error
|
|
if errors.As(err, &pamErr) {
|
|
status = int32(pamErr)
|
|
}
|
|
}
|
|
m.lastStatus.Store(status)
|
|
return err
|
|
}
|
|
|
|
type moduleTransactionIface interface {
|
|
getUser(outUser **C.char, prompt *C.char) C.int
|
|
setData(key *C.char, handle C.uintptr_t) C.int
|
|
getData(key *C.char, outHandle *C.uintptr_t) C.int
|
|
getConv() (*C.struct_pam_conv, error)
|
|
hasBinaryProtocol() bool
|
|
startConv(conv *C.struct_pam_conv, nMsg C.int,
|
|
messages **C.struct_pam_message,
|
|
outResponses **C.struct_pam_response) C.int
|
|
}
|
|
|
|
func (m *moduleTransaction) getUser(outUser **C.char, prompt *C.char) C.int {
|
|
return C.pam_get_user(m.handle, outUser, prompt)
|
|
}
|
|
|
|
// getUserImpl is the default implementation for GetUser, but kept as private so
|
|
// that can be used to test the pam package
|
|
func (m *moduleTransaction) getUserImpl(iface moduleTransactionIface,
|
|
prompt string) (string, error) {
|
|
var user *C.char
|
|
var cPrompt = C.CString(prompt)
|
|
defer C.free(unsafe.Pointer(cPrompt))
|
|
err := m.handlePamStatus(iface.getUser(&user, cPrompt))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return C.GoString(user), nil
|
|
}
|
|
|
|
// GetUser is similar to GetItem(User), but it would start a conversation if
|
|
// no user is currently set in PAM.
|
|
func (m *moduleTransaction) GetUser(prompt string) (string, error) {
|
|
return m.getUserImpl(m, prompt)
|
|
}
|
|
|
|
// SetData allows to save any value in the module data that is preserved
|
|
// during the whole time the module is loaded.
|
|
func (m *moduleTransaction) SetData(key string, data any) error {
|
|
return m.setDataImpl(m, key, data)
|
|
}
|
|
|
|
func (m *moduleTransaction) setData(key *C.char, handle C.uintptr_t) C.int {
|
|
return C.set_data(m.handle, key, handle)
|
|
}
|
|
|
|
// setDataImpl is the implementation for SetData for testing purposes.
|
|
func (m *moduleTransaction) setDataImpl(iface moduleTransactionIface,
|
|
key string, data any) error {
|
|
var cKey = C.CString(key)
|
|
defer C.free(unsafe.Pointer(cKey))
|
|
var handle cgo.Handle
|
|
if data != nil {
|
|
handle = cgo.NewHandle(data)
|
|
}
|
|
return m.handlePamStatus(iface.setData(cKey, C.uintptr_t(handle)))
|
|
}
|
|
|
|
//export _go_pam_data_cleanup
|
|
func _go_pam_data_cleanup(h NativeHandle, handle C.uintptr_t, status C.int) {
|
|
cgo.Handle(handle).Delete()
|
|
}
|
|
|
|
// GetData allows to get any value from the module data saved using SetData
|
|
// that is preserved across the whole time the module is loaded.
|
|
func (m *moduleTransaction) GetData(key string) (any, error) {
|
|
return m.getDataImpl(m, key)
|
|
}
|
|
|
|
func (m *moduleTransaction) getData(key *C.char, outHandle *C.uintptr_t) C.int {
|
|
return C.get_data(m.handle, key, outHandle)
|
|
}
|
|
|
|
// getDataImpl is the implementation for GetData for testing purposes.
|
|
func (m *moduleTransaction) getDataImpl(iface moduleTransactionIface,
|
|
key string) (any, error) {
|
|
var cKey = C.CString(key)
|
|
defer C.free(unsafe.Pointer(cKey))
|
|
var handle C.uintptr_t
|
|
if err := m.handlePamStatus(iface.getData(cKey, &handle)); err != nil {
|
|
return nil, err
|
|
}
|
|
if goHandle := cgo.Handle(handle); goHandle != cgo.Handle(0) {
|
|
return goHandle.Value(), nil
|
|
}
|
|
|
|
return nil, m.handlePamStatus(C.int(ErrNoModuleData))
|
|
}
|
|
|
|
// getConv is a private function to get the conversation pointer to be used
|
|
// with C.do_conv() to initiate conversations.
|
|
func (m *moduleTransaction) getConv() (*C.struct_pam_conv, error) {
|
|
var convPtr unsafe.Pointer
|
|
|
|
if err := m.handlePamStatus(
|
|
C.pam_get_item(m.handle, C.PAM_CONV, &convPtr)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*C.struct_pam_conv)(convPtr), nil
|
|
}
|
|
|
|
// ConvRequest is an interface that all the Conversation requests should
|
|
// implement.
|
|
type ConvRequest interface {
|
|
Style() Style
|
|
}
|
|
|
|
// ConvResponse is an interface that all the Conversation responses should
|
|
// implement.
|
|
type ConvResponse interface {
|
|
Style() Style
|
|
}
|
|
|
|
// StringConvRequest is a ConvRequest for performing text-based conversations.
|
|
type StringConvRequest struct {
|
|
style Style
|
|
prompt string
|
|
}
|
|
|
|
// NewStringConvRequest creates a new StringConvRequest.
|
|
func NewStringConvRequest(style Style, prompt string) StringConvRequest {
|
|
return StringConvRequest{style, prompt}
|
|
}
|
|
|
|
// Style returns the conversation style of the StringConvRequest.
|
|
func (s StringConvRequest) Style() Style {
|
|
return s.style
|
|
}
|
|
|
|
// Prompt returns the conversation style of the StringConvRequest.
|
|
func (s StringConvRequest) Prompt() string {
|
|
return s.prompt
|
|
}
|
|
|
|
// StringConvResponse is an interface that string Conversation responses implements.
|
|
type StringConvResponse interface {
|
|
ConvResponse
|
|
Response() string
|
|
}
|
|
|
|
// stringConvResponse is a StringConvResponse implementation used for text-based
|
|
// conversation responses.
|
|
type stringConvResponse struct {
|
|
style Style
|
|
response string
|
|
}
|
|
|
|
// Style returns the conversation style of the StringConvResponse.
|
|
func (s stringConvResponse) Style() Style {
|
|
return s.style
|
|
}
|
|
|
|
// Response returns the string response of the conversation.
|
|
func (s stringConvResponse) Response() string {
|
|
return s.response
|
|
}
|
|
|
|
// BinaryFinalizer is a type of function that can be used to release
|
|
// the binary when it's not required anymore
|
|
type BinaryFinalizer func(BinaryPointer)
|
|
|
|
// BinaryConvRequester is the interface that binary ConvRequests should
|
|
// implement
|
|
type BinaryConvRequester interface {
|
|
ConvRequest
|
|
Pointer() BinaryPointer
|
|
CreateResponse(BinaryPointer) BinaryConvResponse
|
|
Release()
|
|
}
|
|
|
|
// BinaryConvRequest is a ConvRequest for performing binary conversations.
|
|
type BinaryConvRequest struct {
|
|
ptr atomic.Uintptr
|
|
finalizer BinaryFinalizer
|
|
responseFinalizer BinaryFinalizer
|
|
}
|
|
|
|
// NewBinaryConvRequestFull creates a new BinaryConvRequest with finalizer
|
|
// for response BinaryResponse.
|
|
func NewBinaryConvRequestFull(ptr BinaryPointer, finalizer BinaryFinalizer,
|
|
responseFinalizer BinaryFinalizer) *BinaryConvRequest {
|
|
b := &BinaryConvRequest{finalizer: finalizer, responseFinalizer: responseFinalizer}
|
|
b.ptr.Store(uintptr(ptr))
|
|
if ptr == nil || finalizer == nil {
|
|
return b
|
|
}
|
|
|
|
// The ownership of the data here is temporary
|
|
runtime.SetFinalizer(b, func(b *BinaryConvRequest) { b.Release() })
|
|
return b
|
|
}
|
|
|
|
// NewBinaryConvRequest creates a new BinaryConvRequest
|
|
func NewBinaryConvRequest(ptr BinaryPointer, finalizer BinaryFinalizer) *BinaryConvRequest {
|
|
return NewBinaryConvRequestFull(ptr, finalizer, finalizer)
|
|
}
|
|
|
|
// NewBinaryConvRequestFromBytes creates a new BinaryConvRequest from an array
|
|
// of bytes.
|
|
func NewBinaryConvRequestFromBytes(bytes []byte) *BinaryConvRequest {
|
|
if bytes == nil {
|
|
return &BinaryConvRequest{}
|
|
}
|
|
return NewBinaryConvRequest(BinaryPointer(C.CBytes(bytes)),
|
|
func(ptr BinaryPointer) { C.free(unsafe.Pointer(ptr)) })
|
|
}
|
|
|
|
// Style returns the response style for the request, so always BinaryPrompt.
|
|
func (b *BinaryConvRequest) Style() Style {
|
|
return BinaryPrompt
|
|
}
|
|
|
|
// Pointer returns the conversation style of the StringConvRequest.
|
|
func (b *BinaryConvRequest) Pointer() BinaryPointer {
|
|
ptr := b.ptr.Load()
|
|
return *(*BinaryPointer)(unsafe.Pointer(&ptr))
|
|
}
|
|
|
|
// CreateResponse creates a new BinaryConvResponse from the request
|
|
func (b *BinaryConvRequest) CreateResponse(ptr BinaryPointer) BinaryConvResponse {
|
|
bcr := &binaryConvResponse{ptr, b.responseFinalizer, &sync.Mutex{}}
|
|
runtime.SetFinalizer(bcr, func(bcr *binaryConvResponse) {
|
|
bcr.Release()
|
|
})
|
|
return bcr
|
|
}
|
|
|
|
// Release releases the resources allocated by the request
|
|
func (b *BinaryConvRequest) Release() {
|
|
ptr := b.ptr.Swap(0)
|
|
if b.finalizer != nil {
|
|
b.finalizer(*(*BinaryPointer)(unsafe.Pointer(&ptr)))
|
|
runtime.SetFinalizer(b, nil)
|
|
}
|
|
}
|
|
|
|
// BinaryDecoder is a function type for decode the a binary pointer data into
|
|
// bytes
|
|
type BinaryDecoder func(BinaryPointer) ([]byte, error)
|
|
|
|
// BinaryConvResponse is a subtype of ConvResponse used for binary
|
|
// conversation responses.
|
|
type BinaryConvResponse interface {
|
|
ConvResponse
|
|
Data() BinaryPointer
|
|
Decode(BinaryDecoder) ([]byte, error)
|
|
Release()
|
|
}
|
|
|
|
type binaryConvResponse struct {
|
|
ptr BinaryPointer
|
|
finalizer BinaryFinalizer
|
|
mutex *sync.Mutex
|
|
}
|
|
|
|
// Style returns the response style for the response, so always BinaryPrompt.
|
|
func (b binaryConvResponse) Style() Style {
|
|
return BinaryPrompt
|
|
}
|
|
|
|
// Data returns the response native pointer, it's up to the protocol to parse
|
|
// it accordingly.
|
|
func (b *binaryConvResponse) Data() BinaryPointer {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
return b.ptr
|
|
}
|
|
|
|
// Decode decodes the binary data using the provided decoder function.
|
|
func (b *binaryConvResponse) Decode(decoder BinaryDecoder) (
|
|
[]byte, error) {
|
|
if decoder == nil {
|
|
return nil, errors.New("nil decoder provided")
|
|
}
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
return decoder(b.ptr)
|
|
}
|
|
|
|
// Release releases the binary conversation response data.
|
|
// This is also automatically via a finalizer, but applications may control
|
|
// this explicitly deferring execution of this.
|
|
func (b *binaryConvResponse) Release() {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
ptr := b.ptr
|
|
b.ptr = nil
|
|
if b.finalizer != nil {
|
|
b.finalizer(ptr)
|
|
} else {
|
|
C.free(unsafe.Pointer(ptr))
|
|
}
|
|
}
|
|
|
|
// StartStringConv starts a text-based conversation using the provided style
|
|
// and prompt.
|
|
func (m *moduleTransaction) StartStringConv(style Style, prompt string) (
|
|
StringConvResponse, error) {
|
|
return m.startStringConvImpl(m, style, prompt)
|
|
}
|
|
|
|
func (m *moduleTransaction) startStringConvImpl(iface moduleTransactionIface,
|
|
style Style, prompt string) (
|
|
StringConvResponse, error) {
|
|
switch style {
|
|
case BinaryPrompt:
|
|
return nil, fmt.Errorf("%w: binary style is not supported", ErrConv)
|
|
}
|
|
|
|
res, err := m.startConvImpl(iface, NewStringConvRequest(style, prompt))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stringRes, _ := res.(stringConvResponse)
|
|
return stringRes, nil
|
|
}
|
|
|
|
// StartStringConvf allows to start string conversation with formatting support.
|
|
func (m *moduleTransaction) StartStringConvf(style Style, format string, args ...interface{}) (
|
|
StringConvResponse, error) {
|
|
return m.StartStringConv(style, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// HasBinaryProtocol checks if binary protocol is supported.
|
|
func (m *moduleTransaction) hasBinaryProtocol() bool {
|
|
return CheckPamHasBinaryProtocol()
|
|
}
|
|
|
|
// StartBinaryConv starts a binary conversation using the provided bytes.
|
|
func (m *moduleTransaction) StartBinaryConv(bytes []byte) (
|
|
BinaryConvResponse, error) {
|
|
return m.startBinaryConvImpl(m, bytes)
|
|
}
|
|
|
|
func (m *moduleTransaction) startBinaryConvImpl(iface moduleTransactionIface,
|
|
bytes []byte) (
|
|
BinaryConvResponse, error) {
|
|
res, err := m.startConvImpl(iface, NewBinaryConvRequestFromBytes(bytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
binaryRes, _ := res.(BinaryConvResponse)
|
|
return binaryRes, nil
|
|
}
|
|
|
|
// StartConv initiates a PAM conversation using the provided ConvRequest.
|
|
func (m *moduleTransaction) StartConv(req ConvRequest) (
|
|
ConvResponse, error) {
|
|
return m.startConvImpl(m, req)
|
|
}
|
|
|
|
func (m *moduleTransaction) startConvImpl(iface moduleTransactionIface, req ConvRequest) (
|
|
ConvResponse, error) {
|
|
resp, err := m.startConvMultiImpl(iface, []ConvRequest{req})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(resp) != 1 {
|
|
return nil, fmt.Errorf("%w: not enough values returned", ErrConv)
|
|
}
|
|
return resp[0], nil
|
|
}
|
|
|
|
func (m *moduleTransaction) startConv(conv *C.struct_pam_conv, nMsg C.int,
|
|
messages **C.struct_pam_message, outResponses **C.struct_pam_response) C.int {
|
|
return C.start_pam_conv(conv, nMsg, messages, outResponses)
|
|
}
|
|
|
|
// startConvMultiImpl is the implementation for GetData for testing purposes.
|
|
func (m *moduleTransaction) startConvMultiImpl(iface moduleTransactionIface,
|
|
requests []ConvRequest) (responses []ConvResponse, err error) {
|
|
defer func() {
|
|
if err == nil {
|
|
_ = m.handlePamStatus(success)
|
|
return
|
|
}
|
|
var pamErr Error
|
|
if !errors.As(err, &pamErr) {
|
|
err = errors.Join(ErrConv, err)
|
|
pamErr = ErrConv
|
|
}
|
|
_ = m.handlePamStatus(C.int(pamErr))
|
|
}()
|
|
|
|
if len(requests) == 0 {
|
|
return nil, errors.New("no requests defined")
|
|
}
|
|
if len(requests) > maxNumMsg {
|
|
return nil, errors.New("too many requests")
|
|
}
|
|
|
|
conv, err := iface.getConv()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if conv == nil || conv.conv == nil {
|
|
return nil, errors.New("impossible to find conv handler")
|
|
}
|
|
|
|
// FIXME: Just use make([]C.struct_pam_message, 0, len(requests))
|
|
// and append, when it's possible to use runtime.Pinner
|
|
var cMessagePtr *C.struct_pam_message
|
|
cMessages := (**C.struct_pam_message)(C.calloc(C.size_t(len(requests)),
|
|
(C.size_t)(unsafe.Sizeof(cMessagePtr))))
|
|
defer C.free(unsafe.Pointer(cMessages))
|
|
goMsgs := unsafe.Slice(cMessages, len(requests))
|
|
|
|
for i, req := range requests {
|
|
var cBytes unsafe.Pointer
|
|
switch r := req.(type) {
|
|
case StringConvRequest:
|
|
cBytes = unsafe.Pointer(C.CString(r.Prompt()))
|
|
defer C.free(cBytes)
|
|
case BinaryConvRequester:
|
|
if !iface.hasBinaryProtocol() {
|
|
return nil, errors.New("%w: binary protocol is not supported")
|
|
}
|
|
cBytes = unsafe.Pointer(r.Pointer())
|
|
default:
|
|
return nil, fmt.Errorf("unsupported conversation type %#v", r)
|
|
}
|
|
|
|
cMessage := (*C.struct_pam_message)(C.calloc(1,
|
|
(C.size_t)(unsafe.Sizeof(*goMsgs[i]))))
|
|
defer C.free(unsafe.Pointer(cMessage))
|
|
cMessage.msg_style = C.int(req.Style())
|
|
cMessage.msg = (*C.char)(cBytes)
|
|
goMsgs[i] = cMessage
|
|
}
|
|
|
|
if m.convMutex != nil {
|
|
m.convMutex.Lock()
|
|
defer m.convMutex.Unlock()
|
|
}
|
|
var cResponses *C.struct_pam_response
|
|
ret := iface.startConv(conv, C.int(len(requests)), cMessages, &cResponses)
|
|
if ret != success {
|
|
return nil, Error(ret)
|
|
}
|
|
|
|
goResponses := unsafe.Slice(cResponses, len(requests))
|
|
defer func() {
|
|
for i, resp := range goResponses {
|
|
if resp.resp == nil {
|
|
continue
|
|
}
|
|
switch req := requests[i].(type) {
|
|
case BinaryConvRequester:
|
|
// In the binary prompt case, we need to rely on the provided
|
|
// finalizer to release the response, so let's create a new one.
|
|
req.CreateResponse(BinaryPointer(resp.resp)).Release()
|
|
default:
|
|
C.free(unsafe.Pointer(resp.resp))
|
|
}
|
|
}
|
|
C.free(unsafe.Pointer(cResponses))
|
|
}()
|
|
|
|
responses = make([]ConvResponse, 0, len(requests))
|
|
for i, resp := range goResponses {
|
|
request := requests[i]
|
|
msgStyle := request.Style()
|
|
switch msgStyle {
|
|
case PromptEchoOff:
|
|
fallthrough
|
|
case PromptEchoOn:
|
|
fallthrough
|
|
case ErrorMsg:
|
|
fallthrough
|
|
case TextInfo:
|
|
responses = append(responses, stringConvResponse{
|
|
style: msgStyle,
|
|
response: C.GoString(resp.resp),
|
|
})
|
|
case BinaryPrompt:
|
|
// Let's steal the resp ownership here, so that the request
|
|
// finalizer won't act on it.
|
|
bcr, _ := request.(BinaryConvRequester)
|
|
resp := bcr.CreateResponse(BinaryPointer(resp.resp))
|
|
goResponses[i].resp = nil
|
|
responses = append(responses, resp)
|
|
default:
|
|
return nil,
|
|
fmt.Errorf("unsupported conversation type %v", msgStyle)
|
|
}
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
// StartConvMulti initiates a PAM conversation with multiple ConvRequest's.
|
|
func (m *moduleTransaction) StartConvMulti(requests []ConvRequest) (
|
|
[]ConvResponse, error) {
|
|
return m.startConvMultiImpl(m, requests)
|
|
}
|