Modules have the ability to start PAM conversations, so while the transaction code can handle them we did not have a way to init them. Yet. So add some APIs allowing this, making it easier from the go side to handle the conversations. In this commit we only support text-based conversations, but code is designed with the idea of supporting binary cases too. Added the integration tests using the module that is now able to both start conversation and handle them using Go only.
170 lines
3.5 KiB
Go
170 lines
3.5 KiB
Go
// Package utils contains the internal test utils
|
|
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/msteinert/pam/v2"
|
|
)
|
|
|
|
// Action represents a PAM action to perform.
|
|
type Action int
|
|
|
|
const (
|
|
// Account is the account.
|
|
Account Action = iota + 1
|
|
// Auth is the auth.
|
|
Auth
|
|
// Password is the password.
|
|
Password
|
|
// Session is the session.
|
|
Session
|
|
)
|
|
|
|
func (a Action) String() string {
|
|
switch a {
|
|
case Account:
|
|
return "account"
|
|
case Auth:
|
|
return "auth"
|
|
case Password:
|
|
return "password"
|
|
case Session:
|
|
return "session"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Actions is a map with all the available Actions by their name.
|
|
var Actions = map[string]Action{
|
|
Account.String(): Account,
|
|
Auth.String(): Auth,
|
|
Password.String(): Password,
|
|
Session.String(): Session,
|
|
}
|
|
|
|
// Control represents how a PAM module should controlled in PAM service file.
|
|
type Control int
|
|
|
|
const (
|
|
// Required implies that the module is required.
|
|
Required Control = iota + 1
|
|
// Requisite implies that the module is requisite.
|
|
Requisite
|
|
// Sufficient implies that the module is sufficient.
|
|
Sufficient
|
|
// Optional implies that the module is optional.
|
|
Optional
|
|
)
|
|
|
|
func (c Control) String() string {
|
|
switch c {
|
|
case Required:
|
|
return "required"
|
|
case Requisite:
|
|
return "requisite"
|
|
case Sufficient:
|
|
return "sufficient"
|
|
case Optional:
|
|
return "optional"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// ServiceLine is the representation of a PAM module service file line.
|
|
type ServiceLine struct {
|
|
Action Action
|
|
Control Control
|
|
Module string
|
|
Args []string
|
|
}
|
|
|
|
// FallBackModule is a type to represent the module that should be used as fallback.
|
|
type FallBackModule int
|
|
|
|
const (
|
|
// NoFallback add no fallback module.
|
|
NoFallback FallBackModule = iota + 1
|
|
// Permit uses a module that always permits.
|
|
Permit
|
|
// Deny uses a module that always denys.
|
|
Deny
|
|
)
|
|
|
|
func (a FallBackModule) String() string {
|
|
switch a {
|
|
case Permit:
|
|
return "pam_permit.so"
|
|
case Deny:
|
|
return "pam_deny.so"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// SerializableError is a representation of an error in a way can be serialized.
|
|
type SerializableError struct {
|
|
Msg string
|
|
}
|
|
|
|
func (e *SerializableError) Error() string {
|
|
return e.Msg
|
|
}
|
|
|
|
// Credentials is a test [pam.ConversationHandler] implementation.
|
|
type Credentials struct {
|
|
User string
|
|
Password string
|
|
EchoOn string
|
|
EchoOff string
|
|
TextInfo string
|
|
ErrorMsg string
|
|
ExpectedMessage string
|
|
CheckEmptyMessage bool
|
|
ExpectedStyle pam.Style
|
|
CheckZeroStyle bool
|
|
Context interface{}
|
|
}
|
|
|
|
// RespondPAM handles PAM string conversations.
|
|
func (c Credentials) RespondPAM(s pam.Style, msg string) (string, error) {
|
|
if (c.ExpectedMessage != "" || c.CheckEmptyMessage) &&
|
|
msg != c.ExpectedMessage {
|
|
return "", errors.Join(pam.ErrConv,
|
|
&SerializableError{
|
|
fmt.Sprintf("unexpected prompt: %s vs %s", msg, c.ExpectedMessage),
|
|
})
|
|
}
|
|
|
|
if (c.ExpectedStyle != 0 || c.CheckZeroStyle) &&
|
|
s != c.ExpectedStyle {
|
|
return "", errors.Join(pam.ErrConv,
|
|
&SerializableError{
|
|
fmt.Sprintf("unexpected style: %#v vs %#v", s, c.ExpectedStyle),
|
|
})
|
|
}
|
|
|
|
switch s {
|
|
case pam.PromptEchoOn:
|
|
if c.User != "" {
|
|
return c.User, nil
|
|
}
|
|
return c.EchoOn, nil
|
|
case pam.PromptEchoOff:
|
|
if c.Password != "" {
|
|
return c.Password, nil
|
|
}
|
|
return c.EchoOff, nil
|
|
case pam.TextInfo:
|
|
return c.TextInfo, nil
|
|
case pam.ErrorMsg:
|
|
return c.ErrorMsg, nil
|
|
}
|
|
|
|
return "", errors.Join(pam.ErrConv,
|
|
&SerializableError{fmt.Sprintf("unhandled style: %v", s)})
|
|
}
|