#include "auth.h"
#include "../platform/pal.h"
#include "../platform/memory.h"
#include "../user/usermgr.h"
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <wincrypt.h>
#else
#include <openssl/md5.h>
#include <openssl/rand.h>
#endif
struct auth_context {
user_manager_t *usermgr;
auth_method_t method;
char realm[128];
char current_nonce[64];
time_t nonce_time;
mutex_t mutex;
};
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char *auth_base64_encode(const unsigned char *data, size_t len) {
if (len > (SIZE_MAX / 4) - 1) return NULL;
size_t out_len = 4 * ((len + 2) / 3);
char *out = malloc(out_len + 1);
if (!out) return NULL;
size_t i, j;
for (i = 0, j = 0; i < len;) {
uint32_t octet_a = i < len ? data[i++] : 0;
uint32_t octet_b = i < len ? data[i++] : 0;
uint32_t octet_c = i < len ? data[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
out[j++] = base64_chars[(triple >> 18) & 0x3F];
out[j++] = base64_chars[(triple >> 12) & 0x3F];
out[j++] = base64_chars[(triple >> 6) & 0x3F];
out[j++] = base64_chars[triple & 0x3F];
}
int mod = len % 3;
if (mod > 0) {
for (int k = 0; k < 3 - mod; k++) {
out[out_len - 1 - k] = '=';
}
}
out[out_len] = '\0';
return out;
}
unsigned char *auth_base64_decode(const char *data, size_t *out_len) {
size_t len = strlen(data);
if (len % 4 != 0) return NULL;
*out_len = len / 4 * 3;
if (data[len - 1] == '=') (*out_len)--;
if (data[len - 2] == '=') (*out_len)--;
unsigned char *out = malloc(*out_len);
if (!out) return NULL;
static const unsigned char d[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64
};
size_t i, j;
for (i = 0, j = 0; i < len;) {
uint32_t a = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t b = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t c = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t e = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t triple = (a << 18) + (b << 12) + (c << 6) + e;
if (j < *out_len) out[j++] = (triple >> 16) & 0xFF;
if (j < *out_len) out[j++] = (triple >> 8) & 0xFF;
if (j < *out_len) out[j++] = triple & 0xFF;
}
return out;
}
auth_context_t *auth_create(const char *db_path, auth_method_t method, const char *realm) {
auth_context_t *auth = calloc(1, sizeof(auth_context_t));
if (!auth) return NULL;
auth->method = method;
strncpy(auth->realm, realm ? realm : "WebDAV", sizeof(auth->realm) - 1);
auth->realm[sizeof(auth->realm) - 1] = '\0';
if (pal_mutex_init(&auth->mutex) != NWEDAV_OK) {
free(auth);
return NULL;
}
auth->usermgr = user_manager_create(db_path);
if (!auth->usermgr) {
pal_mutex_destroy(&auth->mutex);
free(auth);
return NULL;
}
return auth;
}
void auth_destroy(auth_context_t *auth) {
if (!auth) return;
user_manager_destroy(auth->usermgr);
pal_mutex_destroy(&auth->mutex);
free(auth);
}
int auth_parse_basic(const char *header, char *username, size_t user_size,
char *password, size_t pass_size) {
if (!header || !username || !password) return NWEDAV_ERROR;
while (*header == ' ') header++;
if (str_ncasecmp(header, "Basic ", 6) != 0) return NWEDAV_ERROR;
header += 6;
while (*header == ' ') header++;
size_t decoded_len;
unsigned char *decoded = auth_base64_decode(header, &decoded_len);
if (!decoded) return NWEDAV_ERROR;
char *colon = memchr(decoded, ':', decoded_len);
if (!colon) {
free(decoded);
return NWEDAV_ERROR;
}
size_t user_len = colon - (char *)decoded;
size_t pass_len = decoded_len - user_len - 1;
if (user_len >= user_size || pass_len >= pass_size) {
free(decoded);
return NWEDAV_ERROR;
}
memcpy(username, decoded, user_len);
username[user_len] = '\0';
memcpy(password, colon + 1, pass_len);
password[pass_len] = '\0';
free(decoded);
return NWEDAV_OK;
}
static char *extract_digest_param(const char *header, const char *param) {
char search[64];
snprintf(search, sizeof(search), "%s=", param);
const char *start = strstr(header, search);
if (!start) return NULL;
start += strlen(search);
while (*start == ' ') start++;
if (*start == '"') {
start++;
const char *end = strchr(start, '"');
if (!end) return NULL;
return str_ndup(start, end - start);
} else {
const char *end = start;
while (*end && *end != ',' && *end != ' ') end++;
return str_ndup(start, end - start);
}
}
int auth_parse_digest(const char *header, char *username, char *realm,
char *nonce, char *uri, char *nc, char *cnonce,
char *qop, char *response) {
if (!header) return NWEDAV_ERROR;
while (*header == ' ') header++;
if (str_ncasecmp(header, "Digest ", 7) != 0) return NWEDAV_ERROR;
header += 7;
char *tmp;
tmp = extract_digest_param(header, "username");
if (tmp) { strncpy(username, tmp, 63); username[63] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "realm");
if (tmp) { strncpy(realm, tmp, 127); realm[127] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "nonce");
if (tmp) { strncpy(nonce, tmp, 63); nonce[63] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "uri");
if (tmp) { strncpy(uri, tmp, 255); uri[255] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "nc");
if (tmp) { strncpy(nc, tmp, 15); nc[15] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "cnonce");
if (tmp) { strncpy(cnonce, tmp, 63); cnonce[63] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "qop");
if (tmp) { strncpy(qop, tmp, 15); qop[15] = '\0'; free(tmp); }
tmp = extract_digest_param(header, "response");
if (tmp) { strncpy(response, tmp, 63); response[63] = '\0'; free(tmp); }
return NWEDAV_OK;
}
static void generate_nonce(char *nonce, size_t size) {
unsigned char rand_bytes[16];
#ifdef _WIN32
HCRYPTPROV hProv;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
CryptGenRandom(hProv, sizeof(rand_bytes), rand_bytes);
CryptReleaseContext(hProv, 0);
}
#else
RAND_bytes(rand_bytes, sizeof(rand_bytes));
#endif
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < 16 && (size_t)(i * 2 + 1) < size - 1; i++) {
nonce[i * 2] = hex[rand_bytes[i] >> 4];
nonce[i * 2 + 1] = hex[rand_bytes[i] & 0x0f];
}
nonce[32] = '\0';
}
int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user) {
if (!auth || !req || !user) return NWEDAV_ERROR;
if (auth->method == AUTH_METHOD_NONE) {
memset(user, 0, sizeof(auth_user_t));
strncpy(user->username, "anonymous", sizeof(user->username) - 1);
user->username[sizeof(user->username) - 1] = '\0';
return NWEDAV_OK;
}
if (!req->authorization) {
return NWEDAV_ERROR_AUTH;
}
if (auth->method == AUTH_METHOD_BASIC) {
char username[64] = {0};
char password[256] = {0};
if (auth_parse_basic(req->authorization, username, sizeof(username),
password, sizeof(password)) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user_t db_user;
if (user_manager_authenticate(auth->usermgr, username, password, &db_user) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user->id = db_user.id;
strncpy(user->username, db_user.username, sizeof(user->username) - 1);
user->username[sizeof(user->username) - 1] = '\0';
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
user->email[sizeof(user->email) - 1] = '\0';
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
user->display_name[sizeof(user->display_name) - 1] = '\0';
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
return NWEDAV_OK;
}
if (auth->method == AUTH_METHOD_DIGEST) {
char username[64] = {0};
char realm[128] = {0};
char nonce[64] = {0};
char uri[256] = {0};
char nc[16] = {0};
char cnonce[64] = {0};
char qop[16] = {0};
char response[64] = {0};
if (auth_parse_digest(req->authorization, username, realm, nonce, uri,
nc, cnonce, qop, response) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user_t db_user;
if (user_manager_get_by_username(auth->usermgr, username, &db_user) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user->id = db_user.id;
strncpy(user->username, db_user.username, sizeof(user->username) - 1);
user->username[sizeof(user->username) - 1] = '\0';
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
user->email[sizeof(user->email) - 1] = '\0';
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
user->display_name[sizeof(user->display_name) - 1] = '\0';
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
return NWEDAV_OK;
}
return NWEDAV_ERROR_AUTH;
}
int auth_build_challenge(auth_context_t *auth, http_response_t *res) {
if (!auth || !res) return NWEDAV_ERROR;
char header[512];
if (auth->method == AUTH_METHOD_BASIC) {
snprintf(header, sizeof(header), "Basic realm=\"%s\"", auth->realm);
} else if (auth->method == AUTH_METHOD_DIGEST) {
pal_mutex_lock(&auth->mutex);
time_t now = time(NULL);
if (now - auth->nonce_time > 300 || auth->current_nonce[0] == '\0') {
generate_nonce(auth->current_nonce, sizeof(auth->current_nonce));
auth->nonce_time = now;
}
snprintf(header, sizeof(header),
"Digest realm=\"%s\", nonce=\"%s\", qop=\"auth\", algorithm=MD5",
auth->realm, auth->current_nonce);
pal_mutex_unlock(&auth->mutex);
} else {
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_401_UNAUTHORIZED);
http_response_add_header(res, "WWW-Authenticate", header);
http_response_error(res, HTTP_401_UNAUTHORIZED, "Authentication required");
return NWEDAV_OK;
}