//go:build pam // Package pamauth authenticates a username/password pair against the host's PAM // stack (/etc/pam.d/). 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 #include #include // 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/). 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 }