summaryrefslogtreecommitdiff
path: root/pam.go
blob: 82de43eb589eddb1fd903ceca283d97a05fbf1da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Package pam provides a wrapper for the application layer of the
// Pluggable Authentication Modules library.
package pam

import (
    //#include "golang-pam.h"
    //#cgo LDFLAGS: -lpam
    "C"
    "unsafe"
    "strings"
)

// Objects implementing the ConversationHandler interface can
// be registered as conversation callbacks to be used during
// PAM authentication.  RespondPAM receives a message style
// (one of PROMPT_ECHO_OFF, PROMPT_ECHO_ON, ERROR_MSG, or
// TEXT_INFO) and a message string.  It is expected to return
// a response string and a bool indicating success or failure.
type ConversationHandler interface {
    RespondPAM(msg_style int, msg string) (string,bool)
}

// ResponseFunc is an adapter to allow the use of ordinary
// functions as conversation callbacks.  ResponseFunc(f) is
// a ConversationHandler that calls f, where f must have
// the signature func(int,string)(string,bool).
type ResponseFunc func(int,string) (string,bool)
func (f ResponseFunc) RespondPAM(style int, msg string) (string,bool) {
    return f(style,msg)
}

// Internal conversation structure
type conversation struct {
    handler ConversationHandler
    cconv *C.struct_pam_conv
}

// Cosntructs 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 {
    conv := &conversation{}
    conv.handler = handler
    conv.cconv = C.make_gopam_conv(unsafe.Pointer(conv))
    return conv
}

//export goPAMConv
// 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.
func goPAMConv(msg_style C.int, msg *C.char, appdata unsafe.Pointer) (*C.char,int)  {
    conv := (*conversation)(appdata)
    resp,ok := conv.handler.RespondPAM(int(msg_style), C.GoString(msg))
    if ok {
        return C.CString(resp),SUCCESS
    }
    return nil,CONV_ERR
}

// Transaction is the application's handle for a single PAM transaction.
type Transaction struct {
    handle *C.pam_handle_t
    conv *conversation
}

// Start initiates a new PAM transaction.  serviceName is treated identically
// to how pam_start internally treats it.  The same applies to user, except that
// the empty string is passed to PAM as nil; therefore the empty string should be
// used to signal that no username is being provided.
//
// All application calls to PAM begin with Start().  The returned *Transaction
// provides an interface to the remainder of the API.
//
// The returned status int may be ABORT, BUF_ERR, SUCCESS, or SYSTEM_ERR, as per
// the official PAM documentation.
func Start(serviceName, user string, handler ConversationHandler) (*Transaction,int) {
    t := &Transaction{}
    t.conv = newConversation(handler)
    var status C.int
    if len(user) == 0 {
        status = C.pam_start(C.CString(serviceName), nil, t.conv.cconv, &t.handle)
    } else {
        status = C.pam_start(C.CString(serviceName), C.CString(user), t.conv.cconv, &t.handle)
    }

    if status != SUCCESS {
        C.free(unsafe.Pointer(t.conv.cconv))
        return nil,int(status)
    }

    return t, int(status)
}

// Ends a PAM transaction.  From Linux-PAM documentation: "The [status] argument
// should be set to the value returned by the last PAM library call."
//
// This may return SUCCESS, or SYSTEM_ERR.
//
// This *must* be called on any Transaction successfully returned by Start() or
// you will leak memory.
func (t *Transaction) End(status int) {
    C.pam_end(t.handle, C.int(status))
    C.free(unsafe.Pointer(t.conv.cconv))
}

// Sets a PAM informational item.  Legal values of itemType are listed here (excluding Linux extensions):
//
// http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_set_item
//
// the CONV item type is also not supported in order to simplify the Go API (and due to
// the fact that it is completely unnecessary).
func (t *Transaction) SetItem(itemType int, item string) int {
    if itemType == CONV { return BAD_ITEM }
    cs := unsafe.Pointer(C.CString(item))
    defer C.free(cs)
    return int(C.pam_set_item(t.handle, C.int(itemType), cs))
}

// Gets a PAM item.  Legal values of itemType are as specified by the documentation of SetItem.
func (t *Transaction) GetItem(itemType int) (string,int) {
    if itemType == CONV { return "",BAD_ITEM }
    result := C.pam_get_item_string(t.handle, C.int(itemType))
    return C.GoString(result.str),int(result.status)
}

// Error returns a PAM error string from a PAM error code
func (t *Transaction) Error(errnum int) string {
    return C.GoString(C.pam_strerror(t.handle, C.int(errnum)))
}

// pam_authenticate
func (t *Transaction) Authenticate(flags int) int {
    return int(C.pam_authenticate(t.handle, C.int(flags)))
}

// pam_setcred
func (t* Transaction) SetCred(flags int) int {
    return int(C.pam_setcred(t.handle, C.int(flags)))
}

// pam_acctmgmt
func (t* Transaction) AcctMgmt(flags int) int {
    return int(C.pam_acct_mgmt(t.handle, C.int(flags)))
}

// pam_chauthtok
func (t* Transaction) ChangeAuthTok(flags int) int {
    return int(C.pam_chauthtok(t.handle, C.int(flags)))
}

// pam_open_session
func (t* Transaction) OpenSession(flags int) int {
    return int(C.pam_open_session(t.handle, C.int(flags)))
}

// pam_close_session
func (t* Transaction) CloseSession(flags int) int {
    return int(C.pam_close_session(t.handle, C.int(flags)))
}

// pam_putenv
func (t* Transaction) PutEnv(nameval string) int {
    cs := C.CString(nameval)
    defer C.free(unsafe.Pointer(cs))
    return int(C.pam_putenv(t.handle, cs))
}

// pam_getenv.  Returns an additional argument indicating
// the actual existence of the given environment variable.
func (t* Transaction) GetEnv(name string) (string,bool) {
    cs := C.CString(name)
    defer C.free(unsafe.Pointer(cs))
    value := C.pam_getenv(t.handle, cs)
    if value != nil {
        return C.GoString(value),true
    }
    return "",false
}

// GetEnvList internally calls pam_getenvlist and then uses some very
// dangerous code to pull out the returned environment data and mash
// it into a map[string]string.  This call may be safe, but it hasn't
// been tested on enough platforms/architectures/PAM-implementations to
// be sure.
func (t* Transaction) GetEnvList() map[string]string {
    env := make(map[string]string)
    list := (uintptr)(unsafe.Pointer(C.pam_getenvlist(t.handle)))
    for *(*uintptr)(unsafe.Pointer(list)) != 0 {
        entry := *(*uintptr)(unsafe.Pointer(list))
        nameval := C.GoString((*C.char)(unsafe.Pointer(entry)))
        chunks := strings.SplitN(nameval, "=", 2)
        env[chunks[0]] = chunks[1]
        list += (uintptr)(unsafe.Sizeof(list))
    }
    return env
}