diff --git a/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module.go b/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module.go index 0ca4beae..71f9d17a 100644 --- a/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module.go +++ b/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module.go @@ -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))) @@ -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) diff --git a/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module_test.go b/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module_test.go index 730e92b2..19548b1b 100644 --- a/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module_test.go +++ b/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module_test.go @@ -72,6 +72,19 @@ func ensureEnv(tx *pam.Transaction, variable string, expected string) error { } } +func (r *Request) toBytes(t *testing.T) []byte { + if bytes, err := r.GOB(); err != nil { + t.Fatalf("error: %v", err) + return nil + } else { + return bytes + } +} + +func (r *Request) toTransactionData(t *testing.T) []byte { + return utils.TestBinaryDataEncoder(r.toBytes(t)) +} + func Test_Moduler_IntegrationTesterModule(t *testing.T) { if !pam.CheckPamHasStartConfdir() { t.Skip("this requires PAM with Conf dir support") @@ -854,6 +867,107 @@ 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-handle-failure-passed-data-mismatch": { + expectedStatus: pam.ConvErr, + 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{ + (&Request{"Not the expected binary data", nil}).toTransactionData(t), + }), + exp: []interface{}{nil, pam.ConvErr}, + }, + { + r: NewRequest("StartBinaryConv", + (&Request{"Not the expected binary data", nil}).toTransactionData(t)), + exp: []interface{}{nil, pam.ConvErr}, + }, + }, + }, + "start-conv-binary-handle-failure-returned-data-mismatch": { + expectedStatus: pam.ConvErr, + credentials: utils.NewBinaryTransactionWithRandomData(100, + []byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}), + checkedRequests: []checkedRequest{ + { + r: NewRequest("StartConv", SerializableBinaryConvRequest{ + (&Request{"Wrong binary data", nil}).toTransactionData(t), + }), + exp: []interface{}{nil, pam.ConvErr}, + }, + { + r: NewRequest("StartBinaryConv", + (&Request{"Wrong binary data", nil}).toTransactionData(t)), + exp: []interface{}{nil, pam.ConvErr}, + }, + }, + }, + "start-conv-binary-in-nil": { + expectedStatus: pam.Success, + credentials: utils.NewBinaryTransactionWithData(nil, + (&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t)), + checkedRequests: []checkedRequest{ + { + r: NewRequest("StartConv", SerializableBinaryConvRequest{}), + exp: []interface{}{SerializableBinaryConvResponse{ + (&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t), + }, nil}, + }, + { + r: NewRequest("StartBinaryConv", nil), + exp: []interface{}{SerializableBinaryConvResponse{ + (&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t), + }, 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 { @@ -1158,6 +1272,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 { diff --git a/cmd/pam-moduler/tests/integration-tester-module/serialization.go b/cmd/pam-moduler/tests/integration-tester-module/serialization.go index 1faefe46..e7977211 100644 --- a/cmd/pam-moduler/tests/integration-tester-module/serialization.go +++ b/cmd/pam-moduler/tests/integration-tester-module/serialization.go @@ -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{}) @@ -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{}) } diff --git a/cmd/pam-moduler/tests/internal/utils/test-utils.go b/cmd/pam-moduler/tests/internal/utils/test-utils.go index d7a39962..a9a3db70 100644 --- a/cmd/pam-moduler/tests/internal/utils/test-utils.go +++ b/cmd/pam-moduler/tests/internal/utils/test-utils.go @@ -1,7 +1,14 @@ package utils +//#include +import "C" + import ( + "crypto/rand" + "encoding/binary" "fmt" + "reflect" + "unsafe" "github.com/msteinert/pam" ) @@ -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 +} diff --git a/module-transaction.go b/module-transaction.go index 630e6462..e234e019 100644 --- a/module-transaction.go +++ b/module-transaction.go @@ -19,6 +19,7 @@ import "C" import ( "errors" "fmt" + "runtime" "runtime/cgo" "sync/atomic" "unsafe" @@ -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(*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) ( @@ -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) { @@ -277,6 +344,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( @@ -320,6 +395,14 @@ func (m *ModuleTransaction) StartConvMulti(requests []ConvRequest) ( style: msgStyle, response: C.GoString(resp.resp), }) + case BinaryPrompt: + // Let's steal the resp ownership in this case + bcr := BinaryConvResponse{BinaryPointer(resp.resp)} + responses[i].resp = nil + runtime.SetFinalizer(&bcr, func(bcr *BinaryConvResponse) { + C.free(unsafe.Pointer(bcr.ptr)) + }) + goReplies = append(goReplies, bcr) default: atomic.StoreInt32(&m.status, int32(ConvErr)) return nil, NewTransactionError( diff --git a/module-transaction_test.go b/module-transaction_test.go index 6f0dc244..6a5c0153 100644 --- a/module-transaction_test.go +++ b/module-transaction_test.go @@ -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!")), }) }, }, diff --git a/transaction.c b/transaction.c index 9b352807..9a268070 100644 --- a/transaction.c +++ b/transaction.c @@ -30,7 +30,10 @@ int cb_pam_conv(int num_msg, PAM_CONST struct pam_message **msg, struct pam_resp error: for (size_t i = 0; i < num_msg; ++i) { if ((*resp)[i].resp) { - memset((*resp)[i].resp, 0, strlen((*resp)[i].resp)); +#ifdef PAM_BINARY_PROMPT + if (msg[i]->msg_style != PAM_BINARY_PROMPT) +#endif + memset((*resp)[i].resp, 0, strlen((*resp)[i].resp)); free((*resp)[i].resp); } }