#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <pwd.h>
#include <utmpx.h>
#include <CoreFoundation/CoreFoundation.h>
#include <OpenDirectory/OpenDirectory.h>
#include <OpenDirectory/OpenDirectoryPriv.h>
#include <DirectoryService/DirectoryService.h>
#include <NetFS/URLMount.h>
#include <DiskImages/DIHLFileVaultInterface.h>
#include <DiskImages/DIFrameworkUtilities.h>
static int
extract_homemount(CFStringRef val, char **in_server_URL, char **in_path)
{
int retval = -1;
CFIndex maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(val), kCFStringEncodingUTF8);
char *buffer = malloc(maxlen+1);
if (NULL == buffer)
goto fin;
if (!CFStringGetCString(val, buffer, maxlen+1, kCFStringEncodingUTF8))
goto fin;
static const char URL_OPEN[] = "<url>";
static const char URL_CLOSE[] = "</url>";
static const char PATH_OPEN[] = "<path>";
static const char PATH_CLOSE[] = "</path>";
char *server_URL;
char *path;
char *record_start = NULL;
char *record_end = NULL;
record_start = buffer;
server_URL = strstr(record_start, URL_OPEN);
if (NULL == server_URL)
goto fin;
server_URL += sizeof(URL_OPEN)-1;
while ('\0' != *server_URL && isspace(*server_URL))
server_URL++;
record_end = strstr(server_URL, URL_CLOSE);
if (NULL == record_end)
goto fin;
while (record_end >= server_URL && '\0' != *record_end && isspace(*(record_end-1)))
record_end--;
if (NULL == record_end)
goto fin;
*record_end = '\0';
if (NULL == (*in_server_URL = strdup(server_URL)))
goto fin;
record_start = record_end+1;
path = strstr(record_start, PATH_OPEN);
if (NULL == path)
goto ok;
path += sizeof(PATH_OPEN)-1;
while ('\0' != *path && isspace(*path))
path++;
record_end = strstr(path, PATH_CLOSE);
if (NULL == record_end)
goto fin;
while (record_end >= path && '\0' != *record_end && isspace(*(record_end-1)))
record_end--;
if (NULL == record_end)
goto fin;
*record_end = '\0';
if (NULL == (*in_path = strdup(path)))
goto fin;
ok:
retval = 0;
fin:
free(buffer);
return retval;
}
static int
extract_homedir(CFStringRef val, char **in_homedir)
{
CFIndex maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(val), kCFStringEncodingUTF8);
char *buffer = malloc(maxlen+1);
if (NULL != buffer && CFStringGetCString(val, buffer, maxlen+1, kCFStringEncodingUTF8)) {
*in_homedir = buffer;
return 0;
} else {
free(buffer);
return -1;
}
}
static int
extract_od_values(const char *username, char **server_URL, char **path, char **homedir)
{
CFArrayRef vals;
int retval = PAM_SUCCESS;
ODNodeRef cfNodeRef = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault, eDSAuthenticationSearchNodeName, NULL);
if (cfNodeRef != NULL) {
CFStringRef cfUser = CFStringCreateWithCString(NULL, username, kCFStringEncodingUTF8);
if (cfUser != NULL) {
ODRecordRef cfRecord = ODNodeCopyRecord(cfNodeRef, CFSTR(kDSStdRecordTypeUsers), cfUser, NULL, NULL);
if (cfRecord != NULL) {
if (retval == PAM_SUCCESS) {
vals = ODRecordCopyValues(cfRecord, CFSTR(kDSNAttrHomeDirectory), NULL);
if (vals != NULL && CFArrayGetCount(vals) > 0) {
const void *val = CFArrayGetValueAtIndex(vals, 0);
if (val == NULL || CFGetTypeID(val) != CFStringGetTypeID()) {
openpam_log(PAM_LOG_ERROR, "The user's kDSNAttrHomeDirectory is not a string.");
retval = PAM_SERVICE_ERR;
} else if (0 != extract_homemount(val, server_URL, path)) {
openpam_log(PAM_LOG_ERROR, "Unable to extract the server_URL or path.");
retval = PAM_SERVICE_ERR;
}
CFRelease(vals);
}
}
if (retval == PAM_SUCCESS) {
vals = ODRecordCopyValues(cfRecord, CFSTR(kDS1AttrNFSHomeDirectory), NULL);
if (vals != NULL && CFArrayGetCount(vals) > 0) {
const void *val = CFArrayGetValueAtIndex(vals, 0);
if (val == NULL || CFGetTypeID(val) != CFStringGetTypeID()) {
openpam_log(PAM_LOG_ERROR, "This user's kDS1AttrNFSHomeDirectory is not a string.");
retval = PAM_SERVICE_ERR;
} else if (0 != extract_homedir(val, homedir)) {
openpam_log(PAM_LOG_ERROR, "Unable to extract the homedir.");
retval = PAM_SERVICE_ERR;
}
CFRelease(vals);
}
}
CFRelease(cfRecord);
}
CFRelease(cfUser);
}
CFRelease(cfNodeRef);
}
return retval;
}
static void
pam_malloc_cleanup(__unused pam_handle_t *pamh, void *data, __unused int pam_end_status)
{
free(data);
}
static void
pam_cf_cleanup(__unused pam_handle_t *pamh, void *data, __unused int pam_end_status)
{
CFStringRef *cfstring = data;
CFRelease(*cfstring);
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
static const char password_prompt[] = "Password:";
char *authenticator = NULL;
if (PAM_SUCCESS != pam_get_item(pamh, PAM_AUTHTOK, (void *)&authenticator))
return PAM_IGNORE;
if (NULL == authenticator && PAM_SUCCESS != pam_get_authtok(pamh, PAM_AUTHTOK, (void *)&authenticator, password_prompt))
return PAM_IGNORE;
if (PAM_SUCCESS != pam_setenv(pamh, "mount_authenticator", authenticator, 1)) {
return PAM_IGNORE;
}
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int retval = PAM_SUCCESS;
char *server_URL = NULL;
char *path = NULL;
char *homedir = NULL;
int mountdirlen = PATH_MAX;
char *mountdir = NULL;
const char *username;
const char *authenticator = NULL;
struct passwd *pwd = NULL;
unsigned int was_remounted = 0;
if (PAM_SUCCESS != (retval = pam_get_user(pamh, &username, NULL))) {
openpam_log(PAM_LOG_ERROR, "Unable to get the username: %s", pam_strerror(pamh, retval));
goto fin;
}
if (username == NULL || *username == '\0') {
openpam_log(PAM_LOG_ERROR, "Username is invalid.");
retval = PAM_PERM_DENIED;
goto fin;
}
if (NULL == (pwd = getpwnam(username))) {
openpam_log(PAM_LOG_ERROR, "Unknown user \"%s\".", username);
retval = PAM_SYSTEM_ERR;
goto fin;
}
if (NULL == (authenticator = pam_getenv(pamh, "mount_authenticator"))) {
openpam_log(PAM_LOG_DEBUG, "Unable to retrieve the authenticator.");
retval = PAM_IGNORE;
goto fin;
}
if (PAM_SUCCESS != (retval = extract_od_values(username, &server_URL, &path, &homedir))) {
openpam_log(PAM_LOG_ERROR, "Error retrieve data from OpenDirectory: %s", pam_strerror(pamh, retval));
goto fin;
}
openpam_log(PAM_LOG_DEBUG, " UID: %d", pwd->pw_uid);
openpam_log(PAM_LOG_DEBUG, " server_URL: %s", server_URL);
openpam_log(PAM_LOG_DEBUG, " path: %s", path);
openpam_log(PAM_LOG_DEBUG, " homedir: %s", homedir);
openpam_log(PAM_LOG_DEBUG, " username: %s", username);
if (0 == access(homedir, F_OK) || EACCES == errno) {
openpam_log(PAM_LOG_DEBUG, "The home folder share is already mounted.");
}
if (NULL == (mountdir = malloc(mountdirlen+1))) {
retval = PAM_IGNORE;
goto fin;
}
if (NULL != server_URL && retval == PAM_SUCCESS) {
if (NULL != path) {
if (0 != NetFSMountHomeDirectoryWithAuthentication(server_URL,
homedir,
path,
pwd->pw_uid,
mountdirlen,
mountdir,
username,
authenticator,
kNetFSAllowKerberos,
&was_remounted)) {
openpam_log(PAM_LOG_DEBUG, "Unable to mount the home folder.");
retval = PAM_SESSION_ERR;
goto fin;
}
else {
if (0 != was_remounted) {
openpam_log(PAM_LOG_DEBUG, "Remounted home folder.");
}
else {
openpam_log(PAM_LOG_DEBUG, "Mounted home folder.");
}
pam_set_data(pamh, "mountdir", mountdir, pam_malloc_cleanup);
mountdir = NULL;
}
}
if (NULL == path && NULL != homedir) {
CFStringRef password = CFStringCreateWithCString(NULL, authenticator, kCFStringEncodingUTF8);
CFStringRef url = CFStringCreateWithCString(NULL, server_URL, kCFStringEncodingUTF8);
CFURLRef dmgin = CFURLCreateWithString(NULL, url, NULL);
CFStringRef mountpoint = CFStringCreateWithCString(NULL, homedir, kCFStringEncodingUTF8);
CFStringRef devicepath;
CFStringRef mountpath;
if (0 != DIHLFVMount(dmgin, kDIHLFVCredUserPasswordType, password, mountpoint, NULL, NULL, NULL, &mountpath, &devicepath)) {
openpam_log(PAM_LOG_ERROR, "Unable to mount the FileVault home folder.");
retval = PAM_SESSION_ERR;
}
else {
openpam_log(PAM_LOG_DEBUG, "Mounted FileVault home folder.");
pam_set_data(pamh, "devicepath", (void *)devicepath, pam_cf_cleanup);
}
CFRelease(url);
CFRelease(password);
CFRelease(dmgin);
CFRelease(mountpoint);
}
}
fin:
if (NULL != authenticator)
pam_setenv(pamh, "mount_authenticator", "", 1);
free(mountdir);
free(server_URL);
free(path);
return retval;
}
PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int retval = PAM_SUCCESS;
const char *username;
char *mountdir = NULL;
CFStringRef devicepath = NULL;
retval = pam_get_user(pamh, &username, NULL);
if (retval != PAM_SUCCESS) {
openpam_log(PAM_LOG_ERROR, "Unable to get the username: %s", pam_strerror(pamh, retval));
return retval;
}
if (username == NULL || *username == '\0') {
openpam_log(PAM_LOG_ERROR, "Username is invalid.");
return PAM_PERM_DENIED;
}
if (PAM_SUCCESS != pam_get_data(pamh, "devicepath", (void *)&devicepath)) {
openpam_log(PAM_LOG_DEBUG, "No cached devicepath in the PAM context.");
}
if (PAM_SUCCESS != pam_get_data(pamh, "mountdir", (void *)&mountdir)) {
openpam_log(PAM_LOG_DEBUG, "No cached mountdir in the PAM context.");
}
if (NULL == devicepath && NULL != mountdir) {
if (0 != UnmountServerURL(mountdir, 0)) {
openpam_log(PAM_LOG_DEBUG, "Unable to unmount the home folder: %s.", strerror(errno));
retval = PAM_IGNORE;
}
else {
openpam_log(PAM_LOG_DEBUG, "Unmounted home folder.");
}
}
if (NULL != devicepath) {
if (0 != (retval = DIHLFVUnmount(devicepath, kDIHLFVUnmountNormally, kDIHLFVUnmountNoTimeout))) {
openpam_log(PAM_LOG_DEBUG, "Unable to unmount the FileVault home folder: %s.", DIStrError(retval));
retval = PAM_IGNORE;
}
else {
openpam_log(PAM_LOG_DEBUG, "Unmounted FileVault home folder.");
}
}
return retval;
}