2025-11-25 21:24:12 +01:00
|
|
|
#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) {
|
|
|
|
|
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);
|
2025-11-28 03:05:51 +01:00
|
|
|
auth->realm[sizeof(auth->realm) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
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");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(username, tmp, 63); username[63] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "realm");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(realm, tmp, 127); realm[127] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "nonce");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(nonce, tmp, 63); nonce[63] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "uri");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(uri, tmp, 255); uri[255] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "nc");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(nc, tmp, 15); nc[15] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "cnonce");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(cnonce, tmp, 63); cnonce[63] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "qop");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(qop, tmp, 15); qop[15] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
tmp = extract_digest_param(header, "response");
|
2025-11-28 03:05:51 +01:00
|
|
|
if (tmp) { strncpy(response, tmp, 63); response[63] = '\0'; free(tmp); }
|
2025-11-25 21:24:12 +01:00
|
|
|
|
|
|
|
|
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));
|
2025-11-28 03:05:51 +01:00
|
|
|
strncpy(user->username, "anonymous", sizeof(user->username) - 1);
|
|
|
|
|
user->username[sizeof(user->username) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
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);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->username[sizeof(user->username) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->email[sizeof(user->email) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->display_name[sizeof(user->display_name) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
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);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->username[sizeof(user->username) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->email[sizeof(user->email) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->display_name[sizeof(user->display_name) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
|
2025-11-28 03:05:51 +01:00
|
|
|
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
|
2025-11-25 21:24:12 +01:00
|
|
|
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;
|
|
|
|
|
}
|