├── Makefile ├── README ├── examples ├── Makefile └── fakelogin.go └── pam ├── Makefile ├── gopam.c ├── gopam.h ├── pam.go ├── pamdefs.c └── pamdefs.go /Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | .PHONY: all pam install examples clean 4 | 5 | all: install examples 6 | 7 | pam: 8 | gomake -C pam 9 | 10 | install: pam 11 | gomake -C pam install 12 | 13 | examples: 14 | gomake -C examples 15 | 16 | clean: 17 | gomake -C pam clean 18 | gomake -C examples clean 19 | 20 | 21 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | It's Go! It's PAM (Pluggable Authentication Modules)! It's GoPAM! 2 | 3 | This is a Go wrapper for the PAM application API. There's not much 4 | else to be said. PAM is a simple API and now it's available for use in Go 5 | applications. 6 | 7 | There's an example of a "fake login" program in the examples 8 | directory. Look at the pam module's godocs for details about the Go 9 | API; for a more general PAM application API reference, peep 10 | 11 | http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html 12 | 13 | In the future, maybe the module API will be wrapped too. I don't know! 14 | 15 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2009 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | include $(GOROOT)/src/Make.inc 6 | 7 | TARG=fakelogin 8 | GOFILES=\ 9 | fakelogin.go 10 | 11 | include $(GOROOT)/src/Make.cmd 12 | -------------------------------------------------------------------------------- /examples/fakelogin.go: -------------------------------------------------------------------------------- 1 | // This is a fake login implementation! It uses whatever default 2 | // PAM service configuration is available on the system, and tries 3 | // to authenticate any user. This should cause PAM to ask its 4 | // conversation handler for a username and password, in sequence. 5 | // 6 | // This application will handle those requests by displaying the 7 | // PAM-provided prompt and sending back the first line of stdin input 8 | // it can read for each. 9 | // 10 | // Keep in mind that unless run as root (or setuid root), the only 11 | // user's authentication that can succeed is that of the process owner. 12 | // 13 | // It's not a real login for several reasons: 14 | // 15 | // (!WARNING!) It echos your password to the terminal (!WARNING!) 16 | // It doesn't switch users. 17 | // It's not a real login. 18 | // 19 | // It does however demonstrate a simple but powerful use of Go PAM. 20 | 21 | package main 22 | 23 | import( 24 | "fmt" 25 | "github.com/krockot/gopam/pam" 26 | "os" 27 | "bufio" 28 | ) 29 | 30 | func GetLine(prompt string) (string,bool) { 31 | fmt.Print(prompt) 32 | in := bufio.NewReader(os.Stdin) 33 | input,err := in.ReadString('\n') 34 | if err != nil { 35 | return "",false 36 | } 37 | return input[:len(input)-1],true 38 | } 39 | 40 | // Echo on/off is ignored; echo will always happen. 41 | func DumbPrompter(style int, msg string) (string,bool) { 42 | switch style { 43 | case pam.PROMPT_ECHO_OFF: 44 | return GetLine(msg) 45 | case pam.PROMPT_ECHO_ON: 46 | return GetLine(msg) 47 | case pam.ERROR_MSG: 48 | fmt.Fprintf(os.Stderr, "Error: %s\n", msg) 49 | return "",true 50 | case pam.TEXT_INFO: 51 | fmt.Println(msg) 52 | return "",true 53 | } 54 | return "",false 55 | } 56 | 57 | func main() { 58 | t,status := pam.Start("", "", pam.ResponseFunc(DumbPrompter)) 59 | if status != pam.SUCCESS { 60 | fmt.Fprintf(os.Stderr, "Start() failed: %s\n", t.Error(status)) 61 | return 62 | } 63 | defer func(){ t.End(status) }() 64 | 65 | status = t.Authenticate(0) 66 | if status != pam.SUCCESS { 67 | fmt.Fprintf(os.Stderr, "Auth failed: %s\n", t.Error(status)) 68 | return 69 | } 70 | 71 | fmt.Printf("Authentication succeeded!\n") 72 | fmt.Printf("Goodbye, friend.\n") 73 | } 74 | 75 | -------------------------------------------------------------------------------- /pam/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | TARG=pam 3 | GOFILES:=pamdefs.go 4 | CGOFILES:=pam.go 5 | CGO_LDFLAGS:=-lpam 6 | CGO_OFILES:=gopam.o 7 | 8 | include $(GOROOT)/src/Make.pkg 9 | 10 | DOLLAR:="$" 11 | 12 | pamdefs.go: pamdefs.c 13 | godefs `echo -n $(CGO_FLAGS) | sed 's/\(^ ^$(DOLLAR)]*\)/-f \1/g'` -g pam pamdefs.c > pamdefs.go 14 | gofmt -w pamdefs.go 15 | 16 | 17 | -------------------------------------------------------------------------------- /pam/gopam.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "gopam.h" 6 | #include "_cgo_export.h" 7 | 8 | /* Simplification of pam_get_item to remove type ambiguity. Will never 9 | be called (promise) with a type that returns anything other than a string */ 10 | get_item_result pam_get_item_string(pam_handle_t *handle, int type) { 11 | get_item_result result; 12 | result.status = pam_get_item(handle, type, (const void **)&result.str); 13 | return result; 14 | } 15 | 16 | /* The universal conversation callback for gopam transactions. appdata_ptr 17 | is always taken as a raw pointer to a Go-side pam.conversation object. 18 | In order to avoid nightmareish unsafe pointer math all over the Go 19 | implementation, this universal callback deals with memory allocation of 20 | response buffers, as well as unpacking and repackaging incoming messages 21 | and responses, calling a Go-side callback that handles each one on an 22 | individual basis. */ 23 | int gopam_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) 24 | { 25 | int i, ok = 1; 26 | struct pam_response *responses = (struct pam_response*)malloc(sizeof(struct pam_response) * num_msg); 27 | memset(responses, 0, sizeof(struct pam_response) * num_msg); 28 | 29 | for(i = 0; i < num_msg; ++i) { 30 | const struct pam_message *m = msg[i]; 31 | struct goPAMConv_return result = goPAMConv(m->msg_style, (char*)m->msg, appdata_ptr); 32 | if(result.r1 == PAM_SUCCESS) 33 | responses[i].resp = result.r0; 34 | else { 35 | ok = 0; 36 | break; 37 | } 38 | } 39 | 40 | if(ok) { 41 | *resp = responses; 42 | return PAM_SUCCESS; 43 | } 44 | 45 | /* In the case of failure PAM will never see these responses. The 46 | resp strings that have been allocated by Go-side C.CString calls 47 | must be freed lest we leak them. */ 48 | for(i = 0; i < num_msg; ++i) 49 | if(responses[i].resp != NULL) 50 | free(responses[i].resp); 51 | 52 | free(responses); 53 | return PAM_CONV_ERR; 54 | } 55 | 56 | /* This allocates a new pam_conv struct and fills in its fields: 57 | The conv function pointer always points to the universal gopam_conv. 58 | The appdata_ptr will be set to the incoming void* argument, which 59 | is always a Go-side *pam.conversation whose handler was given 60 | to pam.Start(). */ 61 | struct pam_conv* make_gopam_conv(void *goconv) 62 | { 63 | struct pam_conv* conv = (struct pam_conv*)malloc(sizeof(struct pam_conv)); 64 | conv->conv = gopam_conv; 65 | conv->appdata_ptr = goconv; 66 | return conv; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /pam/gopam.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct { 5 | const char *str; 6 | int status; 7 | } get_item_result; 8 | 9 | get_item_result pam_get_item_string(pam_handle_t *handle, int type); 10 | struct pam_conv* make_gopam_conv(void *goconv); 11 | 12 | -------------------------------------------------------------------------------- /pam/pam.go: -------------------------------------------------------------------------------- 1 | // Package pam provides a wrapper for the application layer of the 2 | // Pluggable Authentication Modules library. 3 | package pam 4 | 5 | import ( 6 | //#include "gopam.h" 7 | //#cgo LDFLAGS: -lpam 8 | "C" 9 | "unsafe" 10 | "strings" 11 | ) 12 | 13 | // Objects implementing the ConversationHandler interface can 14 | // be registered as conversation callbacks to be used during 15 | // PAM authentication. RespondPAM receives a message style 16 | // (one of PROMPT_ECHO_OFF, PROMPT_ECHO_ON, ERROR_MSG, or 17 | // TEXT_INFO) and a message string. It is expected to return 18 | // a response string and a bool indicating success or failure. 19 | type ConversationHandler interface { 20 | RespondPAM(msg_style int, msg string) (string,bool) 21 | } 22 | 23 | // ResponseFunc is an adapter to allow the use of ordinary 24 | // functions as conversation callbacks. ResponseFunc(f) is 25 | // a ConversationHandler that calls f, where f must have 26 | // the signature func(int,string)(string,bool). 27 | type ResponseFunc func(int,string) (string,bool) 28 | func (f ResponseFunc) RespondPAM(style int, msg string) (string,bool) { 29 | return f(style,msg) 30 | } 31 | 32 | // Internal conversation structure 33 | type conversation struct { 34 | handler ConversationHandler 35 | cconv *C.struct_pam_conv 36 | } 37 | 38 | // Cosntructs a new conversation object with a given handler and a newly 39 | // allocated pam_conv struct that uses this object as its appdata_ptr 40 | func newConversation(handler ConversationHandler) *conversation { 41 | conv := &conversation{} 42 | conv.handler = handler 43 | conv.cconv = C.make_gopam_conv(unsafe.Pointer(conv)) 44 | return conv 45 | } 46 | 47 | //export goPAMConv 48 | // Go-side function for processing a single conversational message. Ultimately 49 | // this calls the associated ConversationHandler's ResponsePAM callback with data 50 | // coming in from a C-side call. 51 | func goPAMConv(msg_style C.int, msg *C.char, appdata unsafe.Pointer) (*C.char,int) { 52 | conv := (*conversation)(appdata) 53 | resp,ok := conv.handler.RespondPAM(int(msg_style), C.GoString(msg)) 54 | if ok { 55 | return C.CString(resp),SUCCESS 56 | } 57 | return nil,CONV_ERR 58 | } 59 | 60 | // Transaction is the application's handle for a single PAM transaction. 61 | type Transaction struct { 62 | handle *C.pam_handle_t 63 | conv *conversation 64 | } 65 | 66 | // Start initiates a new PAM transaction. serviceName is treated identically 67 | // to how pam_start internally treats it. The same applies to user, except that 68 | // the empty string is passed to PAM as nil; therefore the empty string should be 69 | // used to signal that no username is being provided. 70 | // 71 | // All application calls to PAM begin with Start(). The returned *Transaction 72 | // provides an interface to the remainder of the API. 73 | // 74 | // The returned status int may be ABORT, BUF_ERR, SUCCESS, or SYSTEM_ERR, as per 75 | // the official PAM documentation. 76 | func Start(serviceName, user string, handler ConversationHandler) (*Transaction,int) { 77 | t := &Transaction{} 78 | t.conv = newConversation(handler) 79 | var status C.int 80 | if len(user) == 0 { 81 | status = C.pam_start(C.CString(serviceName), nil, t.conv.cconv, &t.handle) 82 | } else { 83 | status = C.pam_start(C.CString(serviceName), C.CString(user), t.conv.cconv, &t.handle) 84 | } 85 | 86 | if status != SUCCESS { 87 | C.free(unsafe.Pointer(t.conv.cconv)) 88 | return nil,int(status) 89 | } 90 | 91 | return t, int(status) 92 | } 93 | 94 | // Ends a PAM transaction. From Linux-PAM documentation: "The [status] argument 95 | // should be set to the value returned by the last PAM library call." 96 | // 97 | // This may return SUCCESS, or SYSTEM_ERR. 98 | // 99 | // This *must* be called on any Transaction successfully returned by Start() or 100 | // you will leak memory. 101 | func (t *Transaction) End(status int) { 102 | C.pam_end(t.handle, C.int(status)) 103 | C.free(unsafe.Pointer(t.conv.cconv)) 104 | } 105 | 106 | // Sets a PAM informational item. Legal values of itemType are listed here (excluding Linux extensions): 107 | // 108 | // http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_set_item 109 | // 110 | // the CONV item type is also not supported in order to simplify the Go API (and due to 111 | // the fact that it is completely unnecessary). 112 | func (t *Transaction) SetItem(itemType int, item string) int { 113 | if itemType == CONV { return BAD_ITEM } 114 | cs := unsafe.Pointer(C.CString(item)) 115 | defer C.free(cs) 116 | return int(C.pam_set_item(t.handle, C.int(itemType), cs)) 117 | } 118 | 119 | // Gets a PAM item. Legal values of itemType are as specified by the documentation of SetItem. 120 | func (t *Transaction) GetItem(itemType int) (string,int) { 121 | if itemType == CONV { return "",BAD_ITEM } 122 | result := C.pam_get_item_string(t.handle, C.int(itemType)) 123 | return C.GoString(result.str),int(result.status) 124 | } 125 | 126 | // Error returns a PAM error string from a PAM error code 127 | func (t *Transaction) Error(errnum int) string { 128 | return C.GoString(C.pam_strerror(t.handle, C.int(errnum))) 129 | } 130 | 131 | // pam_authenticate 132 | func (t *Transaction) Authenticate(flags int) int { 133 | return int(C.pam_authenticate(t.handle, C.int(flags))) 134 | } 135 | 136 | // pam_setcred 137 | func (t* Transaction) SetCred(flags int) int { 138 | return int(C.pam_setcred(t.handle, C.int(flags))) 139 | } 140 | 141 | // pam_acctmgmt 142 | func (t* Transaction) AcctMgmt(flags int) int { 143 | return int(C.pam_acct_mgmt(t.handle, C.int(flags))) 144 | } 145 | 146 | // pam_chauthtok 147 | func (t* Transaction) ChangeAuthTok(flags int) int { 148 | return int(C.pam_chauthtok(t.handle, C.int(flags))) 149 | } 150 | 151 | // pam_open_session 152 | func (t* Transaction) OpenSession(flags int) int { 153 | return int(C.pam_open_session(t.handle, C.int(flags))) 154 | } 155 | 156 | // pam_close_session 157 | func (t* Transaction) CloseSession(flags int) int { 158 | return int(C.pam_close_session(t.handle, C.int(flags))) 159 | } 160 | 161 | // pam_putenv 162 | func (t* Transaction) PutEnv(nameval string) int { 163 | cs := C.CString(nameval) 164 | defer C.free(unsafe.Pointer(cs)) 165 | return int(C.pam_putenv(t.handle, cs)) 166 | } 167 | 168 | // pam_getenv. Returns an additional argument indicating 169 | // the actual existence of the given environment variable. 170 | func (t* Transaction) GetEnv(name string) (string,bool) { 171 | cs := C.CString(name) 172 | defer C.free(unsafe.Pointer(cs)) 173 | value := C.pam_getenv(t.handle, cs) 174 | if value != nil { 175 | return C.GoString(value),true 176 | } 177 | return "",false 178 | } 179 | 180 | // GetEnvList internally calls pam_getenvlist and then uses some very 181 | // dangerous code to pull out the returned environment data and mash 182 | // it into a map[string]string. This call may be safe, but it hasn't 183 | // been tested on enough platforms/architectures/PAM-implementations to 184 | // be sure. 185 | func (t* Transaction) GetEnvList() map[string]string { 186 | env := make(map[string]string) 187 | list := (uintptr)(unsafe.Pointer(C.pam_getenvlist(t.handle))) 188 | for *(*uintptr)(unsafe.Pointer(list)) != 0 { 189 | entry := *(*uintptr)(unsafe.Pointer(list)) 190 | nameval := C.GoString((*C.char)(unsafe.Pointer(entry))) 191 | chunks := strings.Split(nameval, "=", 2) 192 | env[chunks[0]] = chunks[1] 193 | list += (uintptr)(unsafe.Sizeof(list)) 194 | } 195 | return env 196 | } 197 | 198 | -------------------------------------------------------------------------------- /pam/pamdefs.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum { 4 | $SUCCESS = PAM_SUCCESS, 5 | $OPEN_ERR = PAM_OPEN_ERR, 6 | $SYMBOL_ERR = PAM_SYMBOL_ERR, 7 | $SERVICE_ERR = PAM_SERVICE_ERR, 8 | $SYSTEM_ERR = PAM_SYSTEM_ERR, 9 | $BUF_ERR = PAM_BUF_ERR, 10 | $PERM_DENIED = PAM_PERM_DENIED, 11 | $AUTH_ERR = PAM_AUTH_ERR, 12 | $CRED_INSUFFICIENT = PAM_CRED_INSUFFICIENT, 13 | $AUTHINFO_UNAVAIL = PAM_AUTHINFO_UNAVAIL, 14 | $USER_UNKNOWN = PAM_USER_UNKNOWN, 15 | $MAXTRIES = PAM_MAXTRIES, 16 | $NEW_AUTHOTK_REQD = PAM_NEW_AUTHTOK_REQD, 17 | $ACCT_EXPIRED = PAM_ACCT_EXPIRED, 18 | $SESSION_ERR = PAM_SESSION_ERR, 19 | $CRED_UNAVAIL = PAM_CRED_UNAVAIL, 20 | $CRED_EXPIRED = PAM_CRED_EXPIRED, 21 | $CRED_ERR = PAM_CRED_ERR, 22 | $NO_MODULE_DATA = PAM_NO_MODULE_DATA, 23 | $CONV_ERR = PAM_CONV_ERR, 24 | $AUTHTOK_ERR = PAM_AUTHTOK_ERR, 25 | $AUTHTOK_RECOVERY_ERR = PAM_AUTHTOK_RECOVERY_ERR, 26 | $AUTHTOK_LOCK_BUSY = PAM_AUTHTOK_LOCK_BUSY, 27 | $AUTHTOK_DISABLE_AGING = PAM_AUTHTOK_DISABLE_AGING, 28 | $TRY_AGAIN = PAM_TRY_AGAIN, 29 | $IGNORE = PAM_IGNORE, 30 | $ABORT = PAM_ABORT, 31 | $AUTHTOK_EXPIRED = PAM_AUTHTOK_EXPIRED, 32 | $MODULE_UNKNOWN = PAM_MODULE_UNKNOWN, 33 | $BAD_ITEM = PAM_BAD_ITEM, 34 | $CONV_AGAIN = PAM_CONV_AGAIN, 35 | $INCOMPLETE = PAM_INCOMPLETE 36 | }; 37 | 38 | enum { 39 | $SILENT = PAM_SILENT, 40 | $DISALLOW_NULL_AUTHTOK = PAM_DISALLOW_NULL_AUTHTOK, 41 | $ESTABLISH_CRED = PAM_ESTABLISH_CRED, 42 | $DELETE_CRED = PAM_DELETE_CRED, 43 | $REINITIALIZE_CRED = PAM_REINITIALIZE_CRED, 44 | $REFRESH_CRED = PAM_REFRESH_CRED, 45 | $CHANGE_EXPIRED_AUTHTOK = PAM_CHANGE_EXPIRED_AUTHTOK 46 | }; 47 | 48 | enum { 49 | $SERVICE = PAM_SERVICE, 50 | $USER = PAM_USER, 51 | $TTY = PAM_TTY, 52 | $RHOST = PAM_RHOST, 53 | $CONV = PAM_CONV, 54 | $AUTHTOK = PAM_AUTHTOK, 55 | $OLDAUTHTOK = PAM_OLDAUTHTOK, 56 | $RUSER = PAM_RUSER, 57 | $USER_PROMPT = PAM_USER_PROMPT, 58 | 59 | /* Linux-PAM extensions. Leaving these out, for now... 60 | $FAIL_DELAY = PAM_FAIL_DELAY, 61 | $XDISPLAY = PAM_XDISPLAY, 62 | $XAUTHDATA = PAM_XAUTHDATA, 63 | $AUTHTOK_TYPE = PAM_AUTHTOK_TYPE 64 | */ 65 | }; 66 | 67 | enum { 68 | $PROMPT_ECHO_OFF = PAM_PROMPT_ECHO_OFF, 69 | $PROMPT_ECHO_ON = PAM_PROMPT_ECHO_ON, 70 | $ERROR_MSG = PAM_ERROR_MSG, 71 | $TEXT_INFO = PAM_TEXT_INFO 72 | }; 73 | 74 | 75 | -------------------------------------------------------------------------------- /pam/pamdefs.go: -------------------------------------------------------------------------------- 1 | // godefs -g pam pamdefs.c 2 | 3 | // MACHINE GENERATED - DO NOT EDIT. 4 | 5 | package pam 6 | 7 | // Constants 8 | const ( 9 | SUCCESS = 0 10 | OPEN_ERR = 0x1 11 | SYMBOL_ERR = 0x2 12 | SERVICE_ERR = 0x3 13 | SYSTEM_ERR = 0x4 14 | BUF_ERR = 0x5 15 | PERM_DENIED = 0x6 16 | AUTH_ERR = 0x7 17 | CRED_INSUFFICIENT = 0x8 18 | AUTHINFO_UNAVAIL = 0x9 19 | USER_UNKNOWN = 0xa 20 | MAXTRIES = 0xb 21 | NEW_AUTHOTK_REQD = 0xc 22 | ACCT_EXPIRED = 0xd 23 | SESSION_ERR = 0xe 24 | CRED_UNAVAIL = 0xf 25 | CRED_EXPIRED = 0x10 26 | CRED_ERR = 0x11 27 | NO_MODULE_DATA = 0x12 28 | CONV_ERR = 0x13 29 | AUTHTOK_ERR = 0x14 30 | AUTHTOK_RECOVERY_ERR = 0x15 31 | AUTHTOK_LOCK_BUSY = 0x16 32 | AUTHTOK_DISABLE_AGING = 0x17 33 | TRY_AGAIN = 0x18 34 | IGNORE = 0x19 35 | ABORT = 0x1a 36 | AUTHTOK_EXPIRED = 0x1b 37 | MODULE_UNKNOWN = 0x1c 38 | BAD_ITEM = 0x1d 39 | CONV_AGAIN = 0x1e 40 | INCOMPLETE = 0x1f 41 | SILENT = 0x8000 42 | DISALLOW_NULL_AUTHTOK = 0x1 43 | ESTABLISH_CRED = 0x2 44 | DELETE_CRED = 0x4 45 | REINITIALIZE_CRED = 0x8 46 | REFRESH_CRED = 0x10 47 | CHANGE_EXPIRED_AUTHTOK = 0x20 48 | SERVICE = 0x1 49 | USER = 0x2 50 | TTY = 0x3 51 | RHOST = 0x4 52 | CONV = 0x5 53 | AUTHTOK = 0x6 54 | OLDAUTHTOK = 0x7 55 | RUSER = 0x8 56 | USER_PROMPT = 0x9 57 | PROMPT_ECHO_OFF = 0x1 58 | PROMPT_ECHO_ON = 0x2 59 | ERROR_MSG = 0x3 60 | TEXT_INFO = 0x4 61 | ) 62 | 63 | // Types 64 | --------------------------------------------------------------------------------