init
This commit is contained in:
commit
a9a0e45906
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.o
|
||||||
|
caniadd
|
||||||
|
TODO
|
10
.gitmodules
vendored
Normal file
10
.gitmodules
vendored
Normal file
@ -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
|
36
Makefile
Normal file
36
Makefile
Normal file
@ -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
|
27
README.md
Normal file
27
README.md
Normal file
@ -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
|
802
src/api.c
Normal file
802
src/api.c
Normal file
@ -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
|
91
src/api.h
Normal file
91
src/api.h
Normal file
@ -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 */
|
208
src/cache.c
Normal file
208
src/cache.c
Normal file
@ -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;
|
||||||
|
}
|
40
src/cache.h
Normal file
40
src/cache.h
Normal file
@ -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 */
|
29
src/caniadd.c
Normal file
29
src/caniadd.c
Normal file
@ -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;
|
||||||
|
}
|
75
src/cmd.c
Normal file
75
src/cmd.c
Normal file
@ -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;
|
||||||
|
}
|
38
src/cmd.h
Normal file
38
src/cmd.h
Normal file
@ -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 */
|
114
src/cmd_add.c
Normal file
114
src/cmd_add.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
62
src/cmd_ed2k.c
Normal file
62
src/cmd_ed2k.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
18
src/cmd_prog_version.c
Normal file
18
src/cmd_prog_version.c
Normal file
@ -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;
|
||||||
|
}
|
31
src/cmd_server_uptime.c
Normal file
31
src/cmd_server_uptime.c
Normal file
@ -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;
|
||||||
|
}
|
23
src/cmd_server_version.c
Normal file
23
src/cmd_server_version.c
Normal file
@ -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;
|
||||||
|
}
|
539
src/config.c
Normal file
539
src/config.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
src/config.h
Normal file
94
src/config.h
Normal file
@ -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 */
|
61
src/ed2k.c
Normal file
61
src/ed2k.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
18
src/ed2k.h
Normal file
18
src/ed2k.h
Normal file
@ -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 */
|
93
src/ed2k_util.c
Normal file
93
src/ed2k_util.c
Normal file
@ -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;
|
||||||
|
}
|
33
src/ed2k_util.h
Normal file
33
src/ed2k_util.h
Normal file
@ -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 */
|
12
src/error.c
Normal file
12
src/error.c
Normal file
@ -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];
|
||||||
|
}
|
63
src/error.h
Normal file
63
src/error.h
Normal file
@ -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 */
|
265
src/net.c
Normal file
265
src/net.c
Normal file
@ -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;
|
||||||
|
}
|
24
src/net.h
Normal file
24
src/net.h
Normal file
@ -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 */
|
58
src/uio.c
Normal file
58
src/uio.c
Normal file
@ -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);
|
||||||
|
}
|
9
src/uio.h
Normal file
9
src/uio.h
Normal file
@ -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 */
|
39
src/util.c
Normal file
39
src/util.c
Normal file
@ -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);
|
||||||
|
}
|
35
src/util.h
Normal file
35
src/util.h
Normal file
@ -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 */
|
1
subm/MD4
Submodule
1
subm/MD4
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 6771029ebea612a1a53822af6867b6cf172c31f0
|
1
subm/md5-c
Submodule
1
subm/md5-c
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 1525b0db1fb608afed8948f3a972d55486e8cb31
|
1
subm/tiny-AES-c
Submodule
1
subm/tiny-AES-c
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit f06ac37fc31dfdaca2e0d9bec83f90d5663c319b
|
Loading…
Reference in New Issue
Block a user