Skip to content

Commit

Permalink
module-transaction: Add support for binary conversations
Browse files Browse the repository at this point in the history
A module can now initiate a binary conversation decoding the native
pointer value as it wants.

Added tests to verify the main cases
  • Loading branch information
3v1n0 committed Oct 5, 2023
1 parent fbde23c commit cb22d46
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ func (m *integrationTesterModule) handleRequest(authReq *authRequest, r *Request
case SerializableStringConvRequest:
args = append(args, reflect.ValueOf(
pam.NewStringConvRequest(v.Style, v.Request)))
case SerializableBinaryConvRequest:
args = append(args, reflect.ValueOf(
pam.NewBinaryConvRequestFromBytes(v.Request)))
default:
if arg == nil {
args = append(args, reflect.Zero(method.Type().In(i)))
Expand All @@ -83,6 +86,24 @@ func (m *integrationTesterModule) handleRequest(authReq *authRequest, r *Request
} else {
res.ActionArgs = append(res.ActionArgs, nil)
}
case pam.BinaryConvResponse:
data, err := value.Decode(utils.TestBinaryDataDecoder)
if err != nil {
return nil, err
}
res.ActionArgs = append(res.ActionArgs,
SerializableBinaryConvResponse{data})
case *pam.BinaryConvResponse:
if value != nil {
data, err := value.Decode(utils.TestBinaryDataDecoder)
if err != nil {
return nil, err
}
res.ActionArgs = append(res.ActionArgs,
SerializableBinaryConvResponse{data})
} else {
res.ActionArgs = append(res.ActionArgs, nil)
}
case pam.ReturnType:
authReq.lastError = value
res.ActionArgs = append(res.ActionArgs, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,70 @@ func Test_Moduler_IntegrationTesterModule(t *testing.T) {
},
},
},
"start-conv-binary": {
expectedStatus: pam.Success,
credentials: utils.NewBinaryTransactionWithData([]byte(
"\x00This is a binary data request\xC5\x00\xffYes it is!"),
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffYes it is!")),
}),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
{
r: NewRequest("StartBinaryConv",
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffYes it is!"))),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
},
},
"start-conv-binary-in-nil": {
expectedStatus: pam.Success,
credentials: utils.NewBinaryTransactionWithData(nil,
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{}),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
{
r: NewRequest("StartBinaryConv", nil),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
},
},
"start-conv-binary-out-nil": {
expectedStatus: pam.Success,
credentials: utils.NewBinaryTransactionWithData([]byte(
"\x00This is a binary data request\xC5\x00\xffGimme nil!"), nil),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffGimme nil!")),
}),
exp: []interface{}{SerializableBinaryConvResponse{}, nil},
},
{
r: NewRequest("StartBinaryConv",
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffGimme nil!"))),
exp: []interface{}{SerializableBinaryConvResponse{}, nil},
},
},
},
}

for name, tc := range tests {
Expand Down Expand Up @@ -1112,6 +1176,15 @@ func Test_Moduler_IntegrationTesterModule_Authenticate(t *testing.T) {
exp: []interface{}{nil, pam.SystemErr},
}},
},
"StartConv-Binary": {
expectedStatus: pam.SystemErr,
checkedRequests: []checkedRequest{{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}),
exp: []interface{}{nil, pam.SystemErr},
}},
},
}

