webdav_authcache.c [plain text]
#include "webdavd.h"
#include <sys/types.h>
#include <pthread.h>
#include "webdav_authcache.h"
#include "webdav_network.h"
LIST_HEAD(authcache_head, authcache_entry);
struct authcache_entry
{
LIST_ENTRY(authcache_entry) entries;
uid_t uid;
CFHTTPAuthenticationRef auth;
CFStringRef username;
CFStringRef password;
CFStringRef domain;
u_int32_t authflags;
};
enum
{
kAuthNone = 0x00000000,
kCredentialsFromMount = 0x00000001,
kNoMountCredentials = 0x00000002,
kCredentialsValid = 0x00000004
};
static pthread_mutex_t authcache_lock;
static u_int32_t authcache_generation = 1;
static struct authcache_head authcache_list;
static struct authcache_entry *authcache_proxy_entry = NULL;
static CFStringRef mount_username = NULL;
static CFStringRef mount_password = NULL;
static CFStringRef mount_proxy_username = NULL;
static CFStringRef mount_proxy_password = NULL;
static CFStringRef mount_domain = NULL;
static
char *CopyCFStringToCString(CFStringRef theString);
static
void ReleaseCredentials(struct authcache_entry *entry_ptr);
static
void RemoveAuthentication(struct authcache_entry *entry_ptr)
{
if ( entry_ptr != authcache_proxy_entry )
{
LIST_REMOVE(entry_ptr, entries);
}
else
{
authcache_proxy_entry = NULL;
}
++authcache_generation;
if ( authcache_generation == 0 )
{
++authcache_generation;
}
if ( entry_ptr->auth != NULL )
{
CFRelease(entry_ptr->auth);
entry_ptr->auth = NULL;
}
ReleaseCredentials(entry_ptr);
free(entry_ptr);
}
static
void ReleaseCredentials(
struct authcache_entry *entry_ptr)
{
if (entry_ptr->username != NULL)
{
CFRelease(entry_ptr->username);
entry_ptr->username = NULL;
}
if (entry_ptr->password != NULL)
{
CFRelease(entry_ptr->password);
entry_ptr->password = NULL;
}
if (entry_ptr->domain != NULL)
{
CFRelease(entry_ptr->domain);
entry_ptr->domain = NULL;
}
}
static
void SetCredentials(
struct authcache_entry *entry_ptr,
CFStringRef new_username,
CFStringRef new_password,
CFStringRef new_domain)
{
entry_ptr->username = new_username;
entry_ptr->password = new_password;
entry_ptr->domain = new_domain;
}
static
char *CopyCFStringToCString(CFStringRef theString)
{
char *cstring;
CFIndex usedBufLen;
CFIndex converted;
CFRange range;
range = CFRangeMake(0, CFStringGetLength(theString));
converted = CFStringGetBytes(theString, range, kCFStringEncodingUTF8, 0, false, NULL, 0, &usedBufLen);
cstring = malloc(usedBufLen + 1);
if ( cstring != NULL )
{
converted = CFStringGetBytes(theString, range, kCFStringEncodingUTF8, 0, false, (UInt8 *)cstring, usedBufLen, &usedBufLen);
cstring[usedBufLen] = '\0';
}
return ( cstring );
}
static
int CopyMountCredentials(
CFHTTPAuthenticationRef auth,
CFStringRef *username,
CFStringRef *password,
CFStringRef *domain,
int *secureAuth)
{
int result;
CFStringRef method;
if ( gSecureConnection )
{
*secureAuth = TRUE;
}
else
{
method = CFHTTPAuthenticationCopyMethod(auth);
if ( method != NULL )
{
*secureAuth = !CFEqual(method, CFSTR("Basic"));
CFRelease(method);
}
else
{
*secureAuth = FALSE;
}
}
require_action_quiet(!gSecureServerAuth || (*secureAuth), SecureServerAuthRequired, result = EACCES);
if ( mount_username != NULL )
{
CFRetain(mount_username);
*username = mount_username;
if ( mount_password != NULL )
{
CFRetain(mount_password);
}
*password = mount_password;
if ( mount_domain != NULL )
{
CFRetain(mount_domain);
}
*domain = mount_domain;
result = 0;
}
else
{
result = 1;
}
SecureServerAuthRequired:
return ( result );
}
static
int CopyMountProxyCredentials(CFHTTPAuthenticationRef auth,
CFStringRef *username,
CFStringRef *password,
CFStringRef *domain,
int *secureAuth)
{
#pragma unused(domain)
int result = 1;
CFStringRef method;
if (auth == NULL) {
syslog(LOG_ERR, "auth object arg is NULL");
return result;
}
if (username == NULL) {
syslog(LOG_ERR, "user arg is NULL");
return result;
}
if (password == NULL) {
syslog(LOG_ERR, "credential arg is NULL");
return result;
}
if (domain == NULL) {
syslog(LOG_ERR, "domain arg is NULL");
return result;
}
if (secureAuth == NULL) {
syslog(LOG_ERR, "secureAuth object is NULL");
return result;
}
if ( gSecureConnection )
{
*secureAuth = TRUE;
}
else
{
method = CFHTTPAuthenticationCopyMethod(auth);
if ( method != NULL )
{
*secureAuth = !CFEqual(method, CFSTR("Basic"));
CFRelease(method);
}
else
{
*secureAuth = FALSE;
}
}
if ( mount_proxy_username != NULL )
{
CFRetain(mount_proxy_username);
*username = mount_proxy_username;
if ( mount_proxy_password != NULL )
{
CFRetain(mount_proxy_password);
}
*password = mount_proxy_password;
result = 0;
}
return ( result );
}
static
int AddServerCredentials(
struct authcache_entry *entry_ptr,
CFHTTPMessageRef request)
{
int result;
CFStringRef username;
CFStringRef password;
CFStringRef domain;
int secureAuth;
username = password = domain = NULL;
result = EACCES;
if ( CFHTTPAuthenticationRequiresUserNameAndPassword(entry_ptr->auth) )
{
if (entry_ptr->authflags & kCredentialsFromMount)
{
entry_ptr->authflags |= kNoMountCredentials;
}
if ( (entry_ptr->authflags & kNoMountCredentials) == 0 )
{
if ( CopyMountCredentials(entry_ptr->auth, &username, &password, &domain, &secureAuth) == 0 )
{
ReleaseCredentials(entry_ptr);
SetCredentials(entry_ptr, username, password, domain);
entry_ptr->authflags |= kCredentialsFromMount;
result = 0;
}
else
{
syslog(LOG_DEBUG, "AddServerCred: no mount creds, req %p", request);
entry_ptr->authflags |= kNoMountCredentials;
result = EACCES;
}
}
if ( (result == 0) && !(secureAuth) )
{
CFURLRef url;
CFStringRef urlString;
char *urlStr;
urlStr = NULL;
url = CFHTTPMessageCopyRequestURL(request);
if ( url != NULL )
{
urlString = CFURLGetString(url);
if ( urlString != NULL )
{
urlStr = CopyCFStringToCString(urlString);
}
else
{
urlStr = NULL;
}
CFRelease(url);
}
syslog(LOG_ERR | LOG_AUTHPRIV, "WebDAV FS authentication credentials are being sent insecurely to: %s", (urlStr ? urlStr : ""));
if ( urlStr != NULL )
{
free(urlStr);
}
}
}
else
{
result = 0;
}
return ( result );
}
static
int AddProxyCredentials(struct authcache_entry *entry_ptr, CFHTTPMessageRef request)
{
int result;
CFStringRef username;
CFStringRef password;
CFStringRef domain;
int secureAuth;
if ( CFHTTPAuthenticationRequiresUserNameAndPassword(entry_ptr->auth) )
{
username = password = domain = NULL;
result = EACCES;
if (entry_ptr->authflags & kCredentialsFromMount)
{
entry_ptr->authflags |= kNoMountCredentials;
}
if ( (entry_ptr->authflags & kNoMountCredentials) == 0 )
{
if ( CopyMountProxyCredentials(entry_ptr->auth, &username, &password, &domain, &secureAuth) == 0 )
{
ReleaseCredentials(entry_ptr);
SetCredentials(entry_ptr, username, password, domain);
entry_ptr->authflags |= kCredentialsFromMount;
result = 0;
}
else
{
syslog(LOG_DEBUG, "AddProxyCredentials: no mount creds, req %p", request);
entry_ptr->authflags |= kNoMountCredentials;
}
}
if ( (result == 0) && !(secureAuth) )
{
CFURLRef url;
CFStringRef urlString;
char *urlStr;
urlStr = NULL;
url = CFHTTPMessageCopyRequestURL(request);
if ( url != NULL )
{
urlString = CFURLGetString(url);
if ( urlString != NULL )
{
urlStr = CopyCFStringToCString(urlString);
}
else
{
urlStr = NULL;
}
CFRelease(url);
}
syslog(LOG_ERR | LOG_AUTHPRIV, "WebDAV FS authentication proxy credentials are being sent insecurely to: %s", (urlStr ? urlStr : ""));
if ( urlStr != NULL )
{
free(urlStr);
}
}
}
else {
result = 0;
}
return ( result );
}
static
struct authcache_entry *CreateProxyAuthenticationFromResponse(
uid_t uid,
CFHTTPMessageRef request,
CFHTTPMessageRef response)
{
struct authcache_entry *entry_ptr;
int result;
entry_ptr = calloc(sizeof(struct authcache_entry), 1);
require(entry_ptr != NULL, calloc);
entry_ptr->uid = uid;
entry_ptr->auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
require(entry_ptr->auth != NULL, CFHTTPAuthenticationCreateFromResponse);
require(CFHTTPAuthenticationIsValid(entry_ptr->auth, NULL), CFHTTPAuthenticationIsValid);
result = AddProxyCredentials(entry_ptr, request);
require_noerr_quiet(result, AddProxyCredentials);
authcache_proxy_entry = entry_ptr;
++authcache_generation;
if ( authcache_generation == 0 )
{
++authcache_generation;
}
return ( entry_ptr );
AddProxyCredentials:
CFHTTPAuthenticationIsValid:
CFRelease(entry_ptr->auth);
CFHTTPAuthenticationCreateFromResponse:
free(entry_ptr);
calloc:
return ( NULL );
}
static
struct authcache_entry *CreateAuthenticationFromResponse(
uid_t uid,
CFHTTPMessageRef request,
CFHTTPMessageRef response,
int isProxy)
{
struct authcache_entry *entry_ptr;
int result;
entry_ptr = calloc(1, sizeof(struct authcache_entry));
require(entry_ptr != NULL, calloc);
entry_ptr->uid = uid;
entry_ptr->auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
require(entry_ptr->auth != NULL, CFHTTPAuthenticationCreateFromResponse);
require(CFHTTPAuthenticationIsValid(entry_ptr->auth, NULL), CFHTTPAuthenticationIsValid);
if ( !isProxy )
{
result = AddServerCredentials(entry_ptr, request);
require_noerr_quiet(result, AddServerCredentials);
LIST_INSERT_HEAD(&authcache_list, entry_ptr, entries);
}
else
{
result = AddProxyCredentials(entry_ptr, request);
require_noerr_quiet(result, AddProxyCredentials);
}
++authcache_generation;
if ( authcache_generation == 0 )
{
++authcache_generation;
}
return ( entry_ptr );
AddProxyCredentials:
AddServerCredentials:
CFHTTPAuthenticationIsValid:
CFRelease(entry_ptr->auth);
CFHTTPAuthenticationCreateFromResponse:
free(entry_ptr);
calloc:
return ( NULL );
}
static
struct authcache_entry *FindAuthenticationForRequest(
uid_t uid,
CFHTTPMessageRef request)
{
struct authcache_entry *entry_ptr;
LIST_FOREACH(entry_ptr, &authcache_list, entries)
{
if ( (entry_ptr->uid == uid) || (0 == uid) )
{
if ( CFHTTPAuthenticationAppliesToRequest(entry_ptr->auth, request) )
{
break;
}
}
}
return ( entry_ptr );
}
static int ApplyCredentialsToRequest(struct authcache_entry *entry_ptr, CFHTTPMessageRef request)
{
int result;
if ( entry_ptr->domain != NULL ) {
CFMutableDictionaryRef dict;
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require_action(dict != NULL, CFDictionaryCreateMutable, result = FALSE);
if ( entry_ptr->username != NULL ) {
CFDictionaryAddValue(dict, kCFHTTPAuthenticationUsername, entry_ptr->username);
}
if ( entry_ptr->password != NULL ) {
CFDictionaryAddValue(dict, kCFHTTPAuthenticationPassword, entry_ptr->password);
}
if ( entry_ptr->domain != NULL ) {
CFDictionaryAddValue(dict, kCFHTTPAuthenticationAccountDomain, entry_ptr->domain);
}
result = CFHTTPMessageApplyCredentialDictionary(request, entry_ptr->auth, dict, NULL);
CFRelease(dict);
}
else {
result = CFHTTPMessageApplyCredentials(request, entry_ptr->auth, entry_ptr->username, entry_ptr->password, NULL);
}
CFDictionaryCreateMutable:
return ( result );
}
static
int AddExistingAuthentications(
uid_t uid,
CFHTTPMessageRef request)
{
struct authcache_entry *entry_ptr;
entry_ptr = FindAuthenticationForRequest(uid, request);
if ( entry_ptr != NULL )
{
if ( CFHTTPAuthenticationIsValid(entry_ptr->auth, NULL) )
{
if ( !ApplyCredentialsToRequest(entry_ptr, request) )
{
RemoveAuthentication(entry_ptr);
}
}
else
{
RemoveAuthentication(entry_ptr);
}
}
if ( authcache_proxy_entry != NULL )
{
if ( !CFHTTPAuthenticationIsValid(authcache_proxy_entry->auth, NULL) || !ApplyCredentialsToRequest(authcache_proxy_entry, request) )
{
RemoveAuthentication(authcache_proxy_entry);
}
}
return ( 0 );
}
static
int DoServerAuthentication(
uid_t uid,
CFHTTPMessageRef request,
CFHTTPMessageRef response)
{
struct authcache_entry *entry_ptr;
int result = 0;
entry_ptr = FindAuthenticationForRequest(uid, request);
if ( entry_ptr != NULL )
{
entry_ptr->authflags &= ~kCredentialsValid;
if ( CFHTTPAuthenticationIsValid(entry_ptr->auth, NULL) )
{
result = 0;
}
else
{
CFRelease(entry_ptr->auth);
entry_ptr->auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
if ( entry_ptr->auth != NULL )
{
if ( CFHTTPAuthenticationIsValid(entry_ptr->auth, NULL) )
{
result = AddServerCredentials(entry_ptr, request);
}
else
{
result = EACCES;
}
}
else
{
result = EACCES;
}
}
if ( result != 0 )
{
RemoveAuthentication(entry_ptr);
}
}
else
{
entry_ptr = CreateAuthenticationFromResponse(uid, request, response, FALSE);
if ( entry_ptr != NULL )
{
result = 0;
}
else
{
result = EACCES;
}
}
return ( result );
}
static
int AddProxyAuthentication(
uid_t uid,
CFHTTPMessageRef request,
CFHTTPMessageRef response)
{
int result;
if ( authcache_proxy_entry != NULL )
{
authcache_proxy_entry->authflags &= ~kCredentialsValid;
if ( CFHTTPAuthenticationIsValid(authcache_proxy_entry->auth, NULL) )
{
result = 0;
}
else
{
CFRelease(authcache_proxy_entry->auth);
authcache_proxy_entry->auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
if ( authcache_proxy_entry->auth != NULL )
{
if ( CFHTTPAuthenticationIsValid(authcache_proxy_entry->auth, NULL) )
{
result = AddProxyCredentials(authcache_proxy_entry, request);
}
else
{
result = EACCES;
}
}
else
{
result = EACCES;
}
}
if ( result != 0 )
{
RemoveAuthentication(authcache_proxy_entry);
}
}
else
{
authcache_proxy_entry = CreateProxyAuthenticationFromResponse(uid, request, response);
if ( authcache_proxy_entry != NULL )
{
result = 0;
}
else
{
result = EACCES;
}
}
return ( result );
}
int authcache_apply(
uid_t uid,
CFHTTPMessageRef request,
UInt32 statusCode,
CFHTTPMessageRef response,
UInt32 *generation)
{
int result, result2;
result = pthread_mutex_lock(&authcache_lock);
require_noerr_action(result, pthread_mutex_lock, webdav_kill(-1));
switch (statusCode)
{
case 0:
result = 0;
break;
case 401:
if ( (gProcessUID == uid) || (0 == uid) )
{
result = DoServerAuthentication(uid, request, response);
}
else
{
result = EACCES;
}
break;
case 407:
if ( (gProcessUID == uid) || (0 == uid) )
{
result = AddProxyAuthentication(uid, request, response);
}
else
{
result = EACCES;
}
break;
default:
result = EACCES;
break;
}
if ( (result == 0) && ((gProcessUID == uid) || (0 == uid)) )
{
result = AddExistingAuthentications(uid, request);
}
*generation = authcache_generation;
result2 = pthread_mutex_unlock(&authcache_lock);
require_noerr_action(result2, pthread_mutex_unlock, result = result2; webdav_kill(-1));
pthread_mutex_unlock:
pthread_mutex_lock:
return ( result );
}
int authcache_valid(
uid_t uid,
CFHTTPMessageRef request,
UInt32 generation)
{
int result, result2;
require_quiet(((gProcessUID == uid) || (0 == uid)), not_owner_uid);
result = pthread_mutex_lock(&authcache_lock);
require_noerr_action(result, pthread_mutex_lock, webdav_kill(-1));
if ( generation == authcache_generation )
{
struct authcache_entry *entry_ptr;
entry_ptr = FindAuthenticationForRequest(uid, request);
if ( entry_ptr != NULL )
{
entry_ptr->authflags |= kCredentialsValid;
}
if ( authcache_proxy_entry != NULL )
{
authcache_proxy_entry->authflags |= kCredentialsValid;
}
}
result2 = pthread_mutex_unlock(&authcache_lock);
require_noerr_action(result2, pthread_mutex_unlock, result = result2; webdav_kill(-1));
pthread_mutex_unlock:
pthread_mutex_lock:
not_owner_uid:
return ( 0 );
}
int authcache_proxy_invalidate(void)
{
int result, result2;
result = pthread_mutex_lock(&authcache_lock);
require_noerr_action(result, pthread_mutex_lock, webdav_kill(-1));
if ( authcache_proxy_entry != NULL )
{
RemoveAuthentication(authcache_proxy_entry);
}
result2 = pthread_mutex_unlock(&authcache_lock);
require_noerr_action(result2, pthread_mutex_unlock, result = result2; webdav_kill(-1));
pthread_mutex_unlock:
pthread_mutex_lock:
return ( result );
}
int authcache_init(
char *username,
char *password,
char *proxy_username,
char *proxy_password,
char *domain)
{
int result;
pthread_mutexattr_t mutexattr;
result = pthread_mutexattr_init(&mutexattr);
require_noerr(result, pthread_mutexattr_init);
result = pthread_mutex_init(&authcache_lock, &mutexattr);
require_noerr(result, pthread_mutex_init);
LIST_INIT(&authcache_list);
authcache_generation = 1;
result = 0;
if ( username != NULL && password != NULL && username[0] != '\0')
{
mount_username = CFStringCreateWithCString(kCFAllocatorDefault, username, kCFStringEncodingUTF8);
require_action(mount_username != NULL, CFStringCreateWithCString, result = ENOMEM);
mount_password = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8);
require_action(mount_password != NULL, CFStringCreateWithCString, result = ENOMEM);
}
if ( proxy_username != NULL && proxy_password != NULL && proxy_username[0] != '\0')
{
mount_proxy_username = CFStringCreateWithCString(kCFAllocatorDefault, proxy_username, kCFStringEncodingUTF8);
require_action(mount_proxy_username != NULL, CFStringCreateWithCString, result = ENOMEM);
mount_proxy_password = CFStringCreateWithCString(kCFAllocatorDefault, proxy_password, kCFStringEncodingUTF8);
require_action(mount_proxy_password != NULL, CFStringCreateWithCString, result = ENOMEM);
}
if ( domain != NULL && domain[0] != '\0' )
{
mount_domain = CFStringCreateWithCString(kCFAllocatorDefault, domain, kCFStringEncodingUTF8);
require_action(mount_domain != NULL, CFStringCreateWithCString, result = ENOMEM);
}
CFStringCreateWithCString:
pthread_mutex_init:
pthread_mutexattr_init:
return ( result );
}