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.

306 lines
8.5 KiB

  1. // Package pam provides a wrapper for the PAM application API.
  2. package pam
  3. //#include <security/pam_appl.h>
  4. //#include <stdlib.h>
  5. //#cgo CFLAGS: -Wall -std=c99
  6. //#cgo LDFLAGS: -lpam
  7. //void init_pam_conv(struct pam_conv *conv, long c);
  8. import "C"
  9. import (
  10. "runtime"
  11. "strings"
  12. "unsafe"
  13. )
  14. // Style is the type of message that the conversation handler should display.
  15. type Style int
  16. // Coversation handler style types.
  17. const (
  18. // PromptEchoOff indicates the conversation handler should obtain a
  19. // string without echoing any text.
  20. PromptEchoOff Style = C.PAM_PROMPT_ECHO_OFF
  21. // PromptEchoOn indicates the conversation handler should obtain a
  22. // string while echoing text.
  23. PromptEchoOn = C.PAM_PROMPT_ECHO_ON
  24. // ErrorMsg indicates the conversation handler should display an
  25. // error message.
  26. ErrorMsg = C.PAM_ERROR_MSG
  27. // TextInfo indicates the conversation handler should display some
  28. // text.
  29. TextInfo = C.PAM_TEXT_INFO
  30. )
  31. // ConversationHandler is an interface for objects that can be used as
  32. // conversation callbacks during PAM authentication.
  33. type ConversationHandler interface {
  34. // RespondPAM receives a message style and a message string. If the
  35. // message Style is PromptEchoOff or PromptEchoOn then the function
  36. // should return a response string.
  37. RespondPAM(Style, string) (string, error)
  38. }
  39. // ConversationFunc is an adapter to allow the use of ordinary functions as
  40. // conversation callbacks.
  41. type ConversationFunc func(Style, string) (string, error)
  42. // RespondPAM is a conversation callback adapter.
  43. func (f ConversationFunc) RespondPAM(s Style, msg string) (string, error) {
  44. return f(s, msg)
  45. }
  46. // cbPAMConv is a wrapper for the conversation callback function.
  47. //export cbPAMConv
  48. func cbPAMConv(s C.int, msg *C.char, c int) (*C.char, C.int) {
  49. var r string
  50. var err error
  51. v := cbGet(c)
  52. switch cb := v.(type) {
  53. case ConversationHandler:
  54. r, err = cb.RespondPAM(Style(s), C.GoString(msg))
  55. }
  56. if err != nil {
  57. return nil, C.PAM_CONV_ERR
  58. }
  59. return C.CString(r), C.PAM_SUCCESS
  60. }
  61. // Transaction is the application's handle for a PAM transaction.
  62. type Transaction struct {
  63. handle *C.pam_handle_t
  64. conv *C.struct_pam_conv
  65. status C.int
  66. c int
  67. }
  68. // transactionFinalizer cleans up the PAM handle and deletes the callback
  69. // function.
  70. func transactionFinalizer(t *Transaction) {
  71. C.pam_end(t.handle, t.status)
  72. cbDelete(t.c)
  73. }
  74. // Start initiates a new PAM transaction. Service is treated identically to
  75. // how pam_start treats it internally.
  76. //
  77. // All application calls to PAM begin with Start (or StartFunc). The returned
  78. // transaction provides an interface to the remainder of the API.
  79. func Start(service, user string, handler ConversationHandler) (*Transaction, error) {
  80. t := &Transaction{
  81. conv: &C.struct_pam_conv{},
  82. c: cbAdd(handler),
  83. }
  84. C.init_pam_conv(t.conv, C.long(t.c))
  85. runtime.SetFinalizer(t, transactionFinalizer)
  86. s := C.CString(service)
  87. defer C.free(unsafe.Pointer(s))
  88. var u *C.char
  89. if len(user) != 0 {
  90. u = C.CString(user)
  91. defer C.free(unsafe.Pointer(u))
  92. }
  93. t.status = C.pam_start(s, u, t.conv, &t.handle)
  94. if t.status != C.PAM_SUCCESS {
  95. return nil, t
  96. }
  97. return t, nil
  98. }
  99. // StartFunc registers the handler func as a conversation handler.
  100. func StartFunc(service, user string, handler func(Style, string) (string, error)) (*Transaction, error) {
  101. return Start(service, user, ConversationFunc(handler))
  102. }
  103. func (t *Transaction) Error() string {
  104. return C.GoString(C.pam_strerror(t.handle, C.int(t.status)))
  105. }
  106. // Item is a an PAM information type.
  107. type Item int
  108. // PAM Item types.
  109. const (
  110. // Service is the name which identifies the PAM stack.
  111. Service Item = C.PAM_SERVICE
  112. // User identifies the username identity used by a service.
  113. User = C.PAM_USER
  114. // Tty is the terminal name.
  115. Tty = C.PAM_TTY
  116. // Rhost is the requesting host name.
  117. Rhost = C.PAM_RHOST
  118. // Authtok is the currently active authentication token.
  119. Authtok = C.PAM_AUTHTOK
  120. // Oldauthtok is the old authentication token.
  121. Oldauthtok = C.PAM_OLDAUTHTOK
  122. // Ruser is the requesting user name.
  123. Ruser = C.PAM_RUSER
  124. // UserPrompt is the string use to prompt for a username.
  125. UserPrompt = C.PAM_USER_PROMPT
  126. )
  127. // SetItem sets a PAM information item.
  128. func (t *Transaction) SetItem(i Item, item string) error {
  129. cs := unsafe.Pointer(C.CString(item))
  130. defer C.free(cs)
  131. t.status = C.pam_set_item(t.handle, C.int(i), cs)
  132. if t.status != C.PAM_SUCCESS {
  133. return t
  134. }
  135. return nil
  136. }
  137. // GetItem retrieves a PAM information item.
  138. func (t *Transaction) GetItem(i Item) (string, error) {
  139. var s unsafe.Pointer
  140. t.status = C.pam_get_item(t.handle, C.int(i), &s)
  141. if t.status != C.PAM_SUCCESS {
  142. return "", t
  143. }
  144. return C.GoString((*C.char)(s)), nil
  145. }
  146. // Flags are inputs to various PAM functions than be combined with a bitwise
  147. // or. Refer to the official PAM documentation for which flags are accepted
  148. // by which functions.
  149. type Flags int
  150. // PAM Flag types.
  151. const (
  152. // Silent indicates that no messages should be emitted.
  153. Silent Flags = C.PAM_SILENT
  154. // DisallowNullAuthtok indicates that authorization should fail
  155. // if the user does not have a registered authentication token.
  156. DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK
  157. // EstablishCred indicates that credentials should be established
  158. // for the user.
  159. EstablishCred = C.PAM_ESTABLISH_CRED
  160. // DeleteCred inidicates that credentials should be deleted.
  161. DeleteCred = C.PAM_DELETE_CRED
  162. // ReinitializeCred indicates that credentials should be fully
  163. // reinitialized.
  164. ReinitializeCred = C.PAM_REINITIALIZE_CRED
  165. // RefreshCred indicates that the lifetime of existing credentials
  166. // should be extended.
  167. RefreshCred = C.PAM_REFRESH_CRED
  168. // ChangeExpiredAuthtok indicates that the authentication token
  169. // should be changed if it has expired.
  170. ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK
  171. )
  172. // Authenticate is used to authenticate the user.
  173. //
  174. // Valid flags: Silent, DisallowNullAuthtok
  175. func (t *Transaction) Authenticate(f Flags) error {
  176. t.status = C.pam_authenticate(t.handle, C.int(f))
  177. if t.status != C.PAM_SUCCESS {
  178. return t
  179. }
  180. return nil
  181. }
  182. // SetCred is used to establish, maintain and delete the credentials of a
  183. // user.
  184. //
  185. // Valid flags: EstablishCred, DeleteCred, ReinitializeCred, RefreshCred
  186. func (t *Transaction) SetCred(f Flags) error {
  187. t.status = C.pam_setcred(t.handle, C.int(f))
  188. if t.status != C.PAM_SUCCESS {
  189. return t
  190. }
  191. return nil
  192. }
  193. // AcctMgmt is used to determine if the user's account is valid.
  194. //
  195. // Valid flags: Silent, DisallowNullAuthtok
  196. func (t *Transaction) AcctMgmt(f Flags) error {
  197. t.status = C.pam_acct_mgmt(t.handle, C.int(f))
  198. if t.status != C.PAM_SUCCESS {
  199. return t
  200. }
  201. return nil
  202. }
  203. // ChangeAuthTok is used to change the authentication token.
  204. //
  205. // Valid flags: Silent, ChangeExpiredAuthtok
  206. func (t *Transaction) ChangeAuthTok(f Flags) error {
  207. t.status = C.pam_chauthtok(t.handle, C.int(f))
  208. if t.status != C.PAM_SUCCESS {
  209. return t
  210. }
  211. return nil
  212. }
  213. // OpenSession sets up a user session for an authenticated user.
  214. //
  215. // Valid flags: Slient
  216. func (t *Transaction) OpenSession(f Flags) error {
  217. t.status = C.pam_open_session(t.handle, C.int(f))
  218. if t.status != C.PAM_SUCCESS {
  219. return t
  220. }
  221. return nil
  222. }
  223. // CloseSession closes a previously opened session.
  224. //
  225. // Valid flags: Silent
  226. func (t *Transaction) CloseSession(f Flags) error {
  227. t.status = C.pam_close_session(t.handle, C.int(f))
  228. if t.status != C.PAM_SUCCESS {
  229. return t
  230. }
  231. return nil
  232. }
  233. // PutEnv adds or changes the value of PAM environment variables.
  234. //
  235. // NAME=value will set a variable to a value.
  236. // NAME= will set a variable to an empty value.
  237. // NAME (without an "=") will delete a variable.
  238. func (t *Transaction) PutEnv(nameval string) error {
  239. cs := C.CString(nameval)
  240. defer C.free(unsafe.Pointer(cs))
  241. t.status = C.pam_putenv(t.handle, cs)
  242. if t.status != C.PAM_SUCCESS {
  243. return t
  244. }
  245. return nil
  246. }
  247. // GetEnv is used to retrieve a PAM environment variable.
  248. func (t *Transaction) GetEnv(name string) string {
  249. cs := C.CString(name)
  250. defer C.free(unsafe.Pointer(cs))
  251. value := C.pam_getenv(t.handle, cs)
  252. if value == nil {
  253. return ""
  254. }
  255. return C.GoString(value)
  256. }
  257. func next(p **C.char) **C.char {
  258. return (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(p)))
  259. }
  260. // GetEnvList returns a copy of the PAM environment as a map.
  261. func (t *Transaction) GetEnvList() (map[string]string, error) {
  262. env := make(map[string]string)
  263. p := C.pam_getenvlist(t.handle)
  264. if p == nil {
  265. t.status = C.PAM_BUF_ERR
  266. return nil, t
  267. }
  268. for q := p; *q != nil; q = next(q) {
  269. chunks := strings.SplitN(C.GoString(*q), "=", 2)
  270. if len(chunks) == 2 {
  271. env[chunks[0]] = chunks[1]
  272. }
  273. C.free(unsafe.Pointer(*q))
  274. }
  275. C.free(unsafe.Pointer(p))
  276. return env, nil
  277. }