Files
hapiwiki/wiki/data.c

462 lines
10 KiB
C

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include "argon2/argon2.h"
#include "cgi/cgi.h"
#include "util/util.h"
#include "wiki/config.h"
#include "wiki/data.h"
static int is_valid_page(const char *page) {
const char *s;
if (page == NULL) {
return 0;
}
if (strcmp(".", page) == 0) {
return 0;
}
if (strcmp("..", page) == 0) {
return 0;
}
for (s = page; *s; s++) {
if (*s == '/') {
return 0;
}
}
return 1;
}
const char *wiki_data_text_read(const char *data_dir, const char *page) {
char path[WIKI_PATH_SIZE + 1];
struct stat st;
char *text;
FILE *fp;
if (!is_valid_page(page)) {
cgi_die("invalid page name");
}
if (strlen(data_dir) + 1 + strlen("pages") + 1 + strlen(page) + 1 +
strlen("current") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/pages/%s/current", data_dir, page);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
return NULL;
} else {
cgi_die("stat");
}
}
text = malloc(st.st_size + 1);
if (text == NULL) {
cgi_die("malloc");
}
fp = fopen(path, "r");
if (fp == NULL) {
cgi_die("fopen");
}
if (fread(text, 1, st.st_size, fp) != st.st_size) {
cgi_die("fread");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
text[st.st_size] = '\0';
return text;
}
void wiki_data_text_write(const char *data_dir, const char *page,
const char *text) {
char path[WIKI_PATH_SIZE + 1];
struct stat st;
FILE *fp;
int size;
if (!is_valid_page(page)) {
cgi_die("invalid page name");
}
if (text == NULL) {
cgi_die("invalid text");
}
if (strlen(data_dir) + 1 + strlen("pages") + 1 > WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/pages", data_dir);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
if (mkdir(path, 02770) == -1) {
cgi_die("mkdir");
}
if (chmod(path, 02770) == -1) {
cgi_die("chmod");
}
} else {
cgi_die("stat");
}
}
if (strlen(data_dir) + 1 + strlen("pages") + 1 + strlen(page) >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/pages/%s", data_dir, page);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
if (mkdir(path, 02770) == -1) {
cgi_die("mkdir");
}
if (chmod(path, 02770) == -1) {
cgi_die("chmod");
}
} else {
cgi_die("stat");
}
}
if (strlen(data_dir) + 1 + strlen("pages") + 1 + strlen(page) + 1 +
strlen("current") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/pages/%s/current", data_dir, page);
path[WIKI_PATH_SIZE] = '\0';
fp = fopen(path, "w");
if (fp == NULL) {
cgi_die("fopen");
}
size = strlen(text);
if (fwrite(text, 1, size, fp) != size) {
cgi_die("fwrite");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
}
#define SALT_LEN 16
static int get_random_salt(unsigned char *salt, size_t saltlen) {
FILE *urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
return -1;
}
if (fread(salt, 1, saltlen, urandom) != saltlen) {
fclose(urandom);
return -1;
}
fclose(urandom);
return 0;
}
void wiki_data_create_account(const char *data_dir, const char *account,
const char *password) {
int result;
uint32_t t_cost = 2; /* time cost */
uint32_t m_cost = 1 << 16; /* memory cost */
uint32_t parallelism = 1; /* threads */
unsigned char salt[SALT_LEN];
char encoded[512];
char path[WIKI_PATH_SIZE + 1];
struct stat st;
FILE *fp;
int size;
if (!is_valid_page(account)) {
cgi_die("invalid account name");
}
if (password == NULL) {
cgi_die("invalid password");
}
if (get_random_salt(salt, SALT_LEN) != 0) {
cgi_die("failed to generate salt");
}
result = argon2id_hash_encoded(t_cost, m_cost, parallelism, password,
strlen(password), salt, SALT_LEN, 32, encoded,
sizeof(encoded)); /* NULL: random salt */
if (result != ARGON2_OK) {
cgi_die("hash failed");
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 > WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts", data_dir);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
if (mkdir(path, 02770) == -1) {
cgi_die("mkdir");
}
if (chmod(path, 02770) == -1) {
cgi_die("chmod");
}
} else {
cgi_die("stat");
}
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
if (mkdir(path, 02770) == -1) {
cgi_die("mkdir");
}
if (chmod(path, 02770) == -1) {
cgi_die("chmod");
}
} else {
cgi_die("stat");
}
} else {
cgi_die("the account already exists");
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) + 1 +
strlen("password") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s/password", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
fp = fopen(path, "w");
if (fp == NULL) {
cgi_die("fopen");
}
size = strlen(encoded);
if (fwrite(encoded, 1, size, fp) != size) {
cgi_die("fwrite");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
}
const char *wiki_data_read_password(const char *data_dir, const char *account) {
char path[WIKI_PATH_SIZE + 1];
struct stat st;
char *hash;
FILE *fp;
if (!is_valid_page(account)) {
cgi_die("invalid account name");
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) + 1 +
strlen("password") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s/password", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
return NULL;
} else {
cgi_die("stat");
}
}
hash = malloc(st.st_size + 1);
if (hash == NULL) {
cgi_die("malloc");
}
fp = fopen(path, "r");
if (fp == NULL) {
cgi_die("fopen");
}
if (fread(hash, 1, st.st_size, fp) != st.st_size) {
cgi_die("fread");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
hash[st.st_size] = '\0';
return hash;
}
const char *wiki_data_begin_session(const char *data_dir, const char *account) {
int length;
int secret_length;
char *secret;
struct timeval tv;
int i;
int token_length;
char *token;
MD5_CTX md5_ctx;
unsigned char hash[16];
char *hash_string;
char hash_string2[2 * 16 + 1];
char path[WIKI_PATH_SIZE + 1];
FILE *fp;
int size;
if (!is_valid_page(account)) {
cgi_die("invalid account name");
}
length = strlen(account);
secret_length = length + 1 + 32;
secret = malloc(secret_length + 1);
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
sprintf(secret, "%s/", account);
for (i = 0; i < 16; i++) {
sprintf(secret + length + 1 + i * 2, "%02x", rand() & 0xff);
}
token_length = strlen(WIKI_SECRET) + secret_length;
token = malloc(token_length + 1);
if (token == NULL) {
cgi_die("malloc");
}
sprintf(token, "%s%s", WIKI_SECRET, secret);
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, token, strlen(token));
MD5_Final(hash, &md5_ctx);
free(token);
hash_string = malloc(2 * 16 + 1);
if (hash_string == NULL) {
cgi_die("malloc");
}
binary_to_hex_string(hash, 16, hash_string);
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, hash_string, strlen(hash_string));
MD5_Final(hash, &md5_ctx);
binary_to_hex_string(hash, 16, hash_string2);
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) + 1 +
strlen("session") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s/session", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
fp = fopen(path, "w");
if (fp == NULL) {
cgi_die("fopen");
}
size = strlen(hash_string2);
if (fwrite(hash_string2, 1, size, fp) != size) {
cgi_die("fwrite");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
return hash_string;
}
void wiki_data_end_session(const char *data_dir, const char *account) {
char path[WIKI_PATH_SIZE + 1];
if (!is_valid_page(account)) {
cgi_die("invalid account name");
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) + 1 +
strlen("session") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s/session", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
if (unlink(path) == -1) {
cgi_die("unlink");
}
}
const char *wiki_data_read_session(const char *data_dir, const char *account) {
char path[WIKI_PATH_SIZE + 1];
struct stat st;
char *session;
FILE *fp;
if (!is_valid_page(account)) {
cgi_die("invalid account name");
}
if (strlen(data_dir) + 1 + strlen("accounts") + 1 + strlen(account) + 1 +
strlen("session") >
WIKI_PATH_SIZE) {
cgi_die("path size exceeded");
}
snprintf(path, WIKI_PATH_SIZE, "%s/accounts/%s/session", data_dir, account);
path[WIKI_PATH_SIZE] = '\0';
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
return NULL;
} else {
cgi_die("stat");
}
}
session = malloc(st.st_size + 1);
if (session == NULL) {
cgi_die("malloc");
}
fp = fopen(path, "r");
if (fp == NULL) {
cgi_die("fopen");
}
if (fread(session, 1, st.st_size, fp) != st.st_size) {
cgi_die("fread");
}
if (fclose(fp) == EOF) {
cgi_die("fclose");
}
session[st.st_size] = '\0';
return session;
}