2023-01-09 01:22:54 +01:00
|
|
|
#include "auth.h"
|
2023-03-09 01:14:10 +01:00
|
|
|
#include "hex.h"
|
2023-01-09 01:22:54 +01:00
|
|
|
#include "jwt.h"
|
2023-10-10 23:43:47 +02:00
|
|
|
#include <libweb/http.h>
|
2023-01-09 01:22:54 +01:00
|
|
|
#include <cjson/cJSON.h>
|
|
|
|
#include <dynstr.h>
|
|
|
|
#include <openssl/sha.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdlib.h>
|
2023-09-15 22:34:07 +02:00
|
|
|
#include <stdint.h>
|
2023-01-09 01:22:54 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
struct auth
|
|
|
|
{
|
|
|
|
struct dynstr dir, db;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {KEYLEN = 32};
|
|
|
|
|
|
|
|
static char *dump_db(const char *const path)
|
|
|
|
{
|
|
|
|
char *ret = NULL;
|
|
|
|
FILE *f = NULL;
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
if (stat(path, &sb))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
2023-05-28 12:57:21 +02:00
|
|
|
else if (sb.st_size > SIZE_MAX - 1)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: %s too big (%llu bytes, %zu max)\n",
|
|
|
|
__func__, path, (unsigned long long)sb.st_size, (size_t)SIZE_MAX);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(f = fopen(path, "rb")))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(ret = malloc(sb.st_size + 1)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!fread(ret, sb.st_size, 1, f))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: failed to dump %zu bytes, ferror=%d\n",
|
|
|
|
__func__, (size_t)sb.st_size, ferror(f));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret[sb.st_size] = '\0';
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
|
|
if (f && fclose(f))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
|
|
|
free(ret);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int find_cookie(const cJSON *const users, const char *const cookie)
|
|
|
|
{
|
|
|
|
const cJSON *u;
|
|
|
|
|
|
|
|
cJSON_ArrayForEach(u, users)
|
|
|
|
{
|
|
|
|
const cJSON *const n = cJSON_GetObjectItem(u, "name"),
|
|
|
|
*const k = cJSON_GetObjectItem(u, "key");
|
|
|
|
const char *name, *key;
|
|
|
|
unsigned char dkey[KEYLEN];
|
|
|
|
|
|
|
|
if (!n || !(name = cJSON_GetStringValue(n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing username\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (!k || !(key = cJSON_GetStringValue(k)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing key\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-03-09 01:14:10 +01:00
|
|
|
else if (hex_decode(key, dkey, sizeof dkey))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-09 01:14:10 +01:00
|
|
|
fprintf(stderr, "%s: hex_decode failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int res = jwt_check(cookie, dkey, sizeof dkey);
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return 0;
|
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: jwt_decode failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int auth_cookie(const struct auth *const a, const struct http_cookie *const c)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
char *db = NULL;
|
|
|
|
cJSON *json = NULL;
|
|
|
|
|
|
|
|
if (!c->field || !c->value)
|
|
|
|
return 1;
|
|
|
|
else if (!(db = dump_db(a->db.str)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dump_db failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(json = cJSON_Parse(db)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: cJSON_Parse failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const cJSON *const users = cJSON_GetObjectItem(json, "users");
|
|
|
|
|
|
|
|
if (!users)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: could not find users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!cJSON_IsArray(users))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected JSON array for users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = find_cookie(users, c->value)) < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: find_cookie failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(db);
|
|
|
|
cJSON_Delete(json);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int generate_cookie(const cJSON *const json, const char *const path,
|
|
|
|
const char *const name, const char *const key, char **const cookie)
|
|
|
|
{
|
|
|
|
unsigned char dkey[KEYLEN];
|
|
|
|
int ret = -1;
|
|
|
|
char *jwt = NULL;
|
|
|
|
|
2023-03-09 01:14:10 +01:00
|
|
|
if (hex_decode(key, dkey, sizeof dkey))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-09 01:14:10 +01:00
|
|
|
fprintf(stderr, "%s: hex_decode failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(jwt = jwt_encode(name, dkey, sizeof dkey)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: jwt_encode failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(*cookie = http_cookie_create(name, jwt)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_cookie_create failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(jwt);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compare_pwd(const char *const salt, const char *const password,
|
|
|
|
const char *const exp_password)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
enum {SALT_LEN = SHA256_DIGEST_LENGTH};
|
|
|
|
unsigned char dec_salt[SALT_LEN], sha256[SHA256_DIGEST_LENGTH];
|
|
|
|
const size_t slen = strlen(salt),
|
|
|
|
len = strlen(password), n = sizeof dec_salt + len;
|
|
|
|
unsigned char *const salted = malloc(n);
|
|
|
|
|
|
|
|
if (slen != SALT_LEN * 2)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unexpected salt length: %zu\n", __func__, slen);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!salted)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
2023-03-09 01:14:10 +01:00
|
|
|
else if (hex_decode(salt, dec_salt, sizeof dec_salt))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-09 01:14:10 +01:00
|
|
|
fprintf(stderr, "%s: hex_decode failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(salted, dec_salt, sizeof dec_salt);
|
|
|
|
memcpy(salted + sizeof dec_salt, password, len);
|
|
|
|
|
|
|
|
if (!SHA256(salted, n, sha256))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: SHA256 (first round) failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum {ROUNDS = 1000 - 1};
|
|
|
|
|
|
|
|
for (int i = 0; i < ROUNDS; i++)
|
|
|
|
if (!SHA256(sha256, sizeof sha256, sha256))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: SHA256 failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
char sha256_str[sizeof
|
|
|
|
"00000000000000000000000000000000"
|
|
|
|
"00000000000000000000000000000000"];
|
|
|
|
|
|
|
|
for (struct {char *p; size_t i;} a = {.p = sha256_str};
|
|
|
|
a.i < sizeof sha256 / sizeof *sha256; a.i++, a.p += 2)
|
|
|
|
sprintf(a.p, "%02x", sha256[a.i]);
|
|
|
|
|
|
|
|
if (!strcmp(sha256_str, exp_password))
|
|
|
|
ret = 0;
|
|
|
|
else
|
|
|
|
/* Positive error code for password mismatch. */
|
|
|
|
ret = 1;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(salted);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int auth_login(const struct auth *const a, const char *const user,
|
|
|
|
const char *const password, char **const cookie)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const char *const path = a->db.str;
|
|
|
|
char *const db = dump_db(path);
|
|
|
|
cJSON *json = NULL;
|
|
|
|
|
|
|
|
if (!db)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dump_db failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(json = cJSON_Parse(db)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: cJSON_Parse failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const cJSON *const users = cJSON_GetObjectItem(json, "users");
|
|
|
|
|
|
|
|
if (!users)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: could not find users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!cJSON_IsArray(users))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected JSON array for users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-03-06 05:24:13 +01:00
|
|
|
const cJSON *u;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
cJSON_ArrayForEach(u, users)
|
|
|
|
{
|
|
|
|
const cJSON *const n = cJSON_GetObjectItem(u, "name"),
|
|
|
|
*const s = cJSON_GetObjectItem(u, "salt"),
|
|
|
|
*const p = cJSON_GetObjectItem(u, "password"),
|
|
|
|
*const k = cJSON_GetObjectItem(u, "key");
|
|
|
|
const char *name, *salt, *pwd, *key;
|
|
|
|
|
|
|
|
if (!n || !(name = cJSON_GetStringValue(n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing username\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!s || !(salt = cJSON_GetStringValue(s)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing salt\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!p || !(pwd = cJSON_GetStringValue(p)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing password\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!k || !(key = cJSON_GetStringValue(k)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing key\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!strcmp(name, user))
|
|
|
|
{
|
|
|
|
const int res = compare_pwd(salt, password, pwd);
|
|
|
|
|
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: generate_cookie failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!res)
|
|
|
|
{
|
|
|
|
if (generate_cookie(json, path, name, key, cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: generate_cookie failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 1;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(db);
|
|
|
|
cJSON_Delete(json);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void auth_free(struct auth *const a)
|
|
|
|
{
|
|
|
|
if (a)
|
|
|
|
{
|
|
|
|
dynstr_free(&a->dir);
|
|
|
|
dynstr_free(&a->db);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *auth_dir(const struct auth *const a)
|
|
|
|
{
|
|
|
|
return a->dir.str;
|
|
|
|
}
|
|
|
|
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
int auth_quota(const struct auth *const a, const char *const user,
|
|
|
|
bool *const available, unsigned long long *const quota)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const char *const path = a->db.str;
|
|
|
|
char *const db = dump_db(path);
|
|
|
|
cJSON *json = NULL;
|
|
|
|
|
|
|
|
if (!db)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dump_db failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(json = cJSON_Parse(db)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: cJSON_Parse failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const cJSON *const users = cJSON_GetObjectItem(json, "users");
|
|
|
|
|
|
|
|
if (!users)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: could not find users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!cJSON_IsArray(users))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected JSON array for users\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
*available = false;
|
|
|
|
|
|
|
|
const cJSON *u;
|
|
|
|
|
|
|
|
cJSON_ArrayForEach(u, users)
|
|
|
|
{
|
|
|
|
const cJSON *const n = cJSON_GetObjectItem(u, "name"),
|
|
|
|
*const q = cJSON_GetObjectItem(u, "quota");
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
if (!n || !(name = cJSON_GetStringValue(n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing username\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!strcmp(name, user))
|
|
|
|
{
|
|
|
|
const char *qs;
|
|
|
|
|
|
|
|
if (!q || !(qs = cJSON_GetStringValue(q)) || !*qs)
|
|
|
|
{
|
|
|
|
/* Unlimited quota. */
|
|
|
|
ret = 0;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *end;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
*available = true;
|
|
|
|
*quota = strtoull(qs, &end, 10);
|
|
|
|
|
2023-10-14 13:05:58 +02:00
|
|
|
const unsigned long long mul = 1024ul * 1024ul;
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
|
|
|
|
if (errno || *end != '\0')
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid quota %s: %s\n",
|
|
|
|
__func__, qs, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (*quota >= ULLONG_MAX / mul)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: quota %s too large\n", __func__, qs);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-10-14 13:05:58 +02:00
|
|
|
*quota *= mul;
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(db);
|
|
|
|
cJSON_Delete(json);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int create_db(const char *const path)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const int fd = open(path, O_WRONLY | O_CREAT, 0600);
|
|
|
|
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char db[] = "{\"users\": []}\n";
|
|
|
|
|
|
|
|
for (struct {size_t n; const void *p;}
|
|
|
|
a = {.n = strlen(db), .p = db}; a.n;)
|
|
|
|
{
|
|
|
|
const ssize_t n = write(fd, a.p, a.n);
|
|
|
|
|
|
|
|
if (n < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.n -= n;
|
|
|
|
a.p = ((const char *)a.p) + n;
|
|
|
|
}
|
|
|
|
|
2023-03-16 01:59:17 +01:00
|
|
|
printf("Created login database at %s. Please remember to add new users "
|
|
|
|
"with usergen(1).\n", path);
|
2023-01-09 01:22:54 +01:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
|
|
if (fd >= 0 && close(fd))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init_db(struct auth *const a)
|
|
|
|
{
|
|
|
|
struct stat sb;
|
|
|
|
const char *const path = a->db.str;
|
|
|
|
|
|
|
|
if (stat(path, &sb))
|
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
return create_db(path);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-25 09:57:19 +01:00
|
|
|
static char *resolve_cwd(void)
|
|
|
|
{
|
|
|
|
size_t len = 1;
|
|
|
|
char *p = NULL;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
char *const pp = realloc(p, len);
|
|
|
|
|
|
|
|
if (!pp)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = pp;
|
|
|
|
|
|
|
|
if (!getcwd(pp, len))
|
|
|
|
{
|
|
|
|
if (errno != ERANGE)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: getcwd(3): %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
len++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(p);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
struct auth *auth_alloc(const char *const dir)
|
|
|
|
{
|
2023-03-25 09:57:19 +01:00
|
|
|
struct auth *const a = malloc(sizeof *a), *ret = NULL;
|
|
|
|
char *abspath = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (!a)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3) auth: %s\n", __func__, strerror(errno));
|
2023-03-25 09:57:19 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
*a = (const struct auth){0};
|
|
|
|
|
|
|
|
dynstr_init(&a->db);
|
|
|
|
dynstr_init(&a->dir);
|
|
|
|
|
2023-03-25 09:57:19 +01:00
|
|
|
if (*dir != '/' && !(abspath = resolve_cwd()))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-25 09:57:19 +01:00
|
|
|
fprintf(stderr, "%s: resolve_cwd failed\n", __func__);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-03-25 09:57:19 +01:00
|
|
|
else if (abspath && dynstr_append(&a->dir, "%s", abspath))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-25 09:57:19 +01:00
|
|
|
fprintf(stderr, "%s: dynstr_append abspath failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&a->dir, "%s%s", abspath ? "/" : "", dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&a->db, "%s/db.json", a->dir.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append db failed\n", __func__);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
else if (init_db(a))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: init_db failed\n", __func__);
|
2023-03-25 09:57:19 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-03-25 09:57:19 +01:00
|
|
|
ret = a;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-03-25 09:57:19 +01:00
|
|
|
end:
|
|
|
|
free(abspath);
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
auth_free(a);
|
|
|
|
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|