package pam //#include //#include //#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 }