mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
9.0 KiB
319 lines
9.0 KiB
package pam |
|
|
|
//#include <security/pam_appl.h> |
|
//#include <stdlib.h> |
|
//#cgo CFLAGS: -Wall -std=c99 |
|
//#cgo LDFLAGS: -lpam |
|
//struct pam_conv *make_pam_conv(void *); |
|
import "C" |
|
|
|
import ( |
|
"runtime" |
|
"strings" |
|
"unsafe" |
|
) |
|
|
|
// Style is the type of message that the conversation handler should display. |
|
type Style int |
|
|
|
// Coversation handler style types. |
|
const ( |
|
// PromptEchoOff indicates the conversation handler should obtain a |
|
// string without echoing any text. |
|
PromptEchoOff Style = C.PAM_PROMPT_ECHO_OFF |
|
// PromptEchoOn indicates the conversation handler should obtain a |
|
// string while echoing text. |
|
PromptEchoOn = C.PAM_PROMPT_ECHO_ON |
|
// ErrorMsg indicates the conversation handler should display an |
|
// error message. |
|
ErrorMsg = C.PAM_ERROR_MSG |
|
// TextInfo indicates the conversation handler should display some |
|
// text. |
|
TextInfo = C.PAM_TEXT_INFO |
|
) |
|
|
|
// ConversationHandler is an interface for objects that can be used as |
|
// conversation callbacks during PAM authentication. |
|
type ConversationHandler interface { |
|
// RespondPAM receives a message style and a message string. If the |
|
// message Style is PromptEchoOff or PromptEchoOn then the function |
|
// should return a response string. |
|
RespondPAM(Style, string) (string, error) |
|
} |
|
|
|
// ConversationFunc is an adapter to allow the use of ordinary functions as |
|
// conversation callbacks. |
|
type ConversationFunc func(Style, string) (string, error) |
|
|
|
// RespondPAM is a conversation callback adapter. |
|
func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) { |
|
return f(s, msg) |
|
} |
|
|
|
// Internal conversation structure |
|
type conversation struct { |
|
handler ConversationHandler |
|
conv *C.struct_pam_conv |
|
} |
|
|
|
// Constructs a new conversation object with a given handler and a newly |
|
// allocated pam_conv struct that uses this object as its appdata_ptr. |
|
func newConversation(handler ConversationHandler) (*conversation, C.int) { |
|
c := &conversation{} |
|
c.handler = handler |
|
c.conv = C.make_pam_conv(unsafe.Pointer(c)) |
|
if c.conv == nil { |
|
return nil, C.PAM_BUF_ERR |
|
} |
|
return c, C.PAM_SUCCESS |
|
} |
|
|
|
// Go-side function for processing a single conversational message. Ultimately |
|
// this calls the associated ConversationHandler's ResponsePAM callback with data |
|
// coming in from a C-side call. |
|
//export cbPAMConv |
|
func cbPAMConv(s C.int, msg *C.char, appdata unsafe.Pointer) (*C.char, C.int) { |
|
c := (*conversation)(appdata) |
|
r, err := c.handler.RespondPAM(Style(s), C.GoString(msg)) |
|
if err != nil { |
|
return nil, C.PAM_CONV_ERR |
|
} |
|
return C.CString(r), C.PAM_SUCCESS |
|
} |
|
|
|
// Transaction is the application's handle for a PAM transaction. |
|
type Transaction struct { |
|
handle *C.pam_handle_t |
|
conv *conversation |
|
status C.int |
|
} |
|
|
|
// Finalize a PAM transaction. |
|
func transactionFinalizer(t *Transaction) { |
|
C.pam_end(t.handle, t.status) |
|
C.free(unsafe.Pointer(t.conv.conv)) |
|
} |
|
|
|
// Start initiates a new PAM transaction. Service is treated identically to |
|
// how pam_start treats it internally. |
|
// |
|
// All application calls to PAM begin with Start (or StartFunc). The returned |
|
// transaction provides an interface to the remainder of the API. |
|
func Start(service, user string, handler ConversationHandler) (*Transaction, error) { |
|
t := &Transaction{} |
|
t.conv, t.status = newConversation(handler) |
|
if t.status != C.PAM_SUCCESS { |
|
return nil, t |
|
} |
|
s := C.CString(service) |
|
defer C.free(unsafe.Pointer(s)) |
|
var u *C.char |
|
if len(user) != 0 { |
|
u = C.CString(user) |
|
defer C.free(unsafe.Pointer(u)) |
|
} |
|
t.status = C.pam_start(s, u, t.conv.conv, &t.handle) |
|
if t.status != C.PAM_SUCCESS { |
|
C.free(unsafe.Pointer(t.conv.conv)) |
|
return nil, t |
|
} |
|
runtime.SetFinalizer(t, transactionFinalizer) |
|
return t, nil |
|
} |
|
|
|
// StartFunc registers the handler func as a conversation handler. |
|
func StartFunc(service, user string, handler func(Style, string) (string, error)) (*Transaction, error) { |
|
return Start(service, user, ConversationFunc(handler)) |
|
} |
|
|
|
func (t *Transaction) Error() string { |
|
return C.GoString(C.pam_strerror(t.handle, C.int(t.status))) |
|
} |
|
|
|
// Item is a an PAM information type. |
|
type Item int |
|
|
|
// PAM Item types. |
|
const ( |
|
// Service is the name which identifies the PAM stack. |
|
Service Item = C.PAM_SERVICE |
|
// User identifies the username identity used by a service. |
|
User = C.PAM_USER |
|
// Tty is the terminal name. |
|
Tty = C.PAM_TTY |
|
// Rhost is the requesting host name. |
|
Rhost = C.PAM_RHOST |
|
// Authtok is the currently active authentication token. |
|
Authtok = C.PAM_AUTHTOK |
|
// Oldauthtok is the old authentication token. |
|
Oldauthtok = C.PAM_OLDAUTHTOK |
|
// Ruser is the requesting user name. |
|
Ruser = C.PAM_RUSER |
|
// UserPrompt is the string use to prompt for a username. |
|
UserPrompt = C.PAM_USER_PROMPT |
|
) |
|
|
|
// SetItem sets a PAM information item. |
|
func (t *Transaction) SetItem(i Item, item string) error { |
|
cs := unsafe.Pointer(C.CString(item)) |
|
defer C.free(cs) |
|
t.status = C.pam_set_item(t.handle, C.int(i), cs) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// GetItem retrieves a PAM information item. |
|
func (t *Transaction) GetItem(i Item) (string, error) { |
|
var s unsafe.Pointer |
|
t.status = C.pam_get_item(t.handle, C.int(i), &s) |
|
if t.status != C.PAM_SUCCESS { |
|
return "", t |
|
} |
|
return C.GoString((*C.char)(s)), nil |
|
} |
|
|
|
// Flags are inputs to various PAM functions than be combined with a bitwise |
|
// or. Refer to the official PAM documentation for which flags are accepted |
|
// by which functions. |
|
type Flags int |
|
|
|
// PAM Flag types. |
|
const ( |
|
// Silent indicates that no messages should be emitted. |
|
Silent Flags = C.PAM_SILENT |
|
// DisallowNullAuthtok indicates that authorization should fail |
|
// if the user does not have a registered authentication token. |
|
DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK |
|
// EstablishCred indicates that credentials should be established |
|
// for the user. |
|
EstablishCred = C.PAM_ESTABLISH_CRED |
|
// DeleteCred inidicates that credentials should be deleted. |
|
DeleteCred = C.PAM_DELETE_CRED |
|
// ReinitializeCred indicates that credentials should be fully |
|
// reinitialized. |
|
ReinitializeCred = C.PAM_REINITIALIZE_CRED |
|
// RefreshCred indicates that the lifetime of existing credentials |
|
// should be extended. |
|
RefreshCred = C.PAM_REFRESH_CRED |
|
// ChangeExpiredAuthtok indicates that the authentication token |
|
// should be changed if it has expired. |
|
ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK |
|
) |
|
|
|
// Authenticate is used to authenticate the user. |
|
// |
|
// Valid flags: Silent, DisallowNullAuthtok |
|
func (t *Transaction) Authenticate(f Flags) error { |
|
t.status = C.pam_authenticate(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// SetCred is used to establish, maintain and delete the credentials of a |
|
// user. |
|
// |
|
// Valid flags: EstablishCred, DeleteCred, ReinitializeCred, RefreshCred |
|
func (t *Transaction) SetCred(f Flags) error { |
|
t.status = C.pam_setcred(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// AcctMgmt is used to determine if the user's account is valid. |
|
// |
|
// Valid flags: Silent, DisallowNullAuthtok |
|
func (t *Transaction) AcctMgmt(f Flags) error { |
|
t.status = C.pam_acct_mgmt(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// ChangeAuthTok is used to change the authentication token. |
|
// |
|
// Valid flags: Silent, ChangeExpiredAuthtok |
|
func (t *Transaction) ChangeAuthTok(f Flags) error { |
|
t.status = C.pam_chauthtok(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// OpenSession sets up a user session for an authenticated user. |
|
// |
|
// Valid flags: Slient |
|
func (t *Transaction) OpenSession(f Flags) error { |
|
t.status = C.pam_open_session(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// CloseSession closes a previously opened session. |
|
// |
|
// Valid flags: Silent |
|
func (t *Transaction) CloseSession(f Flags) error { |
|
t.status = C.pam_close_session(t.handle, C.int(f)) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// PutEnv adds or changes the value of PAM environment variables. |
|
// |
|
// NAME=value will set a variable to a value. |
|
// NAME= will set a variable to an empty value. |
|
// NAME (without an "=") will delete a variable. |
|
func (t *Transaction) PutEnv(nameval string) error { |
|
cs := C.CString(nameval) |
|
defer C.free(unsafe.Pointer(cs)) |
|
t.status = C.pam_putenv(t.handle, cs) |
|
if t.status != C.PAM_SUCCESS { |
|
return t |
|
} |
|
return nil |
|
} |
|
|
|
// GetEnv is used to retrieve a PAM environment variable. |
|
func (t *Transaction) GetEnv(name string) string { |
|
cs := C.CString(name) |
|
defer C.free(unsafe.Pointer(cs)) |
|
value := C.pam_getenv(t.handle, cs) |
|
if value == nil { |
|
return "" |
|
} |
|
return C.GoString(value) |
|
} |
|
|
|
func next(p **C.char) **C.char { |
|
return (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(p))) |
|
} |
|
|
|
// GetEnvList returns a copy of the PAM environment as a map. |
|
func (t *Transaction) GetEnvList() (map[string]string, error) { |
|
env := make(map[string]string) |
|
p := C.pam_getenvlist(t.handle) |
|
if p == nil { |
|
t.status = C.PAM_BUF_ERR |
|
return nil, t |
|
} |
|
for q := p; *q != nil; q = next(q) { |
|
chunks := strings.SplitN(C.GoString(*q), "=", 2) |
|
if len(chunks) == 2 { |
|
env[chunks[0]] = chunks[1] |
|
} |
|
C.free(unsafe.Pointer(*q)) |
|
} |
|
C.free(unsafe.Pointer(p)) |
|
return env, nil |
|
}
|
|
|