@@ -0,0 +1,3 @@ | |||||
*.o | |||||
caniadd | |||||
TODO |
@@ -0,0 +1,10 @@ | |||||
[submodule "subm/tiny-AES-c"] | |||||
path = subm/tiny-AES-c | |||||
url = https://github.com/kokke/tiny-AES-c | |||||
[submodule "subm/md5-c"] | |||||
path = subm/md5-c | |||||
url = https://github.com/Zunawe/md5-c | |||||
[submodule "subm/MD4"] | |||||
path = subm/MD4 | |||||
url = https://github.com/moex3/MD4 | |||||
branch = ptr_addition_fix |
@@ -0,0 +1,36 @@ | |||||
InstallPrefix := /usr/local/bin | |||||
PROGNAME := caniadd | |||||
VERSION := 1 | |||||
CFLAGS := -Wall -std=gnu11 #-march=native #-Werror | |||||
CPPFLAGS := -DCBC=0 -DCTR=0 -DECB=1 -Isubm/tiny-AES-c/ -Isubm/md5-c/ -Isubm/MD4/ -DPROG_VERSION='"$(VERSION)"' | |||||
LDFLAGS := -lpthread -lsqlite3 | |||||
SOURCES := $(wildcard src/*.c) subm/tiny-AES-c/aes.c subm/md5-c/md5.c subm/MD4/md4.c #$(TOML_SRC) $(BENCODE_SRC) | |||||
OBJS := $(SOURCES:.c=.o) | |||||
all: CFLAGS += -O3 -flto | |||||
all: CPPFLAGS += #-DNDEBUG Just to be safe | |||||
all: $(PROGNAME) | |||||
# no-pie cus it crashes on my 2nd pc for some reason | |||||
dev: CFLAGS += -Og -ggdb -fsanitize=address -fsanitize=leak -fstack-protector-all -no-pie | |||||
dev: $(PROGNAME) | |||||
t: | |||||
echo $(SOURCES) | |||||
install: $(PROGNAME) | |||||
install -s -- $< $(InstallPrefix)/$(PROGNAME) | |||||
uninstall: | |||||
rm -f -- $(InstallPrefix)/$(PROGNAME) | |||||
$(PROGNAME): $(OBJS) | |||||
$(CC) -o $@ $+ $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) | |||||
clean: | |||||
-rm -- $(OBJS) $(PROGNAME) | |||||
re: clean all | |||||
red: clean dev |
@@ -0,0 +1,27 @@ | |||||
# Caniadd will add files to an AniDB list. | |||||
None of the already existing clients did exactly what I wanted, so here I am. | |||||
Caniadd is still in developement, but is is already usable. | |||||
That is, it implements logging in, encryption, ed2k hashing and adding files, basic ratelimit, keepalive, and file caching. | |||||
In the future I want to write an mpv plugin that will use the cached database from caniadd to automatically mark an episode watched on AniDB. | |||||
That will be the peak *comfy* animu list management experience. | |||||
## Things to do: | |||||
- NAT handling | |||||
- Multi thread hashing | |||||
- Read/write timeout in net | |||||
- Api ratelimit (the other part) | |||||
- Decode escaping from server | |||||
- Use a config file | |||||
- Add newline escape to server | |||||
- Better field parsing, remove the horrors at code 310 | |||||
- Add myliststats cmd as --stats arg | |||||
- Add support for compression | |||||
- Make deleting from mylist possible, with | |||||
- Name regexes, | |||||
- If file is not found at a scan | |||||
- Use api\_cmd style in api\_encrypt\_init | |||||
- Buffer up mylistadd api cmds when waiting for ratelimit | |||||
- Handle C-c gracefully at any time | |||||
- Write -h page, and maybe a man page too |
@@ -0,0 +1,802 @@ | |||||
#include <stdbool.h> | |||||
#include <string.h> | |||||
#include <assert.h> | |||||
#include <printf.h> | |||||
#include <time.h> | |||||
#include <pthread.h> | |||||
#include <errno.h> | |||||
#include <md5.h> | |||||
#include <aes.h> | |||||
#include "api.h" | |||||
#include "net.h" | |||||
#include "uio.h" | |||||
#include "config.h" | |||||
#include "ed2k.h" | |||||
#include "util.h" | |||||
/* Needed, bcuz of custom %B format */ | |||||
#pragma GCC diagnostic push | |||||
#pragma GCC diagnostic ignored "-Wformat" | |||||
#pragma GCC diagnostic ignored "-Wformat-extra-args" | |||||
#ifdef CLOCK_MONOTONIC_COARSE | |||||
#define API_CLOCK CLOCK_MONOTONIC_COARSE | |||||
#elif defined(CLOCK_MONOTONIC) | |||||
#warn "No coarse monotonic clock" | |||||
#define API_CLOCK CLOCK_MONOTONIC | |||||
#else | |||||
#error "No monotonic clock" | |||||
#endif | |||||
#define MS_TO_TIMESPEC(ts, ms) { \ | |||||
ts->tv_sec = ms / 1000; \ | |||||
ts->tv_nsec = (ms % 1000) * 1000000; \ | |||||
} | |||||
#define MS_TO_TIMESPEC_L(ts, ms) { \ | |||||
ts.tv_sec = ms / 1000; \ | |||||
ts.tv_nsec = (ms % 1000) * 1000000; \ | |||||
} | |||||
static enum error api_cmd_logout(struct api_result *res); | |||||
static enum error api_cmd_auth(const char *uname, const char *pass, | |||||
struct api_result *res); | |||||
static bool api_authed = false; | |||||
static char api_session[API_SMAXSIZE] = {0}; /* No escaping is needed */ | |||||
static uint8_t e_key[16] = {0}; | |||||
static bool api_encryption = false; | |||||
static pthread_t api_ka_thread = 0; | |||||
static pthread_mutex_t api_work_mx; | |||||
static bool api_ka_now = false; /* Are we doing keepalive now? */ | |||||
static struct timespec api_last_packet = {0}; /* Last packet time */ | |||||
static int32_t api_packet_count = 0; /* Only increment */ | |||||
static int32_t api_fast_packet_count = 0; /* Incremented or decrement */ | |||||
static int api_escaped_string(FILE *io, const struct printf_info *info, | |||||
const void *const *args) | |||||
{ | |||||
/* Ignore newline escapes for now */ | |||||
char *str = *(char**)args[0]; | |||||
char *and_pos = strchr(str, '&'); | |||||
size_t w_chars = 0; | |||||
if (and_pos == NULL) | |||||
return fprintf(io, "%s", str); | |||||
while (and_pos) { | |||||
w_chars += fprintf(io, "%.*s", (int)(and_pos - str), str); | |||||
w_chars += fprintf(io, "&"); | |||||
str = and_pos + 1; | |||||
and_pos = strchr(str, '&'); | |||||
} | |||||
if (*str) | |||||
w_chars += fprintf(io, "%s", str); | |||||
return w_chars; | |||||
} | |||||
static int api_escaped_sring_info(const struct printf_info *info, size_t n, | |||||
int *argtypes, int *size) | |||||
{ | |||||
if (n > 0) { | |||||
argtypes[0] = PA_STRING; | |||||
size[0] = sizeof(const char*); | |||||
} | |||||
return 1; | |||||
} | |||||
static enum error api_init_encrypt(const char *api_key, const char *uname) | |||||
{ | |||||
char buffer[API_BUFSIZE]; | |||||
MD5Context md5_ctx; | |||||
char *salt_start = buffer + 4 /* 209 [salt here] ... */, *salt_end; | |||||
ssize_t r_len, salt_len; | |||||
if (net_send(buffer, snprintf(buffer, sizeof(buffer), | |||||
"ENCRYPT user=%s&type=1", uname)) == -1) { | |||||
return ERR_API_COMMFAIL; | |||||
} | |||||
r_len = net_read(buffer, sizeof(buffer)); | |||||
if (strncmp(buffer, "209", 3) != 0) { | |||||
uio_error("We expected 209 response, but got: %.*s", | |||||
(int)r_len, buffer); | |||||
return ERR_API_ENCRYPTFAIL; | |||||
} | |||||
salt_end = strchr(salt_start, ' '); | |||||
if (!salt_end) { | |||||
uio_error("Cannot find space after salt in response"); | |||||
return ERR_API_ENCRYPTFAIL; | |||||
} | |||||
salt_len = salt_end - salt_start; | |||||
md5Init(&md5_ctx); | |||||
md5Update(&md5_ctx, (uint8_t*)api_key, strlen(api_key)); | |||||
md5Update(&md5_ctx, (uint8_t*)salt_start, salt_len); | |||||
md5Finalize(&md5_ctx); | |||||
memcpy(e_key, md5_ctx.digest, sizeof(e_key)); | |||||
#if 1 | |||||
char *buffpos = buffer; | |||||
for (int i = 0; i < 16; i++) | |||||
buffpos += sprintf(buffpos, "%02x", e_key[i]); | |||||
uio_debug("Encryption key is: '%s'", buffer); | |||||
#endif | |||||
api_encryption = true; | |||||
return NOERR; | |||||
} | |||||
static size_t api_encrypt(char *buffer, size_t data_len) | |||||
{ | |||||
struct AES_ctx actx; | |||||
size_t rem_data_len = data_len, ret_len = data_len; | |||||
char pad_value; | |||||
AES_init_ctx(&actx, e_key); | |||||
while (rem_data_len >= AES_BLOCKLEN) { | |||||
AES_ECB_encrypt(&actx, (uint8_t*)buffer); | |||||
buffer += AES_BLOCKLEN; | |||||
rem_data_len -= AES_BLOCKLEN; | |||||
} | |||||
/* Possible BOF here? maybe? certanly. */ | |||||
pad_value = AES_BLOCKLEN - rem_data_len; | |||||
ret_len += pad_value; | |||||
memset(buffer + rem_data_len, pad_value, pad_value); | |||||
AES_ECB_encrypt(&actx, (uint8_t*)buffer); | |||||
assert(ret_len % AES_BLOCKLEN == 0); | |||||
return ret_len; | |||||
} | |||||
static size_t api_decrypt(char *buffer, size_t data_len) | |||||
{ | |||||
assert(data_len % AES_BLOCKLEN == 0); | |||||
struct AES_ctx actx; | |||||
size_t ret_len = data_len; | |||||
char pad_value; | |||||
AES_init_ctx(&actx, e_key); | |||||
while (data_len) { | |||||
AES_ECB_decrypt(&actx, (uint8_t*)buffer); | |||||
buffer += AES_BLOCKLEN; | |||||
data_len -= AES_BLOCKLEN; | |||||
} | |||||
pad_value = buffer[data_len - 1]; | |||||
ret_len -= pad_value; | |||||
return ret_len; | |||||
} | |||||
static enum error api_auth(const char* uname, const char *passw) | |||||
{ | |||||
struct api_result res; | |||||
enum error err = NOERR; | |||||
if (!api_encryption) | |||||
uio_warning("Logging in without encryption!"); | |||||
if (api_cmd_auth(uname, passw, &res) != NOERR) { | |||||
return ERR_API_AUTH_FAIL; | |||||
} | |||||
switch (res.code) { | |||||
case 201: | |||||
uio_warning("A new client version is available!"); | |||||
case 200: | |||||
memcpy(api_session, res.auth.session_key, sizeof(api_session)); | |||||
api_authed = true; | |||||
uio_debug("Succesfully logged in. Session key: '%s'", api_session); | |||||
break; | |||||
default: | |||||
err = ERR_API_AUTH_FAIL; | |||||
switch (res.code) { | |||||
case 500: | |||||
uio_error("Login failed. Please check your credentials again"); | |||||
break; | |||||
case 503: | |||||
uio_error("Client is outdated. You're probably out of luck here."); | |||||
break; | |||||
case 504: | |||||
uio_error("Client is banned :( Reason: %s", res.auth.banned_reason); | |||||
free(res.auth.banned_reason); | |||||
break; | |||||
case 505: | |||||
uio_error("Illegal input or access denied"); | |||||
break; | |||||
case 601: | |||||
uio_error("AniDB out of service"); | |||||
break; | |||||
default: | |||||
uio_error("Unknown error: %hu", res.code); | |||||
break; | |||||
} | |||||
} | |||||
return err; | |||||
} | |||||
enum error api_logout() | |||||
{ | |||||
struct api_result res; | |||||
enum error err = NOERR; | |||||
if (api_cmd_logout(&res) != NOERR) { | |||||
return ERR_API_AUTH_FAIL; | |||||
} | |||||
switch (res.code) { | |||||
case 203: | |||||
uio_debug("Succesfully logged out"); | |||||
api_authed = false; | |||||
break; | |||||
case 403: | |||||
uio_error("Cannot log out, because we aren't logged in"); | |||||
api_authed = false; | |||||
break; | |||||
default: | |||||
err = ERR_API_LOGOUT; | |||||
uio_error("Unknown error: %hu", res.code); | |||||
break; | |||||
} | |||||
return err; | |||||
} | |||||
static void api_keepalive(struct timespec *out_next) | |||||
{ | |||||
struct timespec ts = {0}; | |||||
uint64_t msdiff; | |||||
clock_gettime(API_CLOCK, &ts); | |||||
msdiff = util_timespec_diff(&api_last_packet, &ts); | |||||
if (msdiff >= API_TIMEOUT) { | |||||
struct api_result r; | |||||
MS_TO_TIMESPEC(out_next, API_TIMEOUT); | |||||
uio_debug("Sending uptime command for keep alive"); | |||||
// TODO what if another action is already in progress? | |||||
api_cmd_uptime(&r); | |||||
} else { | |||||
uint64_t msnext = API_TIMEOUT - msdiff; | |||||
uio_debug("Got keepalive request, but time is not up yet"); | |||||
MS_TO_TIMESPEC(out_next, msnext); | |||||
} | |||||
} | |||||
void *api_keepalive_main(void *arg) | |||||
{ | |||||
struct timespec ka_time; | |||||
MS_TO_TIMESPEC_L(ka_time, API_TIMEOUT); | |||||
uio_debug("Hi from keepalie thread"); | |||||
for (;;) { | |||||
if (nanosleep(&ka_time, NULL) != 0) { | |||||
int e = errno; | |||||
uio_error("Nanosleep failed: %s", strerror(e)); | |||||
} | |||||
/* Needed, because the thread could be canceled while in recv or send | |||||
* and in that case, the mutex will remain locked | |||||
* Could be replaced with a pthread_cleanup_push ? */ | |||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); | |||||
pthread_mutex_lock(&api_work_mx); | |||||
api_ka_now = true; | |||||
uio_debug("G'moooooning! Is it time to keep our special connection alive?"); | |||||
api_keepalive(&ka_time); | |||||
uio_debug("Next wakey-wakey in %ld seconds", ka_time.tv_sec); | |||||
api_ka_now = false; | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); | |||||
} | |||||
return NULL; | |||||
} | |||||
enum error api_clock_init() | |||||
{ | |||||
struct timespec ts; | |||||
memset(&api_last_packet, 0, sizeof(api_last_packet)); | |||||
api_packet_count = 0; | |||||
api_fast_packet_count = 0; | |||||
if (clock_getres(API_CLOCK, &ts) != 0) { | |||||
uio_error("Cannot get clock resolution: %s", strerror(errno)); | |||||
return ERR_API_CLOCK; | |||||
} | |||||
uio_debug("Clock resolution: %f ms", | |||||
(ts.tv_sec * 1000) + (ts.tv_nsec / 1000000.0)); | |||||
return NOERR; | |||||
} | |||||
enum error api_init(bool auth) | |||||
{ | |||||
enum error err = NOERR; | |||||
const char **api_key, **uname, **passwd; | |||||
err = api_clock_init(); | |||||
if (err != NOERR) | |||||
return err; | |||||
err = net_init(); | |||||
if (err != NOERR) | |||||
return err; | |||||
if (config_get("api-key", (void**)&api_key) == NOERR) { | |||||
if (config_get("username", (void**)&uname) != NOERR) { | |||||
uio_error("Api key is specified, but that also requires " | |||||
"the username!"); | |||||
err = ERR_OPT_REQUIRED; | |||||
goto fail; | |||||
} | |||||
err = api_init_encrypt(*api_key, *uname); | |||||
if (err != NOERR) { | |||||
uio_error("Cannot init api encryption"); | |||||
goto fail; | |||||
} | |||||
} | |||||
/* Define an escaped string printf type */ | |||||
if (register_printf_specifier('B', api_escaped_string, | |||||
api_escaped_sring_info) != 0) { | |||||
uio_error("Failed to register escaped printf string function"); | |||||
err = ERR_API_PRINTFFUNC; | |||||
goto fail; | |||||
} | |||||
if (auth) { | |||||
if (config_get("username", (void**)&uname) != NOERR) { | |||||
uio_error("Username is not specified, but it is required!"); | |||||
err = ERR_OPT_REQUIRED; | |||||
goto fail; | |||||
} | |||||
if (config_get("password", (void**)&passwd) != NOERR) { | |||||
uio_error("Password is not specified, but it is required!"); | |||||
err = ERR_OPT_REQUIRED; | |||||
goto fail; | |||||
} | |||||
err = api_auth(*uname, *passwd); | |||||
if (err != NOERR) | |||||
goto fail; | |||||
/* Only do keep alive if we have a session */ | |||||
if (pthread_mutex_init(&api_work_mx, NULL) != 0) { | |||||
uio_error("Cannot create mutex"); | |||||
err = ERR_THRD; | |||||
goto fail; | |||||
} | |||||
if (pthread_create(&api_ka_thread, NULL, api_keepalive_main, NULL) != 0) { | |||||
uio_error("Cannot create api keepalive thread"); | |||||
err = ERR_THRD; | |||||
goto fail; | |||||
} | |||||
} | |||||
#if 0 | |||||
printf("Testings: %B\n", "oi&ha=hi&wooooowz&"); | |||||
printf("Testings: %B\n", "oi&ha=hi&wooooowz"); | |||||
printf("Testings: %B\n", "&oi&ha=hi&wooooowz"); | |||||
printf("Testings: %B\n", "oooooooooiiiiii"); | |||||
#endif | |||||
return err; | |||||
fail: | |||||
api_free(); | |||||
return err; | |||||
} | |||||
void api_free() | |||||
{ | |||||
if (api_authed) { | |||||
if (pthread_cancel(api_ka_thread) != 0) { | |||||
uio_error("Cannot cancel api keepalive thread"); | |||||
} else { | |||||
int je = pthread_join(api_ka_thread, NULL); | |||||
if (je != 0) { | |||||
uio_error("Cannot join api keepalive thread: %s", | |||||
strerror(je)); | |||||
} | |||||
if (pthread_mutex_destroy(&api_work_mx) != 0) | |||||
uio_error("Cannot destroy api work mutex"); | |||||
} | |||||
api_logout(); | |||||
memset(api_session, 0, sizeof(api_session)); | |||||
api_authed = false; /* duplicate */ | |||||
} | |||||
if (api_encryption) { | |||||
api_encryption = false; | |||||
memset(e_key, 0, sizeof(e_key)); | |||||
} | |||||
register_printf_specifier('B', NULL, NULL); | |||||
net_free(); | |||||
} | |||||
/* | |||||
* We just sent a packet, so update the last packet time here | |||||
*/ | |||||
static void api_ratelimit_sent() | |||||
{ | |||||
clock_gettime(API_CLOCK, &api_last_packet); | |||||
} | |||||
static void api_ratelimit() | |||||
{ | |||||
struct timespec ts = {0}; | |||||
uint64_t msdiff, mswait; | |||||
clock_gettime(API_CLOCK, &ts); | |||||
msdiff = util_timespec_diff(&api_last_packet, &ts); | |||||
uio_debug("Time since last packet: %ld ms", msdiff); | |||||
if (msdiff >= API_SENDWAIT) | |||||
return; /* No ratelimiting is needed */ | |||||
/* Need ratelimit, so do it here for now */ | |||||
mswait = API_SENDWAIT - msdiff; | |||||
uio_debug("Ratelimit is needed, sleeping for %ld ms", mswait); | |||||
MS_TO_TIMESPEC_L(ts, mswait); | |||||
if (nanosleep(&ts, NULL) == -1) { | |||||
if (errno == EINTR) | |||||
uio_error("Nanosleep got interrupted"); | |||||
else | |||||
uio_error("Nanosleep failed"); | |||||
} | |||||
} | |||||
static ssize_t api_send(char *buffer, size_t data_len, size_t buf_size) | |||||
{ | |||||
ssize_t read_len; | |||||
api_ratelimit(); | |||||
uio_debug("{Api}: Sending: %.*s", (int)data_len, buffer); | |||||
if (api_encryption) | |||||
data_len = api_encrypt(buffer, data_len); | |||||
if (net_send(buffer, data_len) == -1) { | |||||
uio_error("Cannot send data: %s", strerror(errno)); | |||||
return -1; | |||||
} | |||||
read_len = net_read(buffer, buf_size); | |||||
api_ratelimit_sent(); | |||||
if (api_encryption) | |||||
read_len = api_decrypt(buffer, read_len); | |||||
uio_debug("{Api}: Reading: %.*s", (int)read_len, buffer); | |||||
return read_len; | |||||
} | |||||
long api_res_code(const char *buffer) | |||||
{ | |||||
char *end; | |||||
long res = strtol(buffer, &end, 10); | |||||
if (res == 0 && buffer == end) { | |||||
uio_error("No error codes in the response"); | |||||
return -1; | |||||
} | |||||
assert(*end == ' '); | |||||
return res; | |||||
} | |||||
static bool api_get_fl(const char *buffer, int32_t index, const char *delim, | |||||
char **const out_start, size_t *const out_len) | |||||
{ | |||||
assert(index > 0); | |||||
size_t len = strcspn(buffer, delim); | |||||
while (--index > 0) { | |||||
buffer += len + 1; | |||||
len = strcspn(buffer, delim); | |||||
} | |||||
*out_start = (char*)buffer; | |||||
*out_len = len; | |||||
return true; | |||||
} | |||||
static bool api_get_line(const char *buffer, int32_t line_num, | |||||
char **const out_line_start, size_t *const out_line_len) | |||||
{ | |||||
return api_get_fl(buffer, line_num, "\n", out_line_start, out_line_len); | |||||
} | |||||
static bool api_get_field(const char *buffer, int32_t field_num, | |||||
char **const out_field_start, size_t *const out_field_len) | |||||
{ | |||||
return api_get_fl(buffer, field_num, " |\n", out_field_start, out_field_len); | |||||
} | |||||
#if 0 | |||||
static char *api_get_field_mod(char *buffer, int32_t field_num) | |||||
{ | |||||
char *sptr = NULL; | |||||
char *f_start; | |||||
f_start = strtok_r(buffer, " ", &sptr); | |||||
if (!f_start) | |||||
return NULL; | |||||
while (field_num --> 0) { | |||||
f_start = strtok_r(NULL, " ", &sptr); | |||||
if (!f_start) | |||||
return NULL; | |||||
} | |||||
return f_start; | |||||
} | |||||
#endif | |||||
enum error api_cmd_version(struct api_result *res) | |||||
{ | |||||
char buffer[API_BUFSIZE] = "VERSION"; | |||||
size_t res_len = api_send(buffer, strlen(buffer), sizeof(buffer)); | |||||
long code; | |||||
enum error err = NOERR; | |||||
pthread_mutex_lock(&api_work_mx); | |||||
if (res_len == -1) { | |||||
err = ERR_API_COMMFAIL; | |||||
goto end; | |||||
} | |||||
code = api_res_code(buffer); | |||||
if (code == -1) { | |||||
err = ERR_API_RESP_INVALID; | |||||
goto end; | |||||
} | |||||
if (code == 998) { | |||||
char *ver_start; | |||||
size_t ver_len; | |||||
bool glr = api_get_line(buffer, 2, &ver_start, &ver_len); | |||||
assert(glr); | |||||
(void)glr; | |||||
assert(ver_len < sizeof(res->version.version_str)); | |||||
memcpy(res->version.version_str, ver_start, ver_len); | |||||
res->version.version_str[ver_len] = '\0'; | |||||
} | |||||
res->code = (uint16_t)code; | |||||
end: | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
return err; | |||||
} | |||||
static enum error api_cmd_auth(const char *uname, const char *pass, | |||||
struct api_result *res) | |||||
{ | |||||
pthread_mutex_lock(&api_work_mx); | |||||
char buffer[API_BUFSIZE]; | |||||
long code; | |||||
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), | |||||
"AUTH user=%s&pass=%B&protover=3&client=caniadd&clientver=" | |||||
PROG_VERSION "&enc=UTF-8", uname, pass), sizeof(buffer)); | |||||
enum error err = NOERR; | |||||
if (res_len == -1) { | |||||
err = ERR_API_COMMFAIL; | |||||
goto end; | |||||
} | |||||
code = api_res_code(buffer); | |||||
if (code == -1) { | |||||
err = ERR_API_RESP_INVALID; | |||||
goto end; | |||||
} | |||||
if (code == 200 || code == 201) { | |||||
char *sess; | |||||
size_t sess_len; | |||||
bool gfr = api_get_field(buffer, 2, &sess, &sess_len); | |||||
assert(gfr); | |||||
(void)gfr; | |||||
assert(sess_len < sizeof(res->auth.session_key)); | |||||
memcpy(res->auth.session_key, sess, sess_len); | |||||
res->auth.session_key[sess_len] = '\0'; | |||||
} else if (code == 504) { | |||||
char *reason; | |||||
size_t reason_len; | |||||
bool gfr = api_get_field(buffer, 5, &reason, &reason_len); | |||||
assert(gfr); | |||||
(void)gfr; | |||||
res->auth.banned_reason = strndup(reason, reason_len); | |||||
} | |||||
res->code = (uint16_t)code; | |||||
end: | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
return err; | |||||
} | |||||
static enum error api_cmd_logout(struct api_result *res) | |||||
{ | |||||
pthread_mutex_lock(&api_work_mx); | |||||
char buffer[API_BUFSIZE]; | |||||
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), | |||||
"LOGOUT s=%s", api_session), sizeof(buffer)); | |||||
long code; | |||||
enum error err = NOERR; | |||||
if (res_len == -1) { | |||||
err = ERR_API_COMMFAIL; | |||||
goto end; | |||||
} | |||||
code = api_res_code(buffer); | |||||
if (code == -1) { | |||||
err = ERR_API_RESP_INVALID; | |||||
goto end; | |||||
} | |||||
res->code = (uint16_t)code; | |||||
end: | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
return err; | |||||
} | |||||
enum error api_cmd_uptime(struct api_result *res) | |||||
{ | |||||
/* If mutex is not already locked from the keepalive thread */ | |||||
/* Or we could use a recursive mutex? */ | |||||
if (!api_ka_now) | |||||
pthread_mutex_lock(&api_work_mx); | |||||
char buffer[API_BUFSIZE]; | |||||
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), | |||||
"UPTIME s=%s", api_session), sizeof(buffer)); | |||||
long code; | |||||
enum error err = NOERR; | |||||
if (res_len == -1) { | |||||
err = ERR_API_COMMFAIL; | |||||
goto end; | |||||
} | |||||
code = api_res_code(buffer); | |||||
if (code == -1) { | |||||
err = ERR_API_RESP_INVALID; | |||||
goto end; | |||||
} | |||||
if (code == 208) { | |||||
char *ls; | |||||
size_t ll; | |||||
bool glf = api_get_line(buffer, 2, &ls, &ll); | |||||
assert(glf); | |||||
(void)glf; | |||||
res->uptime.ms = strtol(ls, NULL, 10); | |||||
} | |||||
res->code = (uint16_t)code; | |||||
end: | |||||
if (!api_ka_now) | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
return err; | |||||
} | |||||
enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | |||||
enum mylist_state ml_state, bool watched, struct api_result *res) | |||||
{ | |||||
char buffer[API_BUFSIZE]; | |||||
char hash_str[ED2K_HASH_SIZE * 2 + 1]; | |||||
size_t res_len; | |||||
enum error err = NOERR; | |||||
long code; | |||||
pthread_mutex_lock(&api_work_mx); | |||||
util_byte2hex(hash, ED2K_HASH_SIZE, false, hash_str); | |||||
/* Wiki says file size is 4 bytes, but no way that's true lol */ | |||||
res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), | |||||
"MYLISTADD s=%s&size=%ld&ed2k=%s&state=%hu&viewed=%d", | |||||
api_session, size, hash_str, ml_state, watched), | |||||
sizeof(buffer)); | |||||
if (res_len == -1) { | |||||
err = ERR_API_COMMFAIL; | |||||
goto end; | |||||
} | |||||
code = api_res_code(buffer); | |||||
if (code == -1) { | |||||
err = ERR_API_RESP_INVALID; | |||||
goto end; | |||||
} | |||||
if (code == 210) { | |||||
char *ls, id_str[12]; | |||||
size_t ll; | |||||
bool glr = api_get_line(buffer, 2, &ls, &ll); | |||||
assert(glr); | |||||
(void)glr; | |||||
assert(sizeof(id_str) > ll); | |||||
memcpy(id_str, ls, ll); | |||||
id_str[ll] = '\0'; | |||||
res->mylistadd.new_id = strtoll(id_str, NULL, 10); | |||||
/* Wiki says these id's are 4 bytes, which is untrue... | |||||
* that page may be a little out of date (or they just | |||||
* expect us to use common sense lmao */ | |||||
} else if (code == 310) { | |||||
/* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}| | |||||
* {int4 date}|{int2 state}|{int4 viewdate}|{str storage}| | |||||
* {str source}|{str other}|{int2 filestate} */ | |||||
char *ls; | |||||
size_t ll; | |||||
struct api_mylistadd_result *mr = &res->mylistadd; | |||||
bool glr = api_get_line(buffer, 2, &ls, &ll); | |||||
assert(glr); | |||||
assert(ll < API_BUFSIZE - 1); | |||||
(void)glr; | |||||
ls[ll] = '\0'; | |||||
void *fptrs[] = { | |||||
&mr->lid, &mr->fid, &mr->eid, &mr->aid, &mr->gid, &mr->date, | |||||
&mr->state, &mr->viewdate, &mr->storage, &mr->source, | |||||
&mr->other, &mr->filestate, | |||||
}; | |||||
for (int idx = 1; idx <= 12; idx++) { | |||||
char *fs, *endptr; | |||||
size_t fl; | |||||
bool pr; | |||||
uint64_t val; | |||||
size_t cpy_size = sizeof(mr->lid); | |||||
if (idx == 7) | |||||
cpy_size = sizeof(mr->state); | |||||
if (idx == 12) | |||||
cpy_size = sizeof(mr->filestate); | |||||
pr = api_get_field(ls, idx, &fs, &fl); | |||||
assert(pr); | |||||
(void)pr; | |||||
if (idx == 9 || idx == 10 || idx == 11) { /* string fields */ | |||||
if (fl == 0) | |||||
*(char**)fptrs[idx-1] = NULL; | |||||
else | |||||
*(char**)fptrs[idx-1] = strndup(fs, fl); | |||||
continue; | |||||
} | |||||
val = strtoull(fs, &endptr, 10); | |||||
assert(!(val == 0 && fs == endptr)); | |||||
memcpy(fptrs[idx-1], &val, cpy_size); | |||||
} | |||||
} | |||||
res->code = (uint16_t)code; | |||||
end: | |||||
pthread_mutex_unlock(&api_work_mx); | |||||
return err; | |||||
} | |||||
#pragma GCC diagnostic pop |
@@ -0,0 +1,91 @@ | |||||
#ifndef _API_H | |||||
#define _API_H | |||||
#include <stdint.h> | |||||
#include <time.h> | |||||
#include "error.h" | |||||
/* Maximum length of one response/request */ | |||||
#define API_BUFSIZE 1400 | |||||
/* Session key maximum size, including '\0' */ | |||||
#define API_SMAXSIZE 16 | |||||
/* The session timeout in miliseconds */ | |||||
#define API_TIMEOUT 30 * 60 * 1000 | |||||
/* How many miliseconds to wait between sends */ | |||||
#define API_SENDWAIT 2 * 1000 | |||||
/* The number of packets that are exccempt from the ratelimit */ | |||||
#define API_FREESEND 5 | |||||
/* Long term wait between sends */ | |||||
#define API_SENDWAIT_LONG 4 * 1000 | |||||
/* After this many packets has been sent, use the longterm ratelimit */ | |||||
#define API_LONGTERM_PACKETS 100 | |||||
enum mylist_state { | |||||
MYLIST_STATE_UNKNOWN = 0, | |||||
MYLIST_STATE_INTERNAL, | |||||
MYLIST_STATE_EXTERNAL, | |||||
MYLIST_STATE_DELETED, | |||||
MYLIST_STATE_REMOTE, | |||||
}; | |||||
enum file_state { | |||||
FILE_STATE_NORMAL = 0, | |||||
FILE_STATE_CORRUPT, | |||||
FILE_STATE_SELF_EDIT, | |||||
FILE_STATE_SELF_RIP = 10, | |||||
FILE_STATE_ON_DVD, | |||||
FILE_STATE_ON_VHS, | |||||
FILE_STATE_ON_TV, | |||||
FILE_STATE_IN_THEATERS, | |||||
FILE_STATE_STREAMED, | |||||
FILE_STATE_OTHER = 100, | |||||
}; | |||||
struct api_version_result { | |||||
char version_str[40]; | |||||
}; | |||||
struct api_auth_result { | |||||
union { | |||||
char session_key[API_SMAXSIZE]; | |||||
/* free() */ | |||||
char *banned_reason; | |||||
}; | |||||
}; | |||||
struct api_uptime_result { | |||||
int32_t ms; | |||||
}; | |||||
struct api_mylistadd_result { | |||||
union { | |||||
uint64_t new_id; | |||||
struct { | |||||
uint64_t lid, fid, eid, aid, gid, date, viewdate; | |||||
/* free() if != NULL ofc */ | |||||
char *storage, *source, *other; | |||||
enum mylist_state state; | |||||
enum file_state filestate; | |||||
}; | |||||
}; | |||||
}; | |||||
#define e(n) struct api_##n##_result n | |||||
struct api_result { | |||||
uint16_t code; | |||||
union { | |||||
struct api_version_result version; | |||||
struct api_auth_result auth; | |||||
struct api_uptime_result uptime; | |||||
e(mylistadd); | |||||
}; | |||||
}; | |||||
#undef e | |||||
enum error api_init(bool auth); | |||||
void api_free(); | |||||
enum error api_cmd_version(struct api_result *res); | |||||
enum error api_cmd_uptime(struct api_result *res); | |||||
enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | |||||
enum mylist_state fstate, bool watched, struct api_result *res); | |||||
#endif /* _API_H */ |
@@ -0,0 +1,208 @@ | |||||
#include <stddef.h> | |||||
#include <sqlite3.h> | |||||
#include "cache.h" | |||||
#include "config.h" | |||||
#include "uio.h" | |||||
#include "ed2k.h" | |||||
#include "util.h" | |||||
#define sqlite_bind_goto(smt, name, type, ...) { \ | |||||
int sb_idx = sqlite3_bind_parameter_index(smt, name); \ | |||||
if (sb_idx == 0) { \ | |||||
uio_error("Cannot get named parameter for var: %s", name); \ | |||||
err = ERR_CACHE_SQLITE; \ | |||||
goto fail; \ | |||||
} \ | |||||
int sb_sret = sqlite3_bind_##type(smt, sb_idx, __VA_ARGS__); \ | |||||
if (sb_sret != SQLITE_OK) {\ | |||||
uio_error("Cannot bind to statement: %s", sqlite3_errmsg(cache_db));\ | |||||
err = ERR_CACHE_SQLITE;\ | |||||
goto fail;\ | |||||
} \ | |||||
} | |||||
static sqlite3 *cache_db = NULL; | |||||
static const char sql_create_table[] = "CREATE TABLE IF NOT EXISTS mylist (" | |||||
"lid INTEGER NOT NULL PRIMARY KEY," | |||||
"fname TEXT NOT NULL," | |||||
"fsize INTEGER NOT NULL," | |||||
"ed2k TEXT NOT NULL," | |||||
"UNIQUE (fname, fsize) )"; | |||||
static const char sql_mylist_add[] = "INSERT INTO mylist " | |||||
"(lid, fname, fsize, ed2k) VALUES " | |||||
//"(?, ?, ?, ?)"; | |||||
"(:lid, :fname, :fsize, :ed2k)"; | |||||
static const char sql_mylist_get[] = "SELECT * FROM mylist WHERE " | |||||
"fsize=:fsize AND fname=:fname"; | |||||
#if 0 | |||||
static const char sql_has_tables[] = "SELECT 1 FROM sqlite_master " | |||||
"WHERE type='table' AND tbl_name='mylist'"; | |||||
/* Return 0 if false, 1 if true, and -1 if error */ | |||||
static int cache_has_tables() | |||||
{ | |||||
sqlite3_smt smt; | |||||
int sret; | |||||
sret = sqlite3_prepare_v2(cache_db, sql_has_tables, | |||||
sizeof(sql_has_tables), &smt, NULL); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot prepare statement: %s", sqlite3_errmsg(cache_db)); | |||||
return -1; | |||||
} | |||||
sqlite3_step(&smt); | |||||
// ehh fuck this, lets just use if not exists | |||||
sret = sqlite3_finalize(&smt); | |||||
if (sret != SQLITE_OK) | |||||
uio_debug("sql3_finalize failed: %s", sqlite3_errmsg(cache_db)); | |||||
} | |||||
#endif | |||||
/* | |||||
* Create database table(s) | |||||
*/ | |||||
static enum error cache_init_table() | |||||
{ | |||||
sqlite3_stmt *smt; | |||||
int sret; | |||||
enum error err = NOERR; | |||||
sret = sqlite3_prepare_v2(cache_db, sql_create_table, | |||||
sizeof(sql_create_table), &smt, NULL); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot prepare statement: %s", sqlite3_errmsg(cache_db)); | |||||
return ERR_CACHE_SQLITE; | |||||
} | |||||
sret = sqlite3_step(smt); | |||||
if (sret != SQLITE_DONE) { | |||||
uio_error("sql3_step is not done: %s", sqlite3_errmsg(cache_db)); | |||||
err = ERR_CACHE_SQLITE; | |||||
} | |||||
sret = sqlite3_finalize(smt); | |||||
if (sret != SQLITE_OK) | |||||
uio_debug("sql3_finalize failed: %s", sqlite3_errmsg(cache_db)); | |||||
return err; | |||||
} | |||||
enum error cache_init() | |||||
{ | |||||
char **db_path; | |||||
enum error err; | |||||
int sret; | |||||
err = config_get("cachedb", (void**)&db_path); | |||||
if (err != NOERR) { | |||||
uio_error("Cannot get cache db path from args"); | |||||
return err; | |||||
} | |||||
uio_debug("Opening cache db: '%s'", *db_path); | |||||
sret = sqlite3_open(*db_path, &cache_db); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot create sqlite3 database: %s", sqlite3_errstr(sret)); | |||||
sqlite3_close(cache_db); /* Even if arg is NULL, it's A'OK */ | |||||
return ERR_CACHE_SQLITE; | |||||
} | |||||
sqlite3_extended_result_codes(cache_db, 1); | |||||
err = cache_init_table(); | |||||
if (err != NOERR) | |||||
goto fail; | |||||
return NOERR; | |||||
fail: | |||||
cache_free(); | |||||
return err; | |||||
} | |||||
void cache_free() | |||||
{ | |||||
sqlite3_close(cache_db); | |||||
uio_debug("Closed cache db"); | |||||
} | |||||
enum error cache_add(uint64_t lid, const char *fname, | |||||
uint64_t fsize, const uint8_t *ed2k) | |||||
{ | |||||
char ed2k_str[ED2K_HASH_SIZE * 2 + 1]; | |||||
sqlite3_stmt *smt; | |||||
int sret; | |||||
enum error err = NOERR; | |||||
sret = sqlite3_prepare_v2(cache_db, sql_mylist_add, | |||||
sizeof(sql_mylist_add), &smt, NULL); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot prepare statement: %s", sqlite3_errmsg(cache_db)); | |||||
return ERR_CACHE_SQLITE; | |||||
} | |||||
util_byte2hex(ed2k, ED2K_HASH_SIZE, false, ed2k_str); | |||||
sqlite_bind_goto(smt, ":lid", int64, lid); | |||||
sqlite_bind_goto(smt, ":fname", text, fname, -1, SQLITE_STATIC); | |||||
sqlite_bind_goto(smt, ":fsize", int64, fsize); | |||||
sqlite_bind_goto(smt, ":ed2k", text, ed2k_str, -1, SQLITE_STATIC); | |||||
sret = sqlite3_step(smt); | |||||
if (sret != SQLITE_DONE) { | |||||
if (sret == SQLITE_CONSTRAINT_PRIMARYKEY) { | |||||
uio_debug("Attempted to add duplicate entry!"); | |||||
err = ERR_CACHE_EXISTS; | |||||
} else if (sret == SQLITE_CONSTRAINT_UNIQUE) { | |||||
uio_debug("An entry with the same name and size already exists!"); | |||||
err = ERR_CACHE_NON_UNIQUE; | |||||
} else { | |||||
uio_error("error after sql3_step: %s %d", sqlite3_errmsg(cache_db), sret); | |||||
err = ERR_CACHE_SQLITE; | |||||
} | |||||
} | |||||
fail: | |||||
sqlite3_finalize(smt); | |||||
return err; | |||||
} | |||||
enum error cache_get(const char *fname, uint64_t fsize, | |||||
struct cache_entry *out_ce) | |||||
{ | |||||
sqlite3_stmt *smt; | |||||
int sret; | |||||
enum error err = NOERR; | |||||
sret = sqlite3_prepare_v2(cache_db, sql_mylist_get, | |||||
sizeof(sql_mylist_get), &smt, NULL); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot prepare statement: %s", sqlite3_errmsg(cache_db)); | |||||
return ERR_CACHE_SQLITE; | |||||
} | |||||
sqlite_bind_goto(smt, ":fname", text, fname, -1, SQLITE_STATIC); | |||||
sqlite_bind_goto(smt, ":fsize", int64, fsize); | |||||
sret = sqlite3_step(smt); | |||||
if (sret == SQLITE_DONE) { | |||||
uio_debug("Cache entry with size (%lu) and name (%s) not found", fsize, fname); | |||||
err = ERR_CACHE_NO_EXISTS; | |||||
} else if (sret == SQLITE_ROW) { | |||||
uio_debug("Found Cache entry with size (%lu) and name (%s)", fsize, fname); | |||||
} else { | |||||
uio_error("sqlite_step failed: %s", sqlite3_errmsg(cache_db)); | |||||
err = ERR_CACHE_SQLITE; | |||||
} | |||||
fail: | |||||
sqlite3_finalize(smt); | |||||
return err; | |||||
} |
@@ -0,0 +1,40 @@ | |||||
#ifndef _CACHE_H | |||||
#define _CACHE_H | |||||
#include <stdint.h> | |||||
#include <stdbool.h> | |||||
#include "error.h" | |||||
#include "ed2k.h" | |||||
struct cache_entry { | |||||
uint64_t lid, fsize; | |||||
char *fname; | |||||
uint8_t ed2k[ED2K_HASH_SIZE]; | |||||
}; | |||||
/* | |||||
* Init tha cache | |||||
*/ | |||||
enum error cache_init(); | |||||
/* | |||||
* Free tha cache | |||||
*/ | |||||
void cache_free(); | |||||
/* | |||||
* Add a new mylist entry to the cache | |||||
*/ | |||||
enum error cache_add(uint64_t lid, const char *fname, | |||||
uint64_t fsize, const uint8_t *ed2k); | |||||
/* | |||||
* Get a cache entry | |||||
* | |||||
* out_ce can be NULL. Useful, if we only want | |||||
* to check if the entry exists or not. | |||||
*/ | |||||
enum error cache_get(const char *fname, uint64_t size, | |||||
struct cache_entry *out_ce); | |||||
#endif /* _CACHE_H */ |
@@ -0,0 +1,29 @@ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <signal.h> | |||||
#include "config.h" | |||||
#include "error.h" | |||||
#include "uio.h" | |||||
#include "cmd.h" | |||||
int main(int argc, char **argv) | |||||
{ | |||||
int exit_code = EXIT_SUCCESS; | |||||
enum error err = config_parse(argc, argv); | |||||
if (err == ERR_OPT_EXIT) | |||||
return EXIT_SUCCESS; | |||||
else if (err != NOERR) | |||||
return EXIT_FAILURE; | |||||
//config_dump(); | |||||
err = cmd_main(); | |||||
if (err != NOERR) | |||||
exit_code = EXIT_FAILURE; | |||||
config_free(); | |||||
return exit_code; | |||||
} |
@@ -0,0 +1,75 @@ | |||||
#include <stdbool.h> | |||||
#include "cmd.h" | |||||
#include "error.h" | |||||
#include "config.h" | |||||
#include "api.h" | |||||
#include "uio.h" | |||||
#include "net.h" | |||||
#include "cache.h" | |||||
struct cmd_entry { | |||||
bool need_api : 1; /* Does this command needs to connect to the api? */ | |||||
bool need_auth : 1; /* Does this command needs auth to the api? sets need_api */ | |||||
bool need_cache : 1; /* Does this cmd needs the file cache? */ | |||||
const char *arg_name; /* If this argument is present, execute this cmd */ | |||||
enum error (*fn)(void *data); /* The function for the command */ | |||||
}; | |||||
static const struct cmd_entry ents[] = { | |||||
{ .arg_name = "version", .fn = cmd_prog_version, }, | |||||
{ .arg_name = "server-version", .fn = cmd_server_version, .need_api = true }, | |||||
{ .arg_name = "uptime", .fn = cmd_server_uptime, .need_auth = true }, | |||||
{ .arg_name = "ed2k", .fn = cmd_ed2k, }, | |||||
{ .arg_name = "add", .fn = cmd_add, .need_auth = true, .need_cache = true, }, | |||||
}; | |||||
static const int32_t ents_len = sizeof(ents)/sizeof(*ents); | |||||
static enum error cmd_run_one(const struct cmd_entry *ent) | |||||
{ | |||||
enum error err = NOERR; | |||||
if (ent->need_cache) { | |||||
err = cache_init(); | |||||
if (err != NOERR) | |||||
goto end; | |||||
} | |||||
if (ent->need_api || ent->need_auth) { | |||||
err = api_init(ent->need_auth); | |||||
if (err != NOERR) | |||||
return err; | |||||
} | |||||
void *data = NULL; | |||||
err = ent->fn(data); | |||||
end: | |||||
if (ent->need_api || ent->need_auth) | |||||
api_free(); | |||||
if (ent->need_cache) | |||||
cache_free(); | |||||
return err; | |||||
} | |||||
enum error cmd_main() | |||||
{ | |||||
for (int i = 0; i < ents_len; i++) { | |||||
enum error err; | |||||
bool *is_set; | |||||
err = config_get(ents[i].arg_name, (void**)&is_set); | |||||
if (err != NOERR && err != ERR_OPT_UNSET) { | |||||
uio_error("Cannot get arg '%s' (%s)", ents[i].arg_name, | |||||
error_to_string(err)); | |||||
continue; | |||||
} | |||||
if (*is_set) { | |||||
err = cmd_run_one(&ents[i]); | |||||
return err; | |||||
} | |||||
} | |||||
return ERR_CMD_NONE; | |||||
} |
@@ -0,0 +1,38 @@ | |||||
#ifndef _CMD_H | |||||
#define _CMD_H | |||||
#include "error.h" | |||||
#include "config.h" | |||||
/* | |||||
* Read commands from config and execute them | |||||
*/ | |||||
enum error cmd_main(); | |||||
/* | |||||
* Add files to the AniDB list | |||||
*/ | |||||
enum error cmd_add(void *); | |||||
/* | |||||
* Take in a file/folder and print out | |||||
* the ed2k hash of it | |||||
*/ | |||||
enum error cmd_ed2k(void *data); | |||||
/* | |||||
* Get and print the server api version | |||||
*/ | |||||
enum error cmd_server_version(void *); | |||||
/* | |||||
* Print the server uptime | |||||
*/ | |||||
enum error cmd_server_uptime(void *); | |||||
/* | |||||
* Print the program version | |||||
*/ | |||||
enum error cmd_prog_version(void *); | |||||
#endif /* _CMD_H */ |
@@ -0,0 +1,114 @@ | |||||
#include <sys/stat.h> | |||||
#include <stdlib.h> | |||||
#include <stdio.h> | |||||
#include <stdbool.h> | |||||
#include "cmd.h" | |||||
#include "error.h" | |||||
#include "uio.h" | |||||
#include "api.h" | |||||
#include "config.h" | |||||
#include "ed2k_util.h" | |||||
#include "cache.h" | |||||
#include "util.h" | |||||
struct add_opts { | |||||
enum mylist_state ao_state; | |||||
bool ao_watched; | |||||
}; | |||||
enum error cmd_add_cachecheck(const char *path, const struct stat *st, | |||||
void *data) | |||||
{ | |||||
const char *bname = util_basename(path); | |||||
enum error err; | |||||
err = cache_get(bname, st->st_size, NULL); | |||||
if (err == NOERR) { | |||||
/* We could get the entry, so it exists already */ | |||||
uio_user("This file (%s) with size (%lu) already exists in cache." | |||||
" Skipping", bname, st->st_size); | |||||
return ED2KUTIL_DONTHASH; | |||||
} else if (err != ERR_CACHE_NO_EXISTS) { | |||||
uio_error("Some error when trying to get from cache: %s", | |||||
error_to_string(err)); | |||||
return ED2KUTIL_DONTHASH; | |||||
} | |||||
uio_user("Hashing %s", path); | |||||
return NOERR; | |||||
} | |||||
enum error cmd_add_apisend(const char *path, const uint8_t *hash, | |||||
const struct stat *st, void *data) | |||||
{ | |||||
struct api_result r; | |||||
struct add_opts* ao = (struct add_opts*)data; | |||||
if (api_cmd_mylistadd(st->st_size, hash, ao->ao_state, ao->ao_watched, &r) | |||||
!= NOERR) | |||||
return ERR_CMD_FAILED; | |||||
if (r.code == 310) { | |||||
struct api_mylistadd_result *x = &r.mylistadd; | |||||
uio_warning("File already added! Adding it to cache"); | |||||
uio_debug("File info: lid: %ld, fid: %ld, eid: %ld, aid: %ld," | |||||
" gid: %ld, date: %ld, viewdate: %ld, state: %d," | |||||
" filestate: %d\nstorage: %s\nsource: %s\nother: %s", | |||||
x->lid, x->fid, x->eid, x->aid, x->gid, x->date, x->viewdate, | |||||
x->state, x->filestate, x->storage, x->source, x->other); | |||||
cache_add(x->lid, util_basename(path), st->st_size, hash); | |||||
if (x->storage) | |||||
free(x->storage); | |||||
if (x->source) | |||||
free(x->source); | |||||
if (x->other) | |||||
free(x->other); | |||||
return NOERR; | |||||
} | |||||
if (r.code != 210) { | |||||
uio_error("Mylistadd failure: %hu", r.code); | |||||
return ERR_CMD_FAILED; | |||||
} | |||||
uio_user("Succesfully added!"); | |||||
uio_debug("New mylist id is: %ld", r.mylistadd.new_id); | |||||
cache_add(r.mylistadd.new_id, util_basename(path), st->st_size, hash); | |||||
return NOERR; | |||||
} | |||||
enum error cmd_add(void *data) | |||||
{ | |||||
struct add_opts add_opts = {0}; | |||||
struct ed2k_util_opts ed2k_opts = { | |||||
.pre_hash_fn = cmd_add_cachecheck, | |||||
.post_hash_fn = cmd_add_apisend, | |||||
.data = &add_opts, | |||||
}; | |||||
bool *watched; | |||||
enum error err = NOERR; | |||||
int fcount; | |||||
fcount = config_get_nonopt_count(); | |||||
if (fcount == 0) { | |||||
uio_error("No files specified"); | |||||
return ERR_CMD_ARG; | |||||
} | |||||
if (config_get("watched", (void**)&watched) == NOERR) { | |||||
add_opts.ao_watched = *watched; | |||||
} | |||||
add_opts.ao_state = MYLIST_STATE_INTERNAL; | |||||
for (int i = 0; i < fcount; i++) { | |||||
err = ed2k_util_iterpath(config_get_nonopt(i), &ed2k_opts); | |||||
if (err != NOERR) | |||||
break; | |||||
} | |||||
return err; | |||||
} | |||||
@@ -0,0 +1,62 @@ | |||||
#include <sys/stat.h> | |||||
#include <stdio.h> | |||||
#include <stdbool.h> | |||||
#include "cmd.h" | |||||
#include "error.h" | |||||
#include "uio.h" | |||||
#include "config.h" | |||||
#include "ed2k_util.h" | |||||
#include "ed2k.h" | |||||
#include "util.h" | |||||
struct cmd_ed2k_opts { | |||||
bool link; | |||||
}; | |||||
static enum error cmd_ed2k_output(const char *path, const uint8_t *hash, | |||||
const struct stat *st, void *data) | |||||
{ | |||||
struct cmd_ed2k_opts *eo = data; | |||||
char buff[ED2K_HASH_SIZE * 2 + 1]; | |||||
bool upcase = eo->link; | |||||
util_byte2hex(hash, ED2K_HASH_SIZE, upcase, buff); | |||||
if (eo->link) { | |||||
char *name_part = util_basename(path); | |||||
printf("ed2k://|file|%s|%ld|%s|/\n", name_part, st->st_size, buff); | |||||
} else { | |||||
printf("%s\t%s\n", buff, path); | |||||
} | |||||
return NOERR; | |||||
} | |||||
enum error cmd_ed2k(void *data) | |||||
{ | |||||
struct cmd_ed2k_opts opts = {0}; | |||||
struct ed2k_util_opts ed2k_opts = { | |||||
.post_hash_fn = cmd_ed2k_output, | |||||
.data = &opts, | |||||
}; | |||||
bool *link; | |||||
enum error err = NOERR; | |||||
int fcount; | |||||
fcount = config_get_nonopt_count(); | |||||
if (fcount == 0) { | |||||
uio_error("No files specified"); | |||||
return ERR_CMD_ARG; | |||||
} | |||||
if (config_get("link", (void**)&link) == NOERR) | |||||
opts.link = *link; | |||||
for (int i = 0; i < fcount; i++) { | |||||
err = ed2k_util_iterpath(config_get_nonopt(i), &ed2k_opts); | |||||
if (err != NOERR) | |||||
break; | |||||
} | |||||
return err; | |||||
} | |||||
@@ -0,0 +1,18 @@ | |||||
#include <stdio.h> | |||||
#include "cmd.h" | |||||
#include "cache.h" | |||||
#include <string.h> | |||||
enum error cmd_prog_version(void *data) | |||||
{ | |||||
enum error err = NOERR; | |||||
printf("caniadd v0.1.0" | |||||
#ifdef GIT_REF | |||||
" (" GIT_REF ")" | |||||
#endif | |||||
"\n"); | |||||
return err; | |||||
} |
@@ -0,0 +1,31 @@ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include "cmd.h" | |||||
#include "uio.h" | |||||
#include "api.h" | |||||
enum error cmd_server_uptime(void *data) | |||||
{ | |||||
struct api_result r; | |||||
int32_t h, m, s; | |||||
div_t dt; | |||||
if (api_cmd_uptime(&r) != NOERR) | |||||
return ERR_CMD_FAILED; | |||||
if (r.code != 208) { | |||||
uio_error("VERSION cmd is unsuccesful: %hu", r.code); | |||||
return ERR_CMD_FAILED; | |||||
} | |||||
dt = div(r.uptime.ms, 1000*60*60); | |||||
h = dt.quot; | |||||
dt = div(dt.rem, 1000*60); | |||||
m = dt.quot; | |||||
dt = div(dt.rem, 1000); | |||||
s = dt.quot; | |||||
printf("up %d hours, %d minutes, %d seconds\n", h, m, s); | |||||
return NOERR; | |||||
} |
@@ -0,0 +1,23 @@ | |||||
#include <stdio.h> | |||||
#include "cmd.h" | |||||
#include "uio.h" | |||||
#include "api.h" | |||||
enum error cmd_server_version(void *data) | |||||
{ | |||||
struct api_result a_res; | |||||
struct api_version_result *vr = &a_res.version; | |||||
if (api_cmd_version(&a_res) != NOERR) | |||||
return ERR_CMD_FAILED; | |||||
if (a_res.code == 998) { | |||||
printf("%s\n", vr->version_str); | |||||
} else { | |||||
uio_error("VERSION cmd is unsuccesful: %hu", a_res.code); | |||||
return ERR_CMD_FAILED; | |||||
} | |||||
return NOERR; | |||||
} |
@@ -0,0 +1,539 @@ | |||||
#include <stddef.h> | |||||
#include <inttypes.h> | |||||
#include <getopt.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <stdbool.h> | |||||
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <unistd.h> | |||||
#include <errno.h> | |||||
#include <assert.h> | |||||
//#include <toml.h> | |||||
#include <limits.h> | |||||
#include <time.h> | |||||
#include <unistd.h> | |||||
#include <ctype.h> | |||||
#include "config.h" | |||||
#include "error.h" | |||||
#include "util.h" | |||||
static int show_help(struct conf_entry *ce); | |||||
static int config_parse_file(); | |||||
static enum error config_required_check(); | |||||
static int config_set_str(struct conf_entry *ce, char *arg); | |||||
static int config_set_port(struct conf_entry *ce, char *arg); | |||||
static int config_set_bool(struct conf_entry *ce, char *arg); | |||||
//static int config_def_cachedb(struct conf_entry *ce); | |||||
//static int config_action_write_config(struct conf_entry *ce); | |||||
/* Everything not explicitly defined, is 0 */ | |||||
/* If an option only has a long name, the short name also has to be | |||||
* defined. For example, a number larger than UCHAR_MAX */ | |||||
static struct conf_entry options[] = { | |||||
{ .l_name = "help", .s_name = 'h', .has_arg = no_argument, | |||||
.action_func = show_help, .in_args = true, | |||||
.type = OTYPE_ACTION, .handle_order = 0 }, | |||||
/* | |||||
{ .l_name = "config-dir", .s_name = 'b', .has_arg = required_argument, | |||||
.default_func = config_def_config_dir, .set_func = config_set_str, | |||||
.in_args = true, .type = OTYPE_S, .handle_order = 0 }, | |||||
{ .l_name = "default-download-dir", .s_name = 'd', .has_arg = required_argument, | |||||
.default_func = config_def_default_download_dir, .set_func = config_set_str, | |||||
.in_file = true, .in_args = true, .type = OTYPE_S, .handle_order = 1 }, | |||||
{ .l_name = "port", .s_name = 'p', .has_arg = required_argument, | |||||
.set_func = config_set_port, .value.hu = 21729, .value_is_set = true, | |||||
.in_file = true, .in_args = true, .type = OTYPE_HU, .handle_order = 1 }, | |||||
{ .l_name = "foreground", .s_name = 'f', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .value.b = false, .value_is_set = true, | |||||
.in_args = true, .type = OTYPE_B, .handle_order = 1 }, | |||||
{ .l_name = "write-config", .s_name = UCHAR_MAX + 1, .has_arg = no_argument, | |||||
.action_func = config_action_write_config, .value_is_set = true, | |||||
.in_args = true, .type = OTYPE_ACTION, .handle_order = 2 }, | |||||
{ .l_name = "peer-id", .s_name = UCHAR_MAX + 2, .has_arg = required_argument, | |||||
.default_func = config_def_peer_id, .type = OTYPE_S, .handle_order = 1 }, | |||||
*/ | |||||
{ .l_name = "username", .s_name = 'u', .has_arg = required_argument, | |||||
.set_func = config_set_str, .in_args = true, .in_file = true, | |||||
.type = OTYPE_S, .handle_order = 1 }, | |||||
{ .l_name = "password", .s_name = 'p', .has_arg = required_argument, | |||||
.set_func = config_set_str, .in_args = true, .in_file = true, | |||||
.type = OTYPE_S, .handle_order = 1 }, | |||||
{ .l_name = "port", .s_name = 'P', .has_arg = required_argument, | |||||
.set_func = config_set_port, .in_args = true, .in_file = true, | |||||
.type = OTYPE_HU, .handle_order = 1, .value.hu = 29937, | |||||
.value_is_set = true }, | |||||
{ .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument, | |||||
.set_func = config_set_str, .in_args = true, .in_file = true, | |||||
.type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000", | |||||
.value_is_set = true }, | |||||
{ .l_name = "api-key", .s_name = 'k', .has_arg = required_argument, | |||||
.set_func = config_set_str, .in_args = true, .in_file = true, | |||||
.type = OTYPE_S, .handle_order = 1, }, | |||||
{ .l_name = "save-session", .s_name = 's', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .in_args = true, .in_file = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .in_args = true, .in_file = false, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "watched", .s_name = 'w', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "link", .s_name = 'l', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument, | |||||
.set_func = config_set_str, .in_args = true, .in_file = true, | |||||
.type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/ }, | |||||
{ .l_name = "debug", .s_name = 'D', .has_arg = no_argument, | |||||
.set_func = config_set_bool, .in_args = true, .in_file = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true, }, | |||||
/*### cmd ###*/ | |||||
{ .l_name = "server-version", .s_name = UCHAR_MAX + 2, | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "version", .s_name = 'v', | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "uptime", .s_name = UCHAR_MAX + 3, | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true }, | |||||
{ .l_name = "ed2k", .s_name = 'e', | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1 }, | |||||
{ .l_name = "add", .s_name = 'a', | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1 }, | |||||
/*{ .l_name = "stats", .s_name = UCHAR_MAX + 4, | |||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | |||||
.type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/ | |||||
}; | |||||
static const size_t options_count = sizeof(options) / sizeof(options[0]); | |||||
static const char **opt_argv = NULL; | |||||
static int opt_argc = 0; | |||||
static void config_build_getopt_args(char out_sopt[options_count * 2 + 1], | |||||
struct option out_lopt[options_count + 1]) | |||||
{ | |||||
int i_sopt = 0, i_lopt = 0; | |||||
for (int i = 0; i < options_count; i++) { | |||||
/* Short options */ | |||||
if (options[i].s_name && options[i].s_name <= UCHAR_MAX) { | |||||
out_sopt[i_sopt++] = options[i].s_name; | |||||
if (options[i].has_arg == required_argument) | |||||
out_sopt[i_sopt++] = ':'; | |||||
assert(options[i].has_arg == required_argument || | |||||
options[i].has_arg == no_argument); | |||||
} | |||||
/* Long options */ | |||||
if (options[i].l_name) { | |||||
assert(options[i].s_name); | |||||
out_lopt[i_lopt].name = options[i].l_name; | |||||
out_lopt[i_lopt].has_arg = options[i].has_arg; | |||||
out_lopt[i_lopt].flag = NULL; | |||||
out_lopt[i_lopt].val = options[i].s_name; | |||||
i_lopt++; | |||||
} | |||||
} | |||||
out_sopt[i_sopt] = '\0'; | |||||
memset(&out_lopt[i_lopt], 0, sizeof(struct option)); | |||||
} | |||||
static int config_read_args(int argc, char **argv, char sopt[options_count * 2 + 1], | |||||
struct option lopt[options_count + 1], int level) | |||||
{ | |||||
int optc, err = NOERR; | |||||
optind = 1; | |||||
while ((optc = getopt_long(argc, argv, sopt, | |||||
lopt, NULL)) >= 0) { | |||||
bool handled = false; | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (options[i].handle_order != level) { | |||||
/* Lie a lil :x */ | |||||
handled = true; | |||||
continue; | |||||
} | |||||
if (optc == options[i].s_name) { | |||||
if (options[i].type != OTYPE_ACTION) | |||||
err = options[i].set_func(&options[i], optarg); | |||||
else | |||||
err = options[i].action_func(&options[i]); | |||||
if (err != NOERR) | |||||
goto end; | |||||
options[i].value_is_set = true; | |||||
handled = true; | |||||
break; | |||||
} | |||||
} | |||||
if (handled) | |||||
continue; | |||||
if (optc == '?') { | |||||
err = ERR_OPT_FAILED; | |||||
goto end; | |||||
} else { | |||||
fprintf(stderr, "Unhandled option? '%c'\n", optc); | |||||
err = ERR_OPT_UNHANDLED; | |||||
goto end; | |||||
} | |||||
} | |||||
end: | |||||
return err; | |||||
} | |||||
static enum error config_required_check() | |||||
{ | |||||
enum error err = NOERR; | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (options[i].required && !options[i].value_is_set) { | |||||
printf("Argument %s is required!\n", options[i].l_name); | |||||
err = ERR_OPT_REQUIRED; | |||||
} | |||||
} | |||||
return err; | |||||
} | |||||
enum error config_parse(int argc, char **argv) | |||||
{ | |||||
enum error err = NOERR; | |||||
char sopt[options_count * 2 + 1]; | |||||
struct option lopt[options_count + 1]; | |||||
opt_argv = (const char**)argv; | |||||
opt_argc = argc; | |||||
config_build_getopt_args(sopt, lopt); | |||||
err = config_read_args(argc, argv, sopt, lopt, 0); | |||||
if (err != NOERR) | |||||
goto end; | |||||
err = config_parse_file(); | |||||
if (err != NOERR) | |||||
goto end; | |||||
err = config_read_args(argc, argv, sopt, lopt, 1); | |||||
if (err != NOERR) | |||||
goto end; | |||||
/* Set defaults for those, that didn't got set above */ | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (!options[i].value_is_set && options[i].type != OTYPE_ACTION && | |||||
options[i].default_func) { | |||||
err = options[i].default_func(&options[i]); | |||||
if (err != NOERR) | |||||
goto end; | |||||
options[i].value_is_set = true; | |||||
} | |||||
} | |||||
err = config_read_args(argc, argv, sopt, lopt, 2); | |||||
if (err != NOERR) | |||||
goto end; | |||||
err = config_required_check(); | |||||
end: | |||||
if (err != NOERR) | |||||
config_free(); | |||||
return err; | |||||
} | |||||
#if 0 | |||||
static int config_def_config_dir(struct conf_entry *ce) | |||||
{ | |||||
char *dir; | |||||
int len; | |||||
const char *format = "%s/.config/" CONFIG_DIR_NAME; | |||||
const char *home_env = getenv("HOME"); | |||||
if (!home_env) { | |||||
/* Fix this at a later date with getuid and getpw */ | |||||
fprintf(stderr, "HOME environment variable not found!\n"); | |||||
return ERR_NOTFOUND; | |||||
} | |||||
len = snprintf(NULL, 0, format, home_env); | |||||
if (len == -1) { | |||||
int err = errno; | |||||
fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err)); | |||||
return err; | |||||
} | |||||
dir = malloc(len + 1); | |||||
sprintf(dir, format, home_env); | |||||
ce->value.s = dir; | |||||
ce->value_is_dyn = true; | |||||
return NOERR; | |||||
} | |||||
#endif | |||||
#if 0 | |||||
static int config_def_cachedb(struct conf_entry *ce) | |||||
{ | |||||
bool dh_free = false; | |||||
const char *data_home = getenv("XDG_DATA_HOME"); | |||||
if (!data_home) { | |||||
const char *home = util_get_home(); | |||||
if (!home) | |||||
return ERR_OPT_FAILED; | |||||
sprintf(NULL, "%s/.local/share", home); | |||||
} | |||||
return NOERR; | |||||
} | |||||
#endif | |||||
static int config_set_str(struct conf_entry *ce, char *arg) | |||||
{ | |||||
// TODO use realpath(3), when necessary | |||||
ce->value.s = arg; | |||||
return NOERR; | |||||
} | |||||
static int config_set_port(struct conf_entry *ce, char *arg) | |||||
{ | |||||
long portval = strtol(arg, NULL, 10); | |||||
/* A zero return will be invalid no matter if strtol succeeded or not */ | |||||
if (portval > UINT16_MAX || portval <= 0) { | |||||
fprintf(stderr, "Invalid port value '%s'\n", arg); | |||||
return ERR_OPT_INVVAL; | |||||
} | |||||
ce->value.hu = (uint16_t)portval; | |||||
return NOERR; | |||||
} | |||||
static int config_set_bool(struct conf_entry *ce, char *arg) | |||||
{ | |||||
ce->value.b = true; | |||||
return NOERR; | |||||
} | |||||
static int show_help(struct conf_entry *ce) | |||||
{ | |||||
printf("Todo...\n"); | |||||
return ERR_OPT_EXIT; | |||||
} | |||||
static int config_parse_file() | |||||
{ | |||||
// TODO implement this | |||||
#if 0 | |||||
assert(conf.config_file_path); | |||||
FILE *f = fopen(conf.config_file_path, "rb"); | |||||
char errbuf[200]; | |||||
toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf)); | |||||
fclose(f); | |||||
if (!tml) { | |||||
fprintf(stderr, "Failed to parse config toml: %s\n", errbuf); | |||||
return ERR_TOML_PARSE_ERROR; | |||||
} | |||||
toml_datum_t port = toml_int_in(tml, "port"); | |||||
if (port.ok) | |||||
conf.port = (uint16_t)port.u.i; | |||||
else | |||||
fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf); | |||||
toml_datum_t dldir = toml_string_in(tml, "default_download_dir"); | |||||
if (dldir.ok) { | |||||
conf.default_download_dir = dldir.u.s; | |||||
printf("%s\n", dldir.u.s); | |||||
conf_dyn.default_download_dir = dldir.u.s; | |||||
conf_dyn.default_download_dir = true; | |||||
/* TODO is this always malloced?? if yes, remve dyn check */ | |||||
} else { | |||||
fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf); | |||||
} | |||||
toml_free(tml); | |||||
#endif | |||||
return NOERR; | |||||
} | |||||
int config_free() | |||||
{ | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (options[i].value_is_dyn) { | |||||
free(options[i].value.s); | |||||
options[i].value.s = NULL; | |||||
options[i].value_is_dyn = false; | |||||
options[i].value_is_set = false; | |||||
} | |||||
} | |||||
return NOERR; | |||||
} | |||||
#if 0 | |||||
static int config_action_write_config(struct conf_entry *ce) | |||||
{ | |||||
/* This is the success return here */ | |||||
int err = ERR_OPT_EXIT, plen; | |||||
const char *config_dir; | |||||
FILE *f = NULL; | |||||
config_dir = config_get("config-dir"); | |||||
plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME); | |||||
char path[plen + 1]; | |||||
snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME); | |||||
for (int i = 0; i < 2; i++) { | |||||
f = fopen(path, "wb"); | |||||
if (!f) { | |||||
int errn = errno; | |||||
if (errn == ENOENT && i == 0) { | |||||
/* Try to create parent directory */ | |||||
if (mkdir(config_dir, 0755) == -1) { | |||||
err = errno; | |||||
fprintf(stderr, "Config mkdir failed: %s\n", strerror(err)); | |||||
goto end; | |||||
} | |||||
} else { | |||||
err = errn; | |||||
fprintf(stderr, "Config fopen failed: %s\n", strerror(err)); | |||||
goto end; | |||||
} | |||||
} else { | |||||
break; | |||||
} | |||||
} | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (!options[i].in_file) | |||||
continue; | |||||
fprintf(f, "%s = ", options[i].l_name); | |||||
switch (options[i].type) { | |||||
case OTYPE_S: | |||||
// TODO toml escaping | |||||
fprintf(f, "\"%s\"\n", options[i].value.s); | |||||
break; | |||||
case OTYPE_HU: | |||||
fprintf(f, "%hu\n", options[i].value.hu); | |||||
break; | |||||
case OTYPE_B: | |||||
fprintf(f, "%s\n", options[i].value.b ? "true" : "false"); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
config_dump(); | |||||
end: | |||||
if (f) | |||||
fclose(f); | |||||
return err; | |||||
} | |||||
#endif | |||||
enum error config_get(const char *key, void **out) | |||||
{ | |||||
enum error err = ERR_OPT_NOTFOUND; | |||||
for (int i = 0; i < options_count; i++) { | |||||
struct conf_entry *cc = &options[i]; | |||||
if (strcmp(cc->l_name, key) == 0) { | |||||
if (cc->value_is_set) { | |||||
if (out) | |||||
*out = &cc->value.s; | |||||
err = NOERR; | |||||
} else { | |||||
err = ERR_OPT_UNSET; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
return err; | |||||
} | |||||
const char *config_get_nonopt(int index) | |||||
{ | |||||
if (index >= config_get_nonopt_count()) | |||||
return NULL; | |||||
return opt_argv[optind + index]; | |||||
} | |||||
int config_get_nonopt_count() | |||||
{ | |||||
return opt_argc - optind; | |||||
} | |||||
void config_dump() | |||||
{ | |||||
for (int i = 0; i < options_count; i++) { | |||||
if (options[i].type == OTYPE_ACTION) | |||||
continue; | |||||
printf("%s: ", options[i].l_name); | |||||
if (!options[i].value_is_set) { | |||||
printf("[UNSET (>.<)]\n"); | |||||
continue; | |||||
} | |||||
switch (options[i].type) { | |||||
case OTYPE_S: | |||||
printf("%s\n", options[i].value.s); | |||||
break; | |||||
case OTYPE_HU: | |||||
printf("%hu\n", options[i].value.hu); | |||||
break; | |||||
case OTYPE_B: | |||||
printf("%s\n", options[i].value.b ? "True" : "False"); | |||||
break; | |||||
default: | |||||
printf("Error :(\n"); | |||||
break; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,94 @@ | |||||
#ifndef _CONFIG_H | |||||
#define _CONFIG_H | |||||
#include <inttypes.h> | |||||
#include <stdbool.h> | |||||
#include "error.h" | |||||
#ifndef CONFIG_DIR_NAME | |||||
#define CONFIG_DIR_NAME "caniadd" | |||||
#endif | |||||
enum option_type { | |||||
OTYPE_S, /* Stores a string */ | |||||
OTYPE_HU, /* Stores an unsigned short */ | |||||
OTYPE_B, /* Stores a boolean */ | |||||
/* Does not store anything, does an action. Handled after every | |||||
* other option are parsed, and defaults set */ | |||||
OTYPE_ACTION, | |||||
_OTYPE_COUNT | |||||
}; | |||||
struct conf_entry { | |||||
const char *l_name; /* The long name for the option, or for the config file */ | |||||
int s_name; /* Short option name */ | |||||
union { /* Value of the param */ | |||||
char *s; | |||||
uint16_t hu; | |||||
bool b; | |||||
} value; | |||||
/* The function to use to init a default value, if it's a complex one */ | |||||
int (*default_func)(struct conf_entry *ce); | |||||
union { | |||||
/* The function to use to set the value of the arg from the | |||||
* command line or from the loaded config file */ | |||||
int (*set_func)(struct conf_entry *ce, char *arg); | |||||
/* Callback for an action option */ | |||||
int (*action_func)(struct conf_entry *ce); | |||||
}; | |||||
int has_arg : 4; /* Do we need to specify an argument for this option on the cmd line? */ | |||||
/* Did we set the value? If not, we may need to call the default func */ | |||||
bool value_is_set : 1; | |||||
/* Is the value required? */ | |||||
bool required : 1; | |||||
bool value_is_dyn : 1; /* Do we need to free the value? */ | |||||
bool in_file : 1; /* Is this option in the config file? */ | |||||
bool in_args : 1; /* Is this option in the argument list? */ | |||||
enum option_type type : 4; /* Type of the option's value */ | |||||
/* | |||||
* In which step do we handle this arg? | |||||
* We need this, because: | |||||
* 1. Read in the base dir option from the command line, if present | |||||
* 2. Use the base dir to load the options from the config file | |||||
* 3. Read, and override options from the command line | |||||
* 4. Execute action arguments | |||||
*/ | |||||
int handle_order : 4; | |||||
}; | |||||
/* | |||||
* Parse options from the command line | |||||
* | |||||
* Returns 0 on success. | |||||
*/ | |||||
enum error config_parse(int argc, char **argv); | |||||
/* | |||||
* Free any memory that may have been allocated | |||||
* in config_parse | |||||
*/ | |||||
int config_free(); | |||||
/* | |||||
* Write out the options to stdout | |||||
*/ | |||||
void config_dump(); | |||||
/* | |||||
* Get a config option by its long name | |||||
* The pointer to the options value will be stored, at the pointer pointed to | |||||
* by out | |||||
* If the option is unset, it returns ERR_OPT_UNSET and out is unchanged | |||||
* It the options is not found, it returns ERR_OPT_NOTFOUND, and out is unchanged | |||||
*/ | |||||
enum error config_get(const char *key, void **out); | |||||
/* | |||||
* Return an cmd line that is not an option | |||||
* or null if error, or out of elements | |||||
*/ | |||||
const char *config_get_nonopt(int index); | |||||
int config_get_nonopt_count(); | |||||
#endif /* _CONFIG_H */ |
@@ -0,0 +1,61 @@ | |||||
#include <assert.h> | |||||
#include "ed2k.h" | |||||
/* https://wiki.anidb.net/Ed2k-hash */ | |||||
/* This is using the red method */ | |||||
#define ED2K_CHUNK_SIZE 9728000 | |||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b)) | |||||
void ed2k_init(struct ed2k_ctx *ctx) | |||||
{ | |||||
md4_init(&ctx->chunk_md4_ctx); | |||||
md4_init(&ctx->hash_md4_ctx); | |||||
ctx->byte_count = 0; | |||||
} | |||||
static void ed2k_hash_chunk(struct ed2k_ctx *ctx) | |||||
{ | |||||
unsigned char chunk_hash[MD4_DIGEST_SIZE]; | |||||
md4_final(&ctx->chunk_md4_ctx, chunk_hash); | |||||
md4_update(&ctx->hash_md4_ctx, chunk_hash, sizeof(chunk_hash)); | |||||
md4_init(&ctx->chunk_md4_ctx); | |||||
} | |||||
void ed2k_update(struct ed2k_ctx *ctx, const void *data, size_t data_len) | |||||
{ | |||||
const char *bytes = (const char*)data; | |||||
while (data_len) { | |||||
size_t hdata_size = MIN(ED2K_CHUNK_SIZE - | |||||
(ctx->byte_count % ED2K_CHUNK_SIZE), data_len); | |||||
md4_update(&ctx->chunk_md4_ctx, bytes, hdata_size); | |||||
ctx->byte_count += hdata_size; | |||||
if (ctx->byte_count % ED2K_CHUNK_SIZE == 0) | |||||
ed2k_hash_chunk(ctx); | |||||
data_len -= hdata_size; | |||||
bytes += hdata_size; | |||||
} | |||||
} | |||||
void ed2k_final(struct ed2k_ctx *ctx, unsigned char *out_hash) | |||||
{ | |||||
struct md4_ctx *md_ctx; | |||||
if (ctx->byte_count < ED2K_CHUNK_SIZE) { | |||||
/* File has only 1 chunk, so return the md4 hash of that chunk */ | |||||
md_ctx = &ctx->chunk_md4_ctx; | |||||
} else { | |||||
/* Else hash the md4 hashes, and return that hash */ | |||||
ed2k_hash_chunk(ctx); /* Hash the last partial chunk here */ | |||||
md_ctx = &ctx->hash_md4_ctx; | |||||
} | |||||
md4_final(md_ctx, out_hash); | |||||
} | |||||
@@ -0,0 +1,18 @@ | |||||
#ifndef _ED2K_H | |||||
#define _ED2K_H | |||||
#include <stdint.h> | |||||
#include "md4.h" | |||||
#define ED2K_HASH_SIZE MD4_DIGEST_SIZE | |||||
struct ed2k_ctx { | |||||
struct md4_ctx hash_md4_ctx, chunk_md4_ctx; | |||||
uint64_t byte_count; | |||||
}; | |||||
void ed2k_init(struct ed2k_ctx *ctx); | |||||
void ed2k_update(struct ed2k_ctx *ctx, const void *data, size_t data_len); | |||||
void ed2k_final(struct ed2k_ctx *ctx, unsigned char *out_hash); | |||||
#endif /* _ED2K_H */ |
@@ -0,0 +1,93 @@ | |||||
#define _XOPEN_SOURCE 500 | |||||
#include <sys/stat.h> | |||||
#include <string.h> | |||||
#include <errno.h> | |||||
#include <ftw.h> | |||||
#include "ed2k.h" | |||||
#include "ed2k_util.h" | |||||
#include "uio.h" | |||||
static struct ed2k_util_opts l_opts; | |||||
static enum error ed2k_util_hash(const char *file_path, blksize_t blksize, | |||||
const struct stat *st) | |||||
{ | |||||
unsigned char buf[blksize], hash[ED2K_HASH_SIZE]; | |||||
struct ed2k_ctx ed2k; | |||||
FILE *f; | |||||
size_t read_len; | |||||
if (l_opts.pre_hash_fn) { | |||||
enum error err = l_opts.pre_hash_fn(file_path, st, l_opts.data); | |||||
if (err == ED2KUTIL_DONTHASH) | |||||
return NOERR; | |||||
else if (err != NOERR) | |||||
return err; | |||||
} | |||||
f = fopen(file_path, "rb"); | |||||
if (!f) { | |||||
uio_error("Failed to open file: %s (%s)", file_path, strerror(errno)); | |||||
return ERR_ED2KUTIL_FS; | |||||
} | |||||
ed2k_init(&ed2k); | |||||
read_len = fread(buf, 1, sizeof(buf), f); | |||||
while (read_len > 0) { | |||||
ed2k_update(&ed2k, buf, read_len); | |||||
read_len = fread(buf, 1, sizeof(buf), f); | |||||
} | |||||
// TODO check if eof or error | |||||
ed2k_final(&ed2k, hash); | |||||
fclose(f); | |||||
if (l_opts.post_hash_fn) | |||||
return l_opts.post_hash_fn(file_path, hash, st, l_opts.data); | |||||
return NOERR; | |||||
} | |||||
static int ed2k_util_walk(const char *fpath, const struct stat *sb, | |||||
int typeflag, struct FTW *ftwbuf) | |||||
{ | |||||
if (typeflag == FTW_DNR) { | |||||
uio_error("Cannot read directory '%s'. Skipping", fpath); | |||||
return NOERR; | |||||
} | |||||
if (typeflag == FTW_D) | |||||
return NOERR; | |||||
if (typeflag != FTW_F) { | |||||
uio_error("Unhandled error '%d'", typeflag); | |||||
return ERR_ED2KUTIL_UNSUP; | |||||
} | |||||
return ed2k_util_hash(fpath, sb->st_blksize, sb); | |||||
} | |||||
enum error ed2k_util_iterpath(const char *path, const struct ed2k_util_opts *opts) | |||||
{ | |||||
struct stat ts; | |||||
if (stat(path, &ts) != 0) { | |||||
uio_error("Stat failed for path: '%s' (%s)", | |||||
path, strerror(errno)); | |||||
return ERR_ED2KUTIL_FS; | |||||
} | |||||
l_opts = *opts; | |||||
if (S_ISREG(ts.st_mode)) { | |||||
return ed2k_util_hash(path, ts.st_blksize, &ts); | |||||
} else if (S_ISDIR(ts.st_mode)) { | |||||
int ftwret = nftw(path, ed2k_util_walk, 20, 0); | |||||
if (ftwret == -1) { | |||||
uio_error("nftw failure"); | |||||
return ERR_ED2KUTIL_FS; | |||||
} | |||||
return ftwret; | |||||
} | |||||
uio_error("Unsupported file type: %d", ts.st_mode & S_IFMT); | |||||
return ERR_ED2KUTIL_UNSUP; | |||||
} |
@@ -0,0 +1,33 @@ | |||||
#ifndef _ED2K_UTIL_H | |||||
#define _ED2K_UTIL_H | |||||
#include <sys/stat.h> | |||||
#include <stdint.h> | |||||
#include "error.h" | |||||
typedef enum error (*ed2k_util_fn)(const char *path, const uint8_t *hash, | |||||
const struct stat *st, void *data); | |||||
/* | |||||
* If this returns ED2KUTIL_DONTHASH, then skip the hashing, | |||||
* and the post_hash function | |||||
*/ | |||||
typedef enum error (*ed2k_util_prehash_fn)(const char *path, | |||||
const struct stat *st, void *data); | |||||
struct ed2k_util_opts { | |||||
ed2k_util_fn post_hash_fn; | |||||
ed2k_util_prehash_fn pre_hash_fn; | |||||
void *data; | |||||
}; | |||||
/* | |||||
* Given a path (file or directory) calculate the ed2k | |||||
* hash for the file(s), and call opts.post_hash_fn if not NULL | |||||
* if opts.pre_hash_fn is not NULL, then also call that before the hashing | |||||
* | |||||
* If fn returns any error, the iteration will stop, and this | |||||
* function will return with that error code. | |||||
*/ | |||||
enum error ed2k_util_iterpath(const char *path, const struct ed2k_util_opts *opts); | |||||
#endif /* _ED2K_UTIL_H */ |
@@ -0,0 +1,12 @@ | |||||
#include "error.h" | |||||
static const char *error_string[] = { | |||||
FE_ERROR(GEN_STRING) | |||||
}; | |||||
const char *error_to_string(enum error err) | |||||
{ | |||||
if (err >= _ERR_COUNT) | |||||
return "ERR_UNKNOWN"; | |||||
return error_string[err]; | |||||
} |
@@ -0,0 +1,63 @@ | |||||
#ifndef _ERROR_H | |||||
#define _ERROR_H | |||||
#define FE_ERROR(E) \ | |||||
E(NOERR = 0) \ | |||||
E(ERR_UNKNOWN) \ | |||||
E(ERR_NOTFOUND) \ | |||||
\ | |||||
E(ERR_OPT_REQUIRED) \ | |||||
E(ERR_OPT_FAILED) \ | |||||
E(ERR_OPT_UNHANDLED) \ | |||||
E(ERR_OPT_INVVAL) \ | |||||
E(ERR_OPT_EXIT) /* We should exit in main, if config_parse returns this */ \ | |||||
E(ERR_OPT_UNSET) /* In config_get, if the value isn't set */ \ | |||||
E(ERR_OPT_NOTFOUND) /* In config_get, if the options is not found */ \ | |||||
\ | |||||
E(ERR_NET_APIADDR) /* If there are problems with the api servers address */ \ | |||||
E(ERR_NET_SOCKET) /* If there are problems with the udp socket */ \ | |||||
E(ERR_NET_CONNECTED) /* Socket already connected */ \ | |||||
E(ERR_NET_CONNECT_FAIL) /* Connect attempt failed */ \ | |||||
E(ERR_NET_NOT_CONNECTED) /* Socket wasn't connected */ \ | |||||
\ | |||||
E(ERR_CMD_FAILED) /* Running the command failed */ \ | |||||
E(ERR_CMD_NONE) /* No command was run */ \ | |||||
E(ERR_CMD_ARG) /* Some problem with the command arguments */ \ | |||||
\ | |||||
E(ERR_ED2KUTIL_FS) /* Some filesystem problem */ \ | |||||
E(ERR_ED2KUTIL_UNSUP) /* Operation or file type is unsupported */ \ | |||||
E(ED2KUTIL_DONTHASH) /* Skip the hashing part. pre_hash_fn can return this */ \ | |||||
\ | |||||
E(ERR_API_ENCRYPTFAIL) /* Cannot start encryption with the api */ \ | |||||
E(ERR_API_COMMFAIL) /* Communication failure */ \ | |||||
E(ERR_API_RESP_INVALID) /* Invalid response */ \ | |||||
E(ERR_API_AUTH_FAIL) /* Auth failed */ \ | |||||
E(ERR_API_LOGOUT) /* Logout failed */ \ | |||||
E(ERR_API_PRINTFFUNC) /* New printf function registration failed */ \ | |||||
E(ERR_API_CLOCK) /* Some error with clocks */ \ | |||||
\ | |||||
E(ERR_CACHE_SQLITE) /* Generic sqlite error code */ \ | |||||
E(ERR_CACHE_EXISTS) /* Entry already exists, as determined by lid */ \ | |||||
/* The entry to be added is not unique, (filename and size duplicate, not hash or lid) */ \ | |||||
E(ERR_CACHE_NON_UNIQUE) \ | |||||
E(ERR_CACHE_NO_EXISTS) /* Entry does not exists */ \ | |||||
\ | |||||
E(ERR_THRD) /* Generic pthread error */ \ | |||||
\ | |||||
E(ERR_LIBEVENT) /* There are some problem with a libevent function */ \ | |||||
E(_ERR_COUNT) \ | |||||
#define GEN_ENUM(ENUM) ENUM, | |||||
#define GEN_STRING(STRING) #STRING, | |||||
enum error { | |||||
FE_ERROR(GEN_ENUM) | |||||
}; | |||||
/* | |||||
* Convert a number (0) to the enum name (NOERR) | |||||
*/ | |||||
const char *error_to_string(enum error err); | |||||
#endif /* _ERROR_H */ |
@@ -0,0 +1,265 @@ | |||||
#include <arpa/inet.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/time.h> | |||||
#include <netdb.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <limits.h> | |||||
#include <ctype.h> | |||||
#include <assert.h> | |||||
#include <unistd.h> | |||||
#include <stdbool.h> | |||||
#include <errno.h> | |||||
#include "net.h" | |||||
#include "config.h" | |||||
#include "uio.h" | |||||
static struct addrinfo net_server; | |||||
static bool net_server_set = false; | |||||
static bool net_connected = false; | |||||
static int net_socket = -1; | |||||
static bool net_parse_address(const char* srv, size_t *out_domain_len, | |||||
char **out_port_start) | |||||
{ | |||||
char *port_iter; | |||||
char *port_start = strchr(srv, ':'); | |||||
if (!port_start) { | |||||
*out_port_start = NULL; | |||||
*out_domain_len = strlen(srv); | |||||
return true; | |||||
} | |||||
/* Only one ':' is allowed */ | |||||
if (strchr(port_start + 1, ':')) | |||||
return false; | |||||
*out_domain_len = port_start - srv; | |||||
/*port = strtol(port_start + 1, &port_end, 10); | |||||
if (port_end == port_start || *port_end != '\0' || | |||||
((port == LONG_MIN || port == LONG_MAX) && errno)) | |||||
return false;*/ | |||||
/*if (port <= 0 || port > 65535) | |||||
return false;*/ | |||||
port_iter = port_start + 1; | |||||
if (*port_iter == '\0') | |||||
return false; | |||||
while (*port_iter) { | |||||
if (!isdigit(*port_iter)) | |||||
return false; | |||||
port_iter++; | |||||
} | |||||
*out_port_start = port_start + 1; | |||||
return true; | |||||
} | |||||
static const void *net_get_sockaddr_addr(const struct sockaddr *sa) | |||||
{ | |||||
switch (sa->sa_family) { | |||||
case AF_INET: | |||||
return &((struct sockaddr_in*)sa)->sin_addr; | |||||
case AF_INET6: | |||||
return &((struct sockaddr_in6*)sa)->sin6_addr; | |||||
default: | |||||
uio_error("Sockaddr is not ipv4 or ipv6"); | |||||
exit(1); | |||||
} | |||||
} | |||||
static bool net_lookup_server(const char *domain, const char *port, | |||||
struct addrinfo *out_addr) | |||||
{ | |||||
struct addrinfo hints = { | |||||
.ai_family = AF_UNSPEC, | |||||
//.ai_family = AF_INET6, | |||||
.ai_socktype = SOCK_DGRAM, | |||||
.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG, | |||||
//.ai_flags = AI_NUMERICSERV, | |||||
}, *res = NULL, *curr_res; | |||||
int ret = getaddrinfo(domain, port, &hints, &res); | |||||
if (ret != 0) { | |||||
uio_error("Cannot get addrinfo from address: '%s:%s' (%s)", | |||||
domain, port, gai_strerror(ret)); | |||||
return false; | |||||
} | |||||
curr_res = res; | |||||
while (curr_res) { | |||||
char ip_buffer[INET6_ADDRSTRLEN] = {0}; | |||||
if (inet_ntop(curr_res->ai_family, | |||||
net_get_sockaddr_addr(curr_res->ai_addr), | |||||
ip_buffer, sizeof(ip_buffer))) | |||||
uio_debug("Lookup addrinfo entry: %s", ip_buffer); | |||||
else | |||||
uio_debug("Cannot convert binary ip to string: %s", | |||||
strerror(errno)); | |||||
/* For now, always choose the first one */ | |||||
break; | |||||
curr_res = curr_res->ai_next; | |||||
} | |||||
if (!curr_res) { | |||||
uio_error("Cannot select a usable address."); | |||||
freeaddrinfo(res); | |||||
return false; | |||||
} | |||||
*out_addr = *curr_res; | |||||
out_addr->ai_addr = malloc(sizeof(struct sockaddr)); | |||||
*out_addr->ai_addr = *curr_res->ai_addr; | |||||
out_addr->ai_next = NULL; | |||||
out_addr->ai_canonname = NULL; | |||||
freeaddrinfo(res); | |||||
return true; | |||||
} | |||||
int net_socket_setup() | |||||
{ | |||||
struct sockaddr l_addr = {0}; | |||||
socklen_t l_addr_len; | |||||
int sock; | |||||
uint16_t *port; | |||||
enum error err; | |||||
if ((err = config_get("port", (void**)&port)) != NOERR) { | |||||
uio_error("Cannot get UDP binding port from config (%s)", | |||||
error_to_string(err)); | |||||
return -1; | |||||
} | |||||
sock = socket(net_server.ai_family, net_server.ai_socktype, | |||||
net_server.ai_protocol); | |||||
if (sock == -1) { | |||||
uio_error("Cannot create new socket: %s", strerror(errno)); | |||||
return -1; | |||||
} | |||||
l_addr.sa_family = net_server.ai_family; | |||||
if (net_server.ai_family == AF_INET) { | |||||
struct sockaddr_in *tmp = (struct sockaddr_in*)&l_addr; | |||||
l_addr_len = sizeof(struct sockaddr_in); | |||||
tmp->sin_port = htons(*port); | |||||
tmp->sin_addr.s_addr = INADDR_ANY; | |||||
} else { | |||||
struct sockaddr_in6 *tmp = (struct sockaddr_in6*)&l_addr; | |||||
l_addr_len = sizeof(struct sockaddr_in6); | |||||
tmp->sin6_port = htons(*port); | |||||
tmp->sin6_addr = in6addr_any; | |||||
} | |||||
if (bind(sock, &l_addr, l_addr_len) != 0) { | |||||
uio_error("Cannot bind UDP socket to local port: %s", strerror(errno)); | |||||
close(sock); | |||||
return -1; | |||||
} | |||||
return sock; | |||||
} | |||||
static enum error net_connect(int sock, struct addrinfo *ai) | |||||
{ | |||||
if (net_connected) | |||||
return ERR_NET_CONNECTED; | |||||
if (connect(sock, ai->ai_addr, ai->ai_addrlen) != 0) { | |||||
uio_error("Cannot connect to the server: %s\n", strerror(errno)); | |||||
return ERR_NET_CONNECT_FAIL; | |||||
} | |||||
net_connected = true; | |||||
return NOERR; | |||||
} | |||||
enum error net_init() | |||||
{ | |||||
enum error err; | |||||
const char **srv = NULL; | |||||
char *port_start = NULL; | |||||
size_t domain_len; | |||||
int sock; | |||||
err = config_get("api-server", (void**)&srv); | |||||
if (err != NOERR) { | |||||
uio_error("Cannot get the api servers address (%s).", error_to_string(err)); | |||||
return ERR_NET_APIADDR; | |||||
} | |||||
if (!net_parse_address(*srv, &domain_len, &port_start)) { | |||||
uio_error("Cannot parse the api server address: '%s'.", *srv); | |||||
return ERR_NET_APIADDR; | |||||
} | |||||
/* Port will be set to NULL, if its not in the address */ | |||||
if (port_start == NULL) | |||||
port_start = "9000"; | |||||
char api_domain[domain_len + 1]; | |||||
memcpy(api_domain, *srv, domain_len); | |||||
api_domain[domain_len] = '\0'; | |||||
if (!net_lookup_server(api_domain, port_start, &net_server)) { | |||||
//uio_error("Cannot look up the api server address"); | |||||
return ERR_NET_APIADDR; | |||||
} | |||||
net_server_set = true; | |||||
sock = net_socket_setup(); | |||||
if (sock == -1) { | |||||
return ERR_NET_SOCKET; | |||||
} | |||||
err = net_connect(sock, &net_server); | |||||
if (err != NOERR) { | |||||
net_free(); | |||||
return err; | |||||
} | |||||
net_socket = sock; | |||||
return NOERR; | |||||
} | |||||
void net_free() | |||||
{ | |||||
if (net_server_set) { | |||||
free(net_server.ai_addr); | |||||
memset(&net_server, 0, sizeof(net_server)); | |||||
net_server_set = false; | |||||
} | |||||
if (net_socket != -1) { | |||||
if (net_connected) | |||||
shutdown(net_socket, SHUT_RDWR); | |||||
close(net_socket); | |||||
net_socket = -1; | |||||
} | |||||
net_connected = false; | |||||
} | |||||
ssize_t net_send(const void *msg, size_t msg_len) | |||||
{ | |||||
ssize_t w_len = send(net_socket, msg, msg_len, 0); | |||||
if (w_len == -1) { | |||||
uio_error("{net} Send failed: %s", strerror(errno)); | |||||
return -1; | |||||
} | |||||
return w_len; | |||||
} | |||||
ssize_t net_read(void* out_data, size_t read_size) | |||||
{ | |||||
ssize_t read = recv(net_socket, out_data, read_size, 0); | |||||
if (read == -1) { | |||||
uio_error("{net} Read failed: %s", strerror(errno)); | |||||
return -1; | |||||
} | |||||
return read; | |||||
} |
@@ -0,0 +1,24 @@ | |||||
#ifndef _NET_H | |||||
#define _NET_H | |||||
#include <stdint.h> | |||||
#include <sys/types.h> | |||||
#include "error.h" | |||||
/* | |||||
* Initializes the net class | |||||
*/ | |||||
enum error net_init(); | |||||
/* | |||||
* Send and read data to and from the api | |||||
*/ | |||||
ssize_t net_send(const void *msg, size_t msg_len); | |||||
ssize_t net_read(void *out_data, size_t read_size); | |||||
/* | |||||
* Frees the net class | |||||
*/ | |||||
void net_free(); | |||||
#endif /* _NET_H */ |
@@ -0,0 +1,58 @@ | |||||
#include <stdarg.h> | |||||
#include <stdio.h> | |||||
#include "uio.h" | |||||
#include "config.h" | |||||
void uio_user(const char *format, ...) | |||||
{ | |||||
va_list ap; | |||||
va_start(ap, format); | |||||
vprintf(format, ap); | |||||
printf("\n"); | |||||
va_end(ap); | |||||
} | |||||
void uio_error(const char *format, ...) | |||||
{ | |||||
va_list ap; | |||||
va_start(ap, format); | |||||
printf("\033[31m[ERROR]: "); | |||||
vprintf(format, ap); | |||||
printf("\033[0m\n"); | |||||
va_end(ap); | |||||
} | |||||
void uio_debug(const char *format, ...) | |||||
{ | |||||
bool *dbg_enabled; | |||||
va_list ap; | |||||
config_get("debug", (void**)&dbg_enabled); | |||||
if (!*dbg_enabled) | |||||
return; | |||||
va_start(ap, format); | |||||
printf("\033[35m[DEBUG]: "); | |||||
vprintf(format, ap); | |||||
printf("\033[0m\n"); | |||||
va_end(ap); | |||||
} | |||||
void uio_warning(const char *format, ...) | |||||
{ | |||||
va_list ap; | |||||
va_start(ap, format); | |||||
printf("\033[33m[WARNING]: "); | |||||
vprintf(format, ap); | |||||
printf("\033[0m\n"); | |||||
va_end(ap); | |||||
} |
@@ -0,0 +1,9 @@ | |||||
#ifndef _UIO_H | |||||
#define _UIO_H | |||||
void uio_user(const char *format, ...) __attribute__((format (printf, 1, 2))); | |||||
void uio_error(const char *format, ...) __attribute__((format (printf, 1, 2))); | |||||
void uio_debug(const char *format, ...) __attribute__((format (printf, 1, 2))); | |||||
void uio_warning(const char *format, ...) __attribute__((format (printf, 1, 2))); | |||||
#endif /* _UIO_H */ |
@@ -0,0 +1,39 @@ | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "util.h" | |||||
void util_byte2hex(const uint8_t* bytes, size_t bytes_len, | |||||
bool uppercase, char* out) | |||||
{ | |||||
const char* hex = (uppercase) ? "0123456789ABCDEF" : "0123456789abcdef"; | |||||
for (size_t i = 0; i < bytes_len; i++) { | |||||
*out++ = hex[bytes[i] >> 4]; | |||||
*out++ = hex[bytes[i] & 0xF]; | |||||
} | |||||
*out = '\0'; | |||||
} | |||||
const char *util_get_home() | |||||
{ | |||||
const char *home_env = getenv("HOME"); | |||||
return home_env; /* TODO this can be null, use other methods as fallback */ | |||||
} | |||||
char *util_basename(const char *fullpath) | |||||
{ | |||||
char *name_part = strrchr(fullpath, '/'); | |||||
if (name_part) | |||||
name_part++; | |||||
else | |||||
name_part = (char*)fullpath; | |||||
return name_part; | |||||
} | |||||
uint64_t util_timespec_diff(const struct timespec *past, | |||||
const struct timespec *future) | |||||
{ | |||||
int64_t sdiff = future->tv_sec - past->tv_sec; | |||||
int64_t nsdiff = future->tv_nsec - past->tv_nsec; | |||||
return sdiff * 1000 + (nsdiff / 1000000); | |||||
} |
@@ -0,0 +1,35 @@ | |||||
#ifndef _UTIL_H | |||||
#define _UTIL_H | |||||
#include <stdint.h> | |||||
#include <stddef.h> | |||||
#include <stdbool.h> | |||||
/* | |||||
* Convert bytes to a hex string | |||||
* out needs to be at least (bytes_len * 2 + 1) bytes | |||||
*/ | |||||
void util_byte2hex(const uint8_t* bytes, size_t bytes_len, | |||||
bool uppercase, char* out); | |||||
/* | |||||
* Return the user's home directory | |||||
*/ | |||||
const char *util_get_home(); | |||||
/* | |||||
* Return the filename part of the path | |||||
* This will return a pointer in fullpath | |||||
* !! ONLY WORKS FOR FILES !! | |||||
*/ | |||||
char *util_basename(const char *fullpath); | |||||
/* | |||||
* Calculate the difference between 2 timespec structs in miliseconds | |||||
* | |||||
* future cannot be more in the past than past | |||||
* if that makes any sense | |||||
*/ | |||||
uint64_t util_timespec_diff(const struct timespec *past, | |||||
const struct timespec *future); | |||||
#endif /* _UTIL_H */ |
@@ -0,0 +1 @@ | |||||
Subproject commit 6771029ebea612a1a53822af6867b6cf172c31f0 |
@@ -0,0 +1 @@ | |||||
Subproject commit 1525b0db1fb608afed8948f3a972d55486e8cb31 |
@@ -0,0 +1 @@ | |||||
Subproject commit f06ac37fc31dfdaca2e0d9bec83f90d5663c319b |