#include "auth.h" #include "../platform/pal.h" #include "../platform/memory.h" #include "../user/usermgr.h" #include #include #ifdef _WIN32 #include #else #include #include #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; }