for name, tc := range tests {
Expand Down
12 changes: 12 additions & 0 deletions cmd/pam-moduler/tests/integration-tester-module/serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ type SerializableStringConvResponse struct {
Response string
}

type SerializableBinaryConvRequest struct {
Request []byte
}

type SerializableBinaryConvResponse struct {
Response []byte
}

func init() {
gob.Register(map[string]string{})
gob.Register(Request{})
Expand All @@ -42,5 +50,9 @@ func init() {
SerializableStringConvRequest{})
gob.RegisterName("main.SerializableStringConvResponse",
SerializableStringConvResponse{})
gob.RegisterName("main.SerializableBinaryConvRequest",
SerializableBinaryConvRequest{})
gob.RegisterName("main.SerializableBinaryConvResponse",
SerializableBinaryConvResponse{})
gob.Register(utils.SerializableError{})
}
80 changes: 80 additions & 0 deletions cmd/pam-moduler/tests/internal/utils/test-utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package utils

//#include <stdint.h>
import "C"

import (
"crypto/rand"
"encoding/binary"
"fmt"
"reflect"
"unsafe"

"github.com/msteinert/pam"
)
Expand Down Expand Up @@ -148,3 +155,76 @@ func (c Credentials) RespondPAM(s pam.Style, msg string) (string, error) {
return "", pam.NewTransactionError(
&SerializableError{fmt.Sprintf("unhandled style: %v", s)}, pam.ConvErr)
}

type BinaryTransaction struct {
data []byte
ExpectedNull bool
ReturnedData []byte
}

func TestBinaryDataEncoder(bytes []byte) []byte {
if len(bytes) > 0xff {
panic("Binary transaction size not supported")
}

data := make([]byte, 0, len(bytes)+1)
data = append(data, byte(len(bytes)))
data = append(data, bytes...)
return data
}

func TestBinaryDataDecoder(ptr pam.BinaryPointer) ([]byte, error) {
if ptr == nil {
return nil, nil
}

length := uint8(*((*C.uint8_t)(ptr)))
return C.GoBytes(unsafe.Pointer(ptr), C.int(length+1))[1:], nil
}

func NewBinaryTransactionWithData(data []byte, retData []byte) BinaryTransaction {
t := BinaryTransaction{ReturnedData: retData}
t.data = TestBinaryDataEncoder(data)
t.ExpectedNull = data == nil
return t
}

func NewBinaryTransactionWithRandomData(size uint8, retData []byte) BinaryTransaction {
t := BinaryTransaction{ReturnedData: retData}
randomData := make([]byte, size)
if err := binary.Read(rand.Reader, binary.LittleEndian, &randomData); err != nil {
panic(err)
}

t.data = TestBinaryDataEncoder(randomData)
return t
}

func (b BinaryTransaction) Data() []byte {
return b.data
}

func (b BinaryTransaction) RespondPAM(s pam.Style, msg string) (string, error) {
return "", pam.NewTransactionError(
&SerializableError{"unexpected non-binary request"}, pam.ConvErr)
}

func (b BinaryTransaction) RespondPAMBinary(ptr pam.BinaryPointer) ([]byte, error) {
if ptr == nil && !b.ExpectedNull {
return nil, pam.NewTransactionError(
&SerializableError{"unexpected null binary data"}, pam.ConvErr)
} else if ptr == nil {
return TestBinaryDataEncoder(b.ReturnedData), nil
}

bytes, _ := TestBinaryDataDecoder(ptr)
if !reflect.DeepEqual(bytes, b.data[1:]) {
return nil, pam.NewTransactionError(
&SerializableError{
fmt.Sprintf("data mismatch %v vs %v", bytes, b.data[1:]),
},
pam.ConvErr)
}

return TestBinaryDataEncoder(b.ReturnedData), nil
}
81 changes: 81 additions & 0 deletions module-transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "C"
import (
"errors"
"fmt"
"runtime"
"runtime/cgo"
"sync/atomic"
"unsafe"
Expand Down Expand Up @@ -204,6 +205,60 @@ func (s StringConvResponse) Response() string {
return s.response
}

// StringConvRequest is a ConvRequest for performing binary conversations
type BinaryConvRequest struct {
ptr BinaryPointer
}

// NewBinaryConvRequest creates a new BinaryConvRequest
func NewBinaryConvRequest(ptr BinaryPointer) BinaryConvRequest {
return BinaryConvRequest{ptr}
}

// NewBinaryConvRequest creates a new BinaryConvRequest from an array
// pointer, the lifetime of the data is bound to the object
func NewBinaryConvRequestFromBytes(bytes []byte) BinaryConvRequest {
m := BinaryConvRequest{}
if bytes != nil {
m.ptr = BinaryPointer(C.CBytes(bytes))
runtime.SetFinalizer(&m, func(m *BinaryConvRequest) {
defer C.free(unsafe.Pointer(m.ptr))
})
}
return m
}

// Style returns the response style for the request, so always BinaryPrompt
func (b BinaryConvRequest) Style() Style {
return BinaryPrompt
}

// StringConvResponse is a ConvResponse implementation used for binary
// conversation responses
type BinaryConvResponse struct {
ptr BinaryPointer
}

// 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 {
return b.ptr
}

// Decode decodes the binary data using the provided decoder function
func (b BinaryConvResponse) Decode(decoder func(BinaryPointer) ([]byte, error)) (
[]byte, error) {
if decoder == nil {
errors.New("nil decoder provided")
}
return decoder(b.ptr)
}

// StartStringConv starts a text-based conversation using the provided style
// and prompt
func (m *ModuleTransaction) StartStringConv(style Style, prompt string) (
Expand All @@ -229,6 +284,18 @@ func (m *ModuleTransaction) StartStringConvf(style Style, format string, args ..
return m.StartStringConv(style, fmt.Sprintf(format, args...))
}

// StartBinaryConv starts a binary conversation using the provided bytes
func (m *ModuleTransaction) StartBinaryConv(bytes []byte) (
*BinaryConvResponse, error) {
res, err := m.StartConv(NewBinaryConvRequestFromBytes(bytes))
if err != nil {
return nil, err
}

stringRes := res.(BinaryConvResponse)
return &stringRes, nil
}

// StartConv initiates a PAM conversation using the provided ConvRequest
func (m *ModuleTransaction) StartConv(req ConvRequest) (
ConvResponse, error) {
Expand Down Expand Up @@ -273,6 +340,8 @@ func (m *ModuleTransaction) StartConvMulti(requests []ConvRequest) (
case StringConvRequest:
cBytes = unsafe.Pointer(C.CString(r.prompt))
defer C.free(unsafe.Pointer(cBytes))
case BinaryConvRequest:
cBytes = unsafe.Pointer(r.ptr)
default:
return nil, NewTransactionError(
fmt.Errorf(
Expand All @@ -293,6 +362,14 @@ func (m *ModuleTransaction) StartConvMulti(requests []ConvRequest) (
case StringConvRequest:
cBytes = unsafe.Pointer(C.CString(r.prompt))
defer C.free(unsafe.Pointer(cBytes))
case BinaryConvRequest:
if !CheckPamHasBinaryProtocol() {
return nil, NewTransactionError(
fmt.Errorf(
"binary protocol is not supported"),
ConvErr)
}
cBytes = unsafe.Pointer(r.ptr)
default:
return nil, NewTransactionError(
fmt.Errorf(
Expand Down Expand Up @@ -328,6 +405,10 @@ func (m *ModuleTransaction) StartConvMulti(requests []ConvRequest) (
style: msgStyle,
response: C.GoString(resp.resp),
})
case BinaryPrompt:
fmt.Println("Setting binary reply to", resp.resp)
goReplies = append(goReplies, BinaryConvResponse{
BinaryPointer(resp.resp)})
default:
atomic.StoreInt32(&m.status, int32(ConvErr))
return nil, NewTransactionError(
Expand Down
2 changes: 2 additions & 0 deletions module-transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ func Test_NewNullModuleTransaction(t *testing.T) {
return mt.StartConvMulti([]ConvRequest{
NewStringConvRequest(TextInfo, "a prompt"),
NewStringConvRequest(ErrorMsg, "another prompt"),
NewBinaryConvRequest(BinaryPointer(&mt)),
NewBinaryConvRequestFromBytes([]byte("These are bytes!")),
})
},
},
Expand Down

0 comments on commit cb22d46

Please sign in to comment.