Compare commits

...

2 Commits

Author SHA1 Message Date
1c1a982829
Add cgiArgc/cgiArgv 2023-07-17 00:02:42 +03:00
c920d34e23
Added config support via libconfig 2023-07-14 21:44:39 +03:00
10 changed files with 209 additions and 51 deletions

View File

@ -6,7 +6,7 @@ LIBS ?= $(wildcard $(LIBS_DIR)/*)
CGI_EXECS ?= view_comments.cgi new_client.cgi
EXEC_TARGETS := $(patsubst %.cgi,$(BD)/%.cgi,$(CGI_EXECS))
LDFLAGS ?= -L$(BD)/ -lcgic -lqments -lhiredis -ltomcrypt -luuid
LDFLAGS ?= -L$(BD)/ -lconfig -lcgic -lqments -lhiredis -ltomcrypt -luuid
SRC_FILES := $(shell find $(SD) -name *.c)
EXEC_OBJS := $(patsubst %.cgi,$(BD)/%.o,$(CGI_EXECS))

@ -1 +1 @@
Subproject commit a3490b7612f194f029b6e7a7fc602ad962bc33e2
Subproject commit 40e57a1a9c222ea9757c52ba9f92f0d2d5cb2630

22
redis_create_user.sh Executable file
View File

@ -0,0 +1,22 @@
die() {
echo "$@" >&2
exit 1
}
magic='$z$'
redis_socket=$1
read -p "Username: " username
read -s -p "Password: " password
echo
read -s -p "Confirm your password: " password_conf
echo
if [[ $password != $password_conf ]]; then
die "Passwords don't match"
fi
salt=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
encoded_hash=$({ echo -n $magic; echo -n $password; echo -n $salt; } | sha512sum - \
| xxd -r -p | od -tx1 - | sed -e 's/^[0-9]* //' -e '$d' -e 's/^/ /' -e 's/ /\\x/g' | tr -d '\n')
redis-cli -s $redis_socket set salt:$username $salt || die "Failed to send redis command"
echo \'\"$encoded_hash\"\' | xargs -n 1 redis-cli --quoted-input -s $redis_socket set hash:$username || die "Failed to send redis command"

View File

@ -1,6 +1,9 @@
#ifndef AUTH_H
#define AUTH_H
/* return 0 on success; negative number on error */
int load_auth_config();
/* return 0 on success, negative number on error
* if session_id is not associated with any user, empty
* string is put in username */

36
src/config.c Normal file
View File

@ -0,0 +1,36 @@
#include "config.h"
#include <unistd.h>
#include <libconfig.h>
config_t config;
int
try_preload_config(const char *filename)
{
if (access(filename, F_OK) != 0) {
return CFG_PLD_NOT_FOUND;
}
if (config_read_file(&config, filename) == CONFIG_FALSE) {
return CFG_PLD_INVALID;
}
return CFG_PLD_OK;
}
int
preload_config(const char *filename)
{
char **filename_ptr = config_preload_paths;
int err;
if (filename) {
err = try_preload_config(filename);
if (err != CFG_PLD_NOT_FOUND) {
return err;
}
}
while (filename_ptr && (err = try_preload_config(*filename_ptr++))) {
if (err != CFG_PLD_NOT_FOUND) {
return err;
}
}
}

View File

@ -1,20 +1,48 @@
#define RA_USER_MAX_LENGTH 64
#define RA_PASSWORD_MAX_LENGTH 256
#ifndef CONFIG_H
#define CONFIG_H
#define RA_SESSION_EXPIRE 86400
#include "drivers/unix_fs/unix_fs_driver.h"
#include "driver.h"
#define RA_CONNECTION_TIMEOUT {1, 500000}
#define RA_HOSTNAME "ra-socket"
#include <sys/time.h>
#include <libconfig.h>
#define RA_MAGIC "$z$"
enum ConfigPreloadError {
CFG_PLD_INVALID = -2,
CFG_PLD_NOT_FOUND,
CFG_PLD_OK
};
#define COMMENTS_PER_PAGE 20
#define MAX_NAME_SIZE 1024
#define MAX_COMMENT_SIZE 4096
static char *config_preload_paths[] = {"discuss.conf", "/etc/discuss.conf", NULL};
extern config_t config;
#define QMENTS_PATH "qments-storage"
#define DRIVER_DATA { QMENTS_PATH }
#define DRIVER unix_fs_driver
/* first tries to load config `filename`, then tries preload_paths in order
* on first invalid config returns CFG_PLD_INVALID,
* if all paths do not exist returns CFG_PLD_NOT_FOUND
* if filename is NULL, checks only preload_paths
*/
int preload_config(const char *filename);
#define SESSION_COOKIE_NAME "thecookie"
#define HOSTNAME "localhost"
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
#define CONF_ROOT_LOOKUP(type, name) config_lookup_##type(&config, #name, &name)
/* defaults */
static int comments_per_page = 20;
static int max_name_size = 1024;
static int max_comment_size = 4096;
static int user_max_length = 64;
static int password_max_length = 256;
static int session_expire = 86400;
static char *qments_path = "qments-storage";
static UnixFsDriverData driver_data;
static Driver driver = unix_fs_driver;
static char *session_cookie_name = "thecookie";
static char *hostname = "localhost";
#endif /* CONFIG_H */

View File

@ -4,6 +4,8 @@
#include "utils.h"
#include <string.h>
#include <syslog.h>
#include <stdlib.h>
#define UUID_SIZE 37
@ -11,12 +13,12 @@
char *
validate_credentials()
{
char username[RA_USER_MAX_LENGTH + 1], password[RA_PASSWORD_MAX_LENGTH + 1];
char username[user_max_length + 1], password[password_max_length + 1];
char session_id[UUID_SIZE];
cgiFormResultType err;
int auth;
err = cgiFormString("username", username, RA_USER_MAX_LENGTH + 1);
err = cgiFormString("username", username, user_max_length + 1);
if (err == cgiFormTruncated) {
return "Username too long\n";
}
@ -24,7 +26,7 @@ validate_credentials()
return "Username not provided\n";
}
err = cgiFormString("password", password, RA_PASSWORD_MAX_LENGTH + 1);
err = cgiFormString("password", password, password_max_length + 1);
if (err == cgiFormTruncated) {
return "Password too long\n";
}
@ -46,7 +48,7 @@ validate_credentials()
}
if (auth) {
cgiHeaderCookieSet(SESSION_COOKIE_NAME, session_id, RA_SESSION_EXPIRE, "/", HOSTNAME, 0);
cgiHeaderCookieSet(session_cookie_name, session_id, session_expire, "/", hostname, 0);
return "You've successfully logged in!\n";
} else {
return "Failed to log in, check credentials\n";
@ -68,7 +70,24 @@ print_login_form()
int
cgiMain()
{
int cfg_pld_err;
char *message;
config_init(&config);
cfg_pld_err = preload_config((cgiArgc > 1 ? cgiArgv[1] : NULL));
if (cfg_pld_err == CFG_PLD_INVALID) {
syslog(LOG_ERR, "Got invalid config\n");
syslog(LOG_ERR, "%s\n", config_error_text(&config));
exit(EXIT_FAILURE);
} else if (cfg_pld_err == CFG_PLD_NOT_FOUND) {
syslog(LOG_WARNING, "Config not found, loading defaults\n");
} else {
load_auth_config();
}
CONF_ROOT_LOOKUP(int, session_expire);
if (cgiFormSubmitClicked("login") == cgiFormSuccess &&
!strcmp(cgiRequestMethod, "POST")) {
message = validate_credentials();
@ -76,7 +95,6 @@ cgiMain()
message = "";
}
cgiHeaderContentType("text/html; charset=utf-8");
fprintf(cgiOut, "<html>\n");

View File

@ -16,13 +16,33 @@
goto defer; \
} while (0)
/* redis auth defaults */
static struct timeval ra_connection_timeout = {1, 500000}; /* 1.5 secs */
static char *ra_hostname = "ra-socket";
static char *ra_magic = "$z$";
int
load_auth_config()
{
CONF_ROOT_LOOKUP(string, ra_hostname);
CONF_ROOT_LOOKUP(string, ra_magic);
CONF_ROOT_LOOKUP(int, user_max_length);
CONF_ROOT_LOOKUP(int, password_max_length);
CONF_ROOT_LOOKUP(int, session_expire);
return 0;
}
int
user_by_session_id(const char *session_id, char *username)
{
redisContext *ctx;
redisReply *reply;
uuid_t uu_tmp;
struct timeval timeout = RA_CONNECTION_TIMEOUT;
int retval = 0;
ctx = NULL;
@ -33,7 +53,7 @@ user_by_session_id(const char *session_id, char *username)
goto defer;
}
ctx = redisConnectUnixWithTimeout(RA_HOSTNAME, timeout);
ctx = redisConnectUnixWithTimeout(ra_hostname, ra_connection_timeout);
if (!ctx || ctx->err) {
retval = -EIO;
@ -44,6 +64,10 @@ user_by_session_id(const char *session_id, char *username)
if (reply->type != REDIS_REPLY_STRING) {
strcpy(username, "");
} else {
if (reply->len > user_max_length + 1) {
retval = -EINVAL;
goto defer;
}
strcpy(username, reply->str);
}
@ -62,18 +86,17 @@ authenticate(const char *username, const char *password, char *session_id)
unsigned char db_hash[SHA512_DIGEST_SIZE], got_hash[SHA512_DIGEST_SIZE];
uuid_t uu_tmp;
hash_state md;
struct timeval timeout = RA_CONNECTION_TIMEOUT;
int retval = 0;
ctx = NULL;
salt_reply = hash_reply = sid_reply = NULL;
if (strlen(username) > RA_USER_MAX_LENGTH || strlen(password) > RA_PASSWORD_MAX_LENGTH) {
if (strlen(username) > user_max_length || strlen(password) > password_max_length) {
retval = -EINVAL;
goto defer;
}
ctx = redisConnectUnixWithTimeout(RA_HOSTNAME, timeout);
ctx = redisConnectUnixWithTimeout(ra_hostname, ra_connection_timeout);
if (!ctx || ctx->err) {
retval = -EIO;
@ -96,7 +119,7 @@ authenticate(const char *username, const char *password, char *session_id)
sha512_init(&md);
sha512_process(&md, RA_MAGIC, strlen(RA_MAGIC));
sha512_process(&md, ra_magic, strlen(ra_magic));
sha512_process(&md, password, strlen(password));
sha512_process(&md, salt_reply->str, strlen(salt_reply->str));
@ -120,9 +143,9 @@ authenticate(const char *username, const char *password, char *session_id)
} else {
strcpy(session_id, sid_reply->str);
}
sid_reply = redisCommand(ctx, "EXPIRE session:%s %d", username, RA_SESSION_EXPIRE);
sid_reply = redisCommand(ctx, "EXPIRE session:%s %d", username, session_expire);
freeReplyObject(sid_reply);
sid_reply = redisCommand(ctx, "EXPIRE username:%s %d", session_id, RA_SESSION_EXPIRE);
sid_reply = redisCommand(ctx, "EXPIRE username:%s %d", session_id, session_expire);
retval = 1;
}

View File

@ -5,7 +5,7 @@
#include <string.h>
#include <ctype.h>
#define VALID_FOR_USERNAME(c) (isalpha(c) || c == '_')
#define VALID_FOR_USERNAME(c) (isalpha(c) || isdigit(c) || c == '_')
int
contain_special(const char *s)

View File

@ -1,25 +1,26 @@
#include "config.h"
#include "string_buffer.h"
#include "utils.h"
#include "comment.h"
#include "cgic.h"
#include "driver.h"
#include "drivers/unix_fs/unix_fs_driver.h"
#include "config.h"
#include "auth.h"
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#define UUID_SIZE 37
char authorized_user_sid[RA_USER_MAX_LENGTH + 1], session_id[UUID_SIZE];
char session_id[UUID_SIZE], *authorized_user_sid;
int
page_by_id(int id)
{
return id / COMMENTS_PER_PAGE + 1;
return id / comments_per_page + 1;
}
void
@ -50,8 +51,8 @@ render_comment(const Comment *comment)
void
allocate_header(CommentHeader *header)
{
header->user_sid = malloc(MAX_NAME_SIZE);
header->user_displayname = malloc(MAX_NAME_SIZE);
header->user_sid = malloc(max_name_size);
header->user_displayname = malloc(max_name_size);
}
void
@ -120,8 +121,6 @@ print_page()
int page, max_id;
int id_begin, id_end, i;
Comment comment;
UnixFsDriverData driver_data = DRIVER_DATA;
Driver driver = DRIVER;
cgiFormInteger("page", &page, 0);
if ((max_id = unix_fs_driver_get_max_id(&driver_data)) < 0) {
@ -131,18 +130,18 @@ print_page()
if (page <= 0) {
id_end = max_id;
id_begin = (max_id > COMMENTS_PER_PAGE ? max_id - COMMENTS_PER_PAGE : 1);
id_begin = (max_id > comments_per_page ? max_id - comments_per_page : 1);
} else {
/* max_id > COMMENTS_PER_PAGE * (page - 1) + 1
* max_id - 1 > COMMENTS_PER_PAGE * (page - 1)
/* max_id > comments_per_page * (page - 1) + 1
* max_id - 1 > comments_per_page * (page - 1)
* perform this check without overflow
*/
if ((max_id + COMMENTS_PER_PAGE - 1) / COMMENTS_PER_PAGE <= (page - 1)) {
if ((max_id + comments_per_page - 1) / comments_per_page <= (page - 1)) {
fprintf(cgiOut, "There is no such page\n");
return;
}
id_begin = COMMENTS_PER_PAGE * (page - 1) + 1;
id_end = id_begin + COMMENTS_PER_PAGE;
id_begin = comments_per_page * (page - 1) + 1;
id_end = id_begin + comments_per_page;
if (id_end > max_id) {
id_end = max_id;
}
@ -162,16 +161,14 @@ print_page()
int
handle_submitted_comment()
{
char displayname[MAX_NAME_SIZE], text[MAX_COMMENT_SIZE], *sanitized_text;
char displayname[max_name_size], text[max_comment_size], *sanitized_text;
int rid, retval;
cgiFormResultType err;
CommentHeader header;
Driver driver = DRIVER;
UnixFsDriverData driver_data = DRIVER_DATA;
err = cgiFormString("text", text, MAX_COMMENT_SIZE);
err = cgiFormString("text", text, max_comment_size);
if (err == cgiFormTruncated) {
fprintf(cgiOut, "Comment too long (max %d bytes)\n", MAX_COMMENT_SIZE);
fprintf(cgiOut, "Comment too long (max %d bytes)\n", max_comment_size);
goto defer;
}
if (err == cgiFormNotFound) {
@ -179,9 +176,9 @@ handle_submitted_comment()
goto defer;
}
err = cgiFormString("displayname", displayname, MAX_NAME_SIZE);
err = cgiFormString("displayname", displayname, max_name_size);
if (err == cgiFormTruncated) {
fprintf(cgiOut, "Name too long (max %d bytes)\n", MAX_NAME_SIZE);
fprintf(cgiOut, "Name too long (max %d bytes)\n", max_name_size);
goto defer;
}
if (err == cgiFormNotFound) {
@ -224,7 +221,35 @@ defer:
int
cgiMain()
{
if (cgiCookieString(SESSION_COOKIE_NAME, session_id, UUID_SIZE) == cgiFormSuccess) {
int cfg_pld_err;
config_init(&config);
cfg_pld_err = preload_config((cgiArgc > 1 ? cgiArgv[1] : NULL));
if (cfg_pld_err == CFG_PLD_INVALID) {
syslog(LOG_ERR, "Got invalid config\n");
syslog(LOG_ERR, "%s\n", config_error_text(&config));
exit(EXIT_FAILURE);
} else if (cfg_pld_err == CFG_PLD_NOT_FOUND) {
syslog(LOG_WARNING, "Config not found, loading defaults\n");
} else {
load_auth_config();
}
CONF_ROOT_LOOKUP(int, comments_per_page);
CONF_ROOT_LOOKUP(int, max_name_size);
CONF_ROOT_LOOKUP(int, max_comment_size);
CONF_ROOT_LOOKUP(int, user_max_length);
CONF_ROOT_LOOKUP(int, password_max_length);
CONF_ROOT_LOOKUP(string, qments_path);
CONF_ROOT_LOOKUP(string, session_cookie_name);
CONF_ROOT_LOOKUP(string, hostname);
driver_data.path = qments_path;
authorized_user_sid = malloc(user_max_length + 1);
if (cgiCookieString(session_cookie_name, session_id, UUID_SIZE) == cgiFormSuccess) {
user_by_session_id(session_id, authorized_user_sid);
} else {
authorized_user_sid[0] = '\0';
@ -253,6 +278,9 @@ cgiMain()
fprintf(cgiOut, "</body>\n");
fprintf(cgiOut, "</html>\n");
free(authorized_user_sid);
config_destroy(&config);
return 0;
}