Skip to content

Commit 45e7096

Browse files
committed
pam: Add PamModuleTransaction interface definition
This is a base interface that the PamModule transaction should implement and so use and pass this interface instead of relying on the actual module transaction implementation, so that we can mock it. As per this introduce a new dummy implementation for it that can be used in local tests
1 parent eca0daa commit 45e7096

2 files changed

Lines changed: 300 additions & 17 deletions

File tree

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package pam_test
2+
3+
import "C"
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
"strings"
9+
"unsafe"
10+
11+
"github.com/msteinert/pam"
12+
)
13+
14+
// ModuleTransactionDummy is an implementation of PamModuleTransaction for
15+
// testing purposes.
16+
type ModuleTransactionDummy struct {
17+
Items map[pam.Item]string
18+
Env map[string]string
19+
Data map[string]any
20+
convHandler pam.ConversationHandler
21+
}
22+
23+
// NewModuleTransactionDummy returns a new PamModuleTransactionDummy.
24+
func NewModuleTransactionDummy(convHandler pam.ConversationHandler) pam.ModuleTransaction {
25+
return &ModuleTransactionDummy{
26+
convHandler: convHandler,
27+
}
28+
}
29+
30+
// InvokeHandler is called by the C code to invoke the proper handler.
31+
func (m *ModuleTransactionDummy) InvokeHandler(handler pam.ModuleHandlerFunc,
32+
flags pam.Flags, args []string) (pam.Status, error) {
33+
return pam.Abort, pam.Abort
34+
}
35+
36+
// SetItem sets a PAM information item.
37+
func (m *ModuleTransactionDummy) SetItem(item pam.Item, value string) error {
38+
if m.Items == nil {
39+
m.Items = make(map[pam.Item]string)
40+
}
41+
42+
m.Items[item] = value
43+
return nil
44+
}
45+
46+
// GetItem retrieves a PAM information item.
47+
func (m *ModuleTransactionDummy) GetItem(item pam.Item) (string, error) {
48+
return m.Items[item], nil
49+
}
50+
51+
// PutEnv adds or changes the value of PAM environment variables.
52+
//
53+
// NAME=value will set a variable to a value.
54+
// NAME= will set a variable to an empty value.
55+
// NAME (without an "=") will delete a variable.
56+
func (m *ModuleTransactionDummy) PutEnv(nameVal string) error {
57+
if m.Env == nil {
58+
m.Env = make(map[string]string)
59+
}
60+
61+
env, value, found := strings.Cut(nameVal, "=")
62+
if !found {
63+
delete(m.Env, env)
64+
return nil
65+
}
66+
if env == "" {
67+
return pam.BadItem
68+
}
69+
m.Env[env] = value
70+
return nil
71+
}
72+
73+
// GetEnv is used to retrieve a PAM environment variable.
74+
func (m *ModuleTransactionDummy) GetEnv(name string) string {
75+
return m.Env[name]
76+
}
77+
78+
// GetEnvList returns a copy of the PAM environment as a map.
79+
func (m *ModuleTransactionDummy) GetEnvList() (map[string]string, error) {
80+
if m.Env == nil {
81+
m.Env = make(map[string]string)
82+
}
83+
return m.Env, nil
84+
}
85+
86+
// GetUser is similar to GetItem(User), but it would start a conversation if
87+
// no user is currently set in PAM.
88+
func (m *ModuleTransactionDummy) GetUser(prompt string) (string, error) {
89+
if user, err := m.GetItem(pam.User); err != nil {
90+
return "", err
91+
} else if user != "" {
92+
return user, nil
93+
}
94+
95+
resp, err := m.StartStringConv(pam.PromptEchoOn, prompt)
96+
if err != nil {
97+
return "", err
98+
}
99+
100+
return resp.Response(), nil
101+
}
102+
103+
// SetData allows to save any value in the module data that is preserved
104+
// during the whole time the module is loaded.
105+
func (m *ModuleTransactionDummy) SetData(key string, data any) error {
106+
if m.Data == nil {
107+
m.Data = make(map[string]any)
108+
}
109+
110+
m.Data[key] = data
111+
return nil
112+
}
113+
114+
// GetData allows to get any value from the module data saved using SetData
115+
// that is preserved across the whole time the module is loaded.
116+
func (m *ModuleTransactionDummy) GetData(key string) (any, error) {
117+
data, found := m.Data[key]
118+
if !found {
119+
return nil, pam.NoModuleData
120+
}
121+
return data, nil
122+
}
123+
124+
// StartStringConv starts a text-based conversation using the provided style
125+
// and prompt.
126+
func (m *ModuleTransactionDummy) StartStringConv(style pam.Style, prompt string) (
127+
pam.StringConvResponse, error) {
128+
if style == pam.BinaryPrompt {
129+
return nil, pam.NewTransactionError(errors.New("Binary style is not supported"),
130+
pam.ConvErr)
131+
}
132+
133+
res, err := m.StartConv(pam.NewStringConvRequest(style, prompt))
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
stringRes, ok := res.(pam.StringConvResponse)
139+
if !ok {
140+
return nil, pam.NewTransactionError(errors.New("Can't convert to pam.StringConvResponse"), pam.ConvErr)
141+
}
142+
return stringRes, nil
143+
}
144+
145+
// StartStringConvf allows to start string conversation with formatting support.
146+
func (m *ModuleTransactionDummy) StartStringConvf(style pam.Style, format string, args ...interface{}) (
147+
pam.StringConvResponse, error) {
148+
return m.StartStringConv(style, fmt.Sprintf(format, args...))
149+
}
150+
151+
// StartBinaryConv starts a binary conversation using the provided bytes.
152+
func (m *ModuleTransactionDummy) StartBinaryConv(bytes []byte) (
153+
pam.BinaryConvResponse, error) {
154+
res, err := m.StartConv(pam.NewBinaryConvRequestFromBytes(bytes))
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
binaryRes, ok := res.(pam.BinaryConvResponse)
160+
if !ok {
161+
return nil, pam.NewTransactionError(
162+
errors.New("Can't convert to pam.BinaryConvResponse"), pam.ConvErr)
163+
}
164+
return binaryRes, nil
165+
}
166+
167+
// StartConv initiates a PAM conversation using the provided ConvRequest.
168+
func (m *ModuleTransactionDummy) StartConv(req pam.ConvRequest) (
169+
pam.ConvResponse, error) {
170+
resp, err := m.StartConvMulti([]pam.ConvRequest{req})
171+
if err != nil {
172+
return nil, err
173+
}
174+
if len(resp) != 1 {
175+
return nil, pam.NewTransactionError(errors.New("not enough values returned"),
176+
pam.ConvErr)
177+
}
178+
return resp[0], nil
179+
}
180+
181+
// StartConvMulti initiates a PAM conversation with multiple ConvRequest's.
182+
func (m *ModuleTransactionDummy) StartConvMulti(requests []pam.ConvRequest) (
183+
[]pam.ConvResponse, error) {
184+
if len(requests) == 0 {
185+
return nil, pam.NewTransactionError(errors.New("no requests defined"),
186+
pam.ConvErr)
187+
}
188+
189+
goReplies := make([]pam.ConvResponse, 0, len(requests))
190+
for _, req := range requests {
191+
msgStyle := req.Style()
192+
switch msgStyle {
193+
case pam.PromptEchoOff:
194+
fallthrough
195+
case pam.PromptEchoOn:
196+
fallthrough
197+
case pam.ErrorMsg:
198+
fallthrough
199+
case pam.TextInfo:
200+
if m.convHandler == nil {
201+
return nil, pam.NewTransactionError(
202+
fmt.Errorf(
203+
"no conversation handler provided for style %v", msgStyle),
204+
pam.ConvErr)
205+
}
206+
reply, err := m.convHandler.RespondPAM(msgStyle,
207+
req.(pam.StringConvRequest).Prompt())
208+
if err != nil {
209+
return nil, err
210+
}
211+
goReplies = append(goReplies, StringResponseDummy{
212+
msgStyle,
213+
reply,
214+
})
215+
case pam.BinaryPrompt:
216+
handler, ok := m.convHandler.(pam.BinaryConversationHandler)
217+
if handler == nil || !ok {
218+
return nil, pam.NewTransactionError(
219+
errors.New("no binary handler provided"),
220+
pam.ConvErr)
221+
}
222+
reply, err := handler.RespondPAMBinary(
223+
req.(pam.BinaryConvRequest).Pointer())
224+
if err != nil {
225+
return nil, err
226+
}
227+
goReplies = append(goReplies,
228+
BinaryResponseDummy{pam.BinaryPointer(&reply)})
229+
default:
230+
return nil, pam.NewTransactionError(
231+
fmt.Errorf(
232+
"unsupported conversation type %v", msgStyle),
233+
pam.ConvErr)
234+
}
235+
}
236+
237+
return goReplies, nil
238+
}
239+
240+
func getBinaryFromPointer(ptr pam.BinaryPointer, length int) []byte {
241+
return C.GoBytes(unsafe.Pointer(ptr), C.int(length))
242+
}
243+
244+
// StringResponseDummy is a simple implementation of pam.StringConvResponse.
245+
type StringResponseDummy struct {
246+
style pam.Style
247+
content string
248+
}
249+
250+
// Style returns the conversation style of the DummyStringResponse.
251+
func (s StringResponseDummy) Style() pam.Style {
252+
return s.style
253+
}
254+
255+
// Response returns the string response of the DummyStringResponse.
256+
func (s StringResponseDummy) Response() string {
257+
return s.content
258+
}
259+
260+
// BinaryResponseDummy is an implementation of pam.BinaryConvResponse.
261+
type BinaryResponseDummy struct {
262+
ptr pam.BinaryPointer
263+
}
264+
265+
// Style returns the response style for the response, so always BinaryPrompt.
266+
func (b BinaryResponseDummy) Style() pam.Style {
267+
return pam.BinaryPrompt
268+
}
269+
270+
// Data returns the response native pointer, it's up to the protocol to parse
271+
// it accordingly.
272+
func (b BinaryResponseDummy) Data() pam.BinaryPointer {
273+
return b.ptr
274+
}
275+
276+
// Decode decodes the binary data using the provided decoder function.
277+
func (b BinaryResponseDummy) Decode(decoder pam.BinaryDecoder) (
278+
[]byte, error) {
279+
if decoder == nil {
280+
return nil, errors.New("nil decoder provided")
281+
}
282+
return decoder(b.ptr)
283+
}

0 commit comments

Comments
 (0)