107 lines
3.7 KiB
Go
107 lines
3.7 KiB
Go
//go:build pam
|
|
|
|
// Package pamauth authenticates a username/password pair against the host's PAM
|
|
// stack (/etc/pam.d/<service>). It is the backend for uopi's built-in HTTP Basic
|
|
// authentication: because the uopi host is typically already an SSSD/LDAP client,
|
|
// validating through PAM reuses the exact same login path as `login`/`ssh`
|
|
// (pam_sss → the site directory) without uopi needing any directory schema.
|
|
//
|
|
// This file is the real implementation, compiled only with the `pam` build tag
|
|
// (which also requires cgo + libpam). The default fully-static CGO_ENABLED=0
|
|
// build uses stub.go instead, where Authenticate reports PAM is unavailable.
|
|
package pamauth
|
|
|
|
/*
|
|
#cgo LDFLAGS: -lpam
|
|
#include <security/pam_appl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// uopiPamConv answers every password-style PAM prompt with the password passed
|
|
// through appdata_ptr. Informational/error messages get a NULL response. The PAM
|
|
// library takes ownership of the returned responses and frees them.
|
|
static int uopiPamConv(int num_msg, const struct pam_message **msg,
|
|
struct pam_response **resp, void *appdata_ptr) {
|
|
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
|
|
return PAM_CONV_ERR;
|
|
}
|
|
struct pam_response *r = calloc((size_t)num_msg, sizeof(struct pam_response));
|
|
if (r == NULL) {
|
|
return PAM_BUF_ERR;
|
|
}
|
|
for (int i = 0; i < num_msg; i++) {
|
|
int style = msg[i]->msg_style;
|
|
if (style == PAM_PROMPT_ECHO_OFF || style == PAM_PROMPT_ECHO_ON) {
|
|
r[i].resp = strdup((const char *)appdata_ptr);
|
|
if (r[i].resp == NULL) {
|
|
for (int j = 0; j < i; j++) {
|
|
free(r[j].resp);
|
|
}
|
|
free(r);
|
|
return PAM_BUF_ERR;
|
|
}
|
|
}
|
|
r[i].resp_retcode = 0;
|
|
}
|
|
*resp = r;
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
// uopiPamAuth runs authentication + account management for service/user using
|
|
// pass. Returns PAM_SUCCESS or the failing PAM error code.
|
|
static int uopiPamAuth(const char *service, const char *user, char *pass) {
|
|
struct pam_conv conv;
|
|
conv.conv = uopiPamConv;
|
|
conv.appdata_ptr = (void *)pass;
|
|
|
|
pam_handle_t *pamh = NULL;
|
|
int ret = pam_start(service, user, &conv, &pamh);
|
|
if (ret != PAM_SUCCESS) {
|
|
return ret;
|
|
}
|
|
ret = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK);
|
|
if (ret == PAM_SUCCESS) {
|
|
ret = pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK);
|
|
}
|
|
pam_end(pamh, ret);
|
|
return ret;
|
|
}
|
|
|
|
// uopiStrerror maps a PAM error code to a human-readable string. Linux-PAM
|
|
// ignores the handle, so NULL is fine after pam_end.
|
|
static const char *uopiStrerror(int code) {
|
|
return pam_strerror(NULL, code);
|
|
}
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
// Available reports whether this build includes PAM support. True here.
|
|
const Available = true
|
|
|
|
// ErrUnavailable is returned by Authenticate in builds without PAM support. It
|
|
// is declared in both build variants so callers can compare against it.
|
|
var ErrUnavailable = errors.New("pamauth: PAM support not compiled in")
|
|
|
|
// Authenticate verifies username/password against the named PAM service
|
|
// (/etc/pam.d/<service>). It returns nil on success or an error describing the
|
|
// PAM failure. It is safe for concurrent use.
|
|
func Authenticate(service, username, password string) error {
|
|
cService := C.CString(service)
|
|
cUser := C.CString(username)
|
|
cPass := C.CString(password)
|
|
defer C.free(unsafe.Pointer(cService))
|
|
defer C.free(unsafe.Pointer(cUser))
|
|
defer C.free(unsafe.Pointer(cPass))
|
|
|
|
if ret := C.uopiPamAuth(cService, cUser, cPass); ret != C.PAM_SUCCESS {
|
|
return fmt.Errorf("pam: %s", C.GoString(C.uopiStrerror(ret)))
|
|
}
|
|
return nil
|
|
}
|