Compare commits

..

3 Commits

Author SHA1 Message Date
x3
09c8a57a78
Handle base error codes 2022-01-10 20:40:38 +01:00
x3
605f47f761
Add api_cmd_encrypt and use in api_init_encrypt 2022-01-09 19:35:40 +01:00
x3
6cf62ab039
Graceful C-c termination 1/2 2022-01-09 18:45:19 +01:00
10 changed files with 539 additions and 184 deletions

View File

@ -34,7 +34,6 @@ make
- Make deleting from mylist possible, with - Make deleting from mylist possible, with
- Name regexes, - Name regexes,
- If file is not found at a scan - 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 - Buffer up mylistadd api cmds when waiting for ratelimit
- Handle C-c gracefully at any time - Handle C-c gracefully at any time
- Write -h page, and maybe a man page too - Write -h page, and maybe a man page too

442
src/api.c
View File

@ -1,5 +1,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <stdarg.h>
#include <assert.h> #include <assert.h>
#include <printf.h> #include <printf.h>
#include <time.h> #include <time.h>
@ -15,6 +16,7 @@
#include "config.h" #include "config.h"
#include "ed2k.h" #include "ed2k.h"
#include "util.h" #include "util.h"
#include "globals.h"
/* Needed, bcuz of custom %B format */ /* Needed, bcuz of custom %B format */
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -30,19 +32,13 @@
#error "No monotonic clock" #error "No monotonic clock"
#endif #endif
#define MS_TO_TIMESPEC(ts, ms) { \ static enum error api_cmd_base(char buffer[API_BUFSIZE],
ts->tv_sec = ms / 1000; \ struct api_result *res, const char *fmt, ...)
ts->tv_nsec = (ms % 1000) * 1000000; \ __attribute__((format (printf, 3, 4)));
}
#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_logout(struct api_result *res);
static enum error api_cmd_auth(const char *uname, const char *pass, static enum error api_cmd_auth(const char *uname, const char *pass,
struct api_result *res); struct api_result *res);
static enum error api_cmd_encrypt(const char *uname, struct api_result *res);
static bool api_authed = false; static bool api_authed = false;
static char api_session[API_SMAXSIZE] = {0}; /* No escaping is needed */ static char api_session[API_SMAXSIZE] = {0}; /* No escaping is needed */
@ -51,12 +47,14 @@ static bool api_encryption = false;
static pthread_t api_ka_thread = 0; static pthread_t api_ka_thread = 0;
static pthread_mutex_t api_work_mx; 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 struct timespec api_last_packet = {0}; /* Last packet time */
static int32_t api_packet_count = 0; /* Only increment */ static int32_t api_packet_count = 0; /* Only increment */
//static int32_t api_fast_packet_count = 0; /* Increment or decrement */ //static int32_t api_fast_packet_count = 0; /* Increment or decrement */
/* For some commands, we need a global retry counter */
static int32_t api_g_retry_count = 0;
static int api_escaped_string(FILE *io, const struct printf_info *info, static int api_escaped_string(FILE *io, const struct printf_info *info,
const void *const *args) const void *const *args)
{ {
@ -93,41 +91,44 @@ static int api_escaped_sring_info(const struct printf_info *info, size_t n,
static enum error api_init_encrypt(const char *api_key, const char *uname) static enum error api_init_encrypt(const char *api_key, const char *uname)
{ {
char buffer[API_BUFSIZE];
MD5Context md5_ctx; MD5Context md5_ctx;
char *salt_start = buffer + 4 /* 209 [salt here] ... */, *salt_end; struct api_result res;
ssize_t r_len, salt_len; enum error err;
size_t salt_len;
if (net_send(buffer, snprintf(buffer, sizeof(buffer), if (api_cmd_encrypt(uname, &res) != NOERR)
"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; return ERR_API_ENCRYPTFAIL;
}
salt_end = strchr(salt_start, ' '); if (res.code != 209) {
if (!salt_end) { err = ERR_API_ENCRYPTFAIL;
uio_error("Cannot find space after salt in response"); switch (res.code) {
return ERR_API_ENCRYPTFAIL; case 309:
uio_error("You'r API key is not defined. Define it here: "
"http://anidb.net/perl-bin/animedb.pl?show=profile");
break;
case 509:
uio_error("No such encryption type. Maybe client is outdated?");
break;
case 394:
uio_error("No user with name: '%s' found by AniDB", uname);
break;
default:
uio_error("Unknown encrypt failure: %ld", res.code);
}
return err;
} }
salt_len = salt_end - salt_start; salt_len = strlen(res.encrypt.salt);
md5Init(&md5_ctx); md5Init(&md5_ctx);
md5Update(&md5_ctx, (uint8_t*)api_key, strlen(api_key)); md5Update(&md5_ctx, (uint8_t*)api_key, strlen(api_key));
md5Update(&md5_ctx, (uint8_t*)salt_start, salt_len); md5Update(&md5_ctx, (uint8_t*)res.encrypt.salt, salt_len);
md5Finalize(&md5_ctx); md5Finalize(&md5_ctx);
memcpy(e_key, md5_ctx.digest, sizeof(e_key)); memcpy(e_key, md5_ctx.digest, sizeof(e_key));
#if 1 #if 1
char *buffpos = buffer; char bf[sizeof(e_key) * 2 + 1];
for (int i = 0; i < 16; i++) util_byte2hex(e_key, sizeof(e_key), false, bf);
buffpos += sprintf(buffpos, "%02x", e_key[i]); uio_debug("Encryption key is: '%s'", bf);
uio_debug("Encryption key is: '%s'", buffer);
#endif #endif
api_encryption = true; api_encryption = true;
@ -181,16 +182,29 @@ static size_t api_decrypt(char *buffer, size_t data_len)
return ret_len; return ret_len;
} }
static enum error api_auth(const char* uname, const char *passw) static enum error api_auth()
{ {
struct api_result res; struct api_result res;
enum error err = NOERR; enum error err = NOERR;
char **uname, **passw;
if (config_get("username", (void**)&uname) != NOERR) {
uio_error("Username is not specified, but it is required!");
return ERR_OPT_REQUIRED;
}
if (config_get("password", (void**)&passw) != NOERR) {
uio_error("Password is not specified, but it is required!");
return ERR_OPT_REQUIRED;
}
/*
* We could try passing in a session key, if we are executing
* a login in response to a nologin or inv session error code?
*/
if (!api_encryption) if (!api_encryption)
uio_warning("Logging in without encryption!"); uio_warning("Logging in without encryption!");
if (api_cmd_auth(uname, passw, &res) != NOERR) { if (api_cmd_auth(*uname, *passw, &res) != NOERR)
return ERR_API_AUTH_FAIL; return ERR_API_AUTH_FAIL;
}
switch (res.code) { switch (res.code) {
case 201: case 201:
@ -213,12 +227,6 @@ static enum error api_auth(const char* uname, const char *passw)
uio_error("Client is banned :( Reason: %s", res.auth.banned_reason); uio_error("Client is banned :( Reason: %s", res.auth.banned_reason);
free(res.auth.banned_reason); free(res.auth.banned_reason);
break; break;
case 505:
uio_error("Illegal input or access denied");
break;
case 601:
uio_error("AniDB out of service");
break;
default: default:
uio_error("Unknown error: %hu", res.code); uio_error("Unknown error: %hu", res.code);
break; break;
@ -296,13 +304,11 @@ void *api_keepalive_main(void *arg)
* Could be replaced with a pthread_cleanup_push ? */ * Could be replaced with a pthread_cleanup_push ? */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&api_work_mx); pthread_mutex_lock(&api_work_mx);
api_ka_now = true;
uio_debug("G'moooooning! Is it time to keep our special connection alive?"); uio_debug("G'moooooning! Is it time to keep our special connection alive?");
api_keepalive(&ka_time); api_keepalive(&ka_time);
uio_debug("Next wakey-wakey in %ld seconds", ka_time.tv_sec); uio_debug("Next wakey-wakey in %ld seconds", ka_time.tv_sec);
api_ka_now = false;
pthread_mutex_unlock(&api_work_mx); pthread_mutex_unlock(&api_work_mx);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
} }
@ -328,7 +334,7 @@ enum error api_clock_init()
enum error api_init(bool auth) enum error api_init(bool auth)
{ {
enum error err = NOERR; enum error err = NOERR;
const char **api_key, **uname, **passwd; const char **api_key, **uname;
err = api_clock_init(); err = api_clock_init();
if (err != NOERR) if (err != NOERR)
@ -347,7 +353,8 @@ enum error api_init(bool auth)
} }
err = api_init_encrypt(*api_key, *uname); err = api_init_encrypt(*api_key, *uname);
if (err != NOERR) { if (err != NOERR) {
uio_error("Cannot init api encryption"); if (err != ERR_SHOULD_EXIT)
uio_error("Cannot init api encryption");
goto fail; goto fail;
} }
} }
@ -361,26 +368,24 @@ enum error api_init(bool auth)
} }
if (auth) { if (auth) {
if (config_get("username", (void**)&uname) != NOERR) { pthread_mutexattr_t attr;
uio_error("Username is not specified, but it is required!"); int mxres;
err = ERR_OPT_REQUIRED;
goto fail; err = api_auth();
}
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) if (err != NOERR)
goto fail; goto fail;
/* Only do keep alive if we have a session */ /* Only do keep alive if we have a session */
if (pthread_mutex_init(&api_work_mx, NULL) != 0) { pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
mxres = pthread_mutex_init(&api_work_mx, NULL);
pthread_mutexattr_destroy(&attr);
if (mxres != 0) {
uio_error("Cannot create mutex"); uio_error("Cannot create mutex");
err = ERR_THRD; err = ERR_THRD;
goto fail; goto fail;
} }
if (pthread_create(&api_ka_thread, NULL, api_keepalive_main, NULL) != 0) { if (pthread_create(&api_ka_thread, NULL, api_keepalive_main, NULL) != 0) {
uio_error("Cannot create api keepalive thread"); uio_error("Cannot create api keepalive thread");
err = ERR_THRD; err = ERR_THRD;
@ -409,10 +414,12 @@ void api_free()
uio_error("Cannot cancel api keepalive thread"); uio_error("Cannot cancel api keepalive thread");
} else { } else {
int je = pthread_join(api_ka_thread, NULL); int je = pthread_join(api_ka_thread, NULL);
if (je != 0) { if (je != 0)
uio_error("Cannot join api keepalive thread: %s", uio_error("Cannot join api keepalive thread: %s",
strerror(je)); strerror(je));
} else
uio_debug("Keepalive thread ended");
if (pthread_mutex_destroy(&api_work_mx) != 0) if (pthread_mutex_destroy(&api_work_mx) != 0)
uio_error("Cannot destroy api work mutex"); uio_error("Cannot destroy api work mutex");
} }
@ -473,21 +480,33 @@ static void api_ratelimit()
} }
} }
/*
* Returns the written byte count
* Or -1 on error, and -2 if errno was EINTR
*/
static ssize_t api_send(char *buffer, size_t data_len, size_t buf_size) static ssize_t api_send(char *buffer, size_t data_len, size_t buf_size)
{ {
ssize_t read_len; ssize_t read_len;
int en;
api_ratelimit(); api_ratelimit();
uio_debug("{Api}: Sending: %.*s", (int)data_len, buffer); uio_debug("{Api}: Sending: %.*s", (int)data_len, buffer);
if (api_encryption) if (api_encryption)
data_len = api_encrypt(buffer, data_len); data_len = api_encrypt(buffer, data_len);
if (net_send(buffer, data_len) == -1) { en = net_send(buffer, data_len);
uio_error("Cannot send data: %s", strerror(errno)); if (en < 0)
return -1; return en;
}
read_len = net_read(buffer, buf_size); read_len = net_read(buffer, buf_size);
if (read_len < 0) {
uio_error("!!! BAD PLACE EINTR !!! report pls");
return read_len; /* This could lead so some problems if we also want to
log out. If we hit this, the msg got sent, but we
couldn't read the response. That means, in the
logout call, this msg's data will be read
Let's see if this ever comes up */
}
api_ratelimit_sent(); api_ratelimit_sent();
if (api_encryption) if (api_encryption)
@ -558,26 +577,170 @@ static char *api_get_field_mod(char *buffer, int32_t field_num)
} }
#endif #endif
/* Basically convert remote codes into local error codes */
static enum error api_cmd_base_errorc(long code, char buffer[API_BUFSIZE])
{
switch (code) {
case APICODE_ILLEGAL_INPUT_OR_ACCESS_DENIED:
uio_error("Got unretryable error code: %d", code);
return ERR_API_INVCOMM;
case APICODE_BANNED:
{
char *ls;
size_t ll;
api_get_line(buffer, 2, &ls, &ll);
uio_error("Banned: %.*s", (int)ll, ls);
return ERR_API_BANNED;
}
case APICODE_UNKNOWN_COMMAND:
uio_error("The sent command is unknown");
return ERR_API_CMD_UNK;
case APICODE_INTERNAL_SERVER_ERROR:
uio_error("Internal server error!");
return ERR_API_INT_SRV;
case APICODE_ANIDB_OUT_OF_SERVICE:
uio_error("AniDB is currently out of service");
return ERR_API_OOS;
case APICODE_SERVER_BUSY:
uio_warning("Server is busy rn, trying again later");
return ERR_API_SRV_BUSY;
case APICODE_TIMEOUT:
uio_debug("Timed out, retrying");
return ERR_API_TIMEOUT;
case APICODE_LOGIN_FIRST:
uio_error("This command required AUTH");
return ERR_API_NOLOGIN;
case APICODE_ACCESS_DENIED:
uio_error("Access is denied for this info");
return ERR_API_AXX_DENIED;
case APICODE_INVALID_SESSION:
uio_error("The login session is invalid");
return ERR_API_INV_SESSION;
default:
/* Not an error, or at least not a base error */
return NOERR;
}
}
/*
* Base for all api_cmd's. This will also execute the default
* error code handers, like 505, 555, 604...
* If success, res.code will be filled out
*/
static enum error api_cmd_base(char buffer[API_BUFSIZE], struct api_result *res,
const char *fmt, ...)
{
int send_len;
enum error err = NOERR;
va_list ap;
int retry_count = 0;
va_start(ap, fmt);
send_len = vsnprintf(buffer, API_BUFSIZE, fmt, ap);
va_end(ap);
pthread_mutex_lock(&api_work_mx);
api_g_retry_count = 0;
while (retry_count < API_MAX_TRYAGAIN &&
api_g_retry_count < API_MAX_TRYAGAIN) {
long code;
ssize_t res_len = api_send(buffer, send_len, API_BUFSIZE);
if (res_len < 0) {
if (res_len == -2 && should_exit)
err = ERR_SHOULD_EXIT;
else
err = ERR_API_COMMFAIL;
goto end;
}
code = api_res_code(buffer);
if (code == -1) {
err = ERR_API_RESP_INVALID;
goto end;
}
res->code = code;
err = api_cmd_base_errorc(code, buffer);
if (err == ERR_API_OOS || err == ERR_API_SRV_BUSY ||
err == ERR_API_TIMEOUT) {
struct timespec ts;
MS_TO_TIMESPEC_L(ts, API_TRYAGAIN_TIME);
retry_count++;
uio_debug("Retry after %ld ms (%d/%d)", API_TRYAGAIN_TIME,
retry_count, API_MAX_TRYAGAIN);
if (nanosleep(&ts, NULL) == -1) {
if (errno == EINTR && should_exit) {
err = ERR_SHOULD_EXIT;
goto end;
}
}
continue;
}
if (err == ERR_API_NOLOGIN || err == ERR_API_INV_SESSION) {
api_g_retry_count++;
if (api_g_retry_count < API_MAX_TRYAGAIN) {
uio_debug("Let's try loggin in agane");
api_authed = false; /* We got logged out probably */
err = api_auth(); /* -> will call this function */
if (api_g_retry_count < API_MAX_TRYAGAIN)
continue;
}
break;
}
break;
};
if (retry_count >= API_MAX_TRYAGAIN ||
api_g_retry_count >= API_MAX_TRYAGAIN) {
uio_debug("Max retry count reached");
goto end;
}
end:
pthread_mutex_unlock(&api_work_mx);
return err;
}
static enum error api_cmd_encrypt(const char *uname, struct api_result *res)
{
char buffer[API_BUFSIZE];
enum error err;
/* Usernames can't contain '&' */
err = api_cmd_base(buffer, res, "ENCRYPT user=%s&type=1", uname);
if (err != NOERR)
return err;
if (res->code == APICODE_ENCRYPTION_ENABLED) {
char *fs;
size_t fl;
bool gfl = api_get_field(buffer, 2, &fs, &fl);
assert(gfl);
(void)gfl;
assert(sizeof(res->encrypt.salt) > fl);
memcpy(res->encrypt.salt, fs, fl);
res->encrypt.salt[fl] = '\0';
}
return err;
}
enum error api_cmd_version(struct api_result *res) enum error api_cmd_version(struct api_result *res)
{ {
char buffer[API_BUFSIZE] = "VERSION"; char buffer[API_BUFSIZE];
size_t res_len = api_send(buffer, strlen(buffer), sizeof(buffer)); enum error err;
long code;
enum error err = NOERR;
pthread_mutex_lock(&api_work_mx);
if (res_len == -1) { err = api_cmd_base(buffer, res, "VERSION");
err = ERR_API_COMMFAIL; if (err != NOERR)
goto end; return err;
}
code = api_res_code(buffer); if (res->code == APICODE_VERSION) {
if (code == -1) {
err = ERR_API_RESP_INVALID;
goto end;
}
if (code == 998) {
char *ver_start; char *ver_start;
size_t ver_len; size_t ver_len;
bool glr = api_get_line(buffer, 2, &ver_start, &ver_len); bool glr = api_get_line(buffer, 2, &ver_start, &ver_len);
@ -588,37 +751,24 @@ enum error api_cmd_version(struct api_result *res)
memcpy(res->version.version_str, ver_start, ver_len); memcpy(res->version.version_str, ver_start, ver_len);
res->version.version_str[ver_len] = '\0'; res->version.version_str[ver_len] = '\0';
} }
res->code = (uint16_t)code;
end:
pthread_mutex_unlock(&api_work_mx);
return err; return err;
} }
static enum error api_cmd_auth(const char *uname, const char *pass, static enum error api_cmd_auth(const char *uname, const char *pass,
struct api_result *res) struct api_result *res)
{ {
pthread_mutex_lock(&api_work_mx);
char buffer[API_BUFSIZE]; char buffer[API_BUFSIZE];
long code; enum error err;
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer),
"AUTH user=%s&pass=%B&protover=" API_VERSION "&client=caniadd&"
"clientver=" PROG_VERSION "&enc=UTF-8", uname, pass),
sizeof(buffer));
enum error err = NOERR;
if (res_len == -1) { err = api_cmd_base(buffer, res, "AUTH user=%s&pass=%B&protover="
err = ERR_API_COMMFAIL; API_VERSION "&client=caniadd&clientver=" PROG_VERSION
goto end; "&enc=UTF-8", uname, pass);
} if (err != NOERR)
return err;
code = api_res_code(buffer); if (res->code == APICODE_LOGIN_ACCEPTED ||
if (code == -1) { res->code == APICODE_LOGIN_ACCEPTED_NEW_VERSION) {
err = ERR_API_RESP_INVALID;
goto end;
}
if (code == 200 || code == 201) {
char *sess; char *sess;
size_t sess_len; size_t sess_len;
bool gfr = api_get_field(buffer, 2, &sess, &sess_len); bool gfr = api_get_field(buffer, 2, &sess, &sess_len);
@ -628,7 +778,7 @@ static enum error api_cmd_auth(const char *uname, const char *pass,
assert(sess_len < sizeof(res->auth.session_key)); assert(sess_len < sizeof(res->auth.session_key));
memcpy(res->auth.session_key, sess, sess_len); memcpy(res->auth.session_key, sess, sess_len);
res->auth.session_key[sess_len] = '\0'; res->auth.session_key[sess_len] = '\0';
} else if (code == 504) { } else if (res->code == APICODE_CLIENT_BANNED) {
char *reason; char *reason;
size_t reason_len; size_t reason_len;
bool gfr = api_get_field(buffer, 5, &reason, &reason_len); bool gfr = api_get_field(buffer, 5, &reason, &reason_len);
@ -637,64 +787,32 @@ static enum error api_cmd_auth(const char *uname, const char *pass,
(void)gfr; (void)gfr;
res->auth.banned_reason = strndup(reason, reason_len); res->auth.banned_reason = strndup(reason, reason_len);
} }
res->code = (uint16_t)code;
end:
pthread_mutex_unlock(&api_work_mx);
return err; return err;
} }
static enum error api_cmd_logout(struct api_result *res) static enum error api_cmd_logout(struct api_result *res)
{ {
pthread_mutex_lock(&api_work_mx);
char buffer[API_BUFSIZE]; char buffer[API_BUFSIZE];
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), enum error err;
"LOGOUT s=%s", api_session), sizeof(buffer));
long code;
enum error err = NOERR;
if (res_len == -1) { err = api_cmd_base(buffer, res, "LOGOUT s=%s", api_session);
err = ERR_API_COMMFAIL; if (err != NOERR)
goto end; return err;
}
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; return err;
} }
enum error api_cmd_uptime(struct api_result *res) 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]; char buffer[API_BUFSIZE];
size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), enum error err;
"UPTIME s=%s", api_session), sizeof(buffer));
long code;
enum error err = NOERR;
if (res_len == -1) { err = api_cmd_base(buffer, res, "UPTIME s=%s", api_session);
err = ERR_API_COMMFAIL; if (err != NOERR)
goto end; return err;
}
code = api_res_code(buffer); if (res->code == APICODE_UPTIME) {
if (code == -1) {
err = ERR_API_RESP_INVALID;
goto end;
}
if (code == 208) {
char *ls; char *ls;
size_t ll; size_t ll;
bool glf = api_get_line(buffer, 2, &ls, &ll); bool glf = api_get_line(buffer, 2, &ls, &ll);
@ -704,11 +822,6 @@ enum error api_cmd_uptime(struct api_result *res)
res->uptime.ms = strtol(ls, NULL, 10); 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; return err;
} }
@ -717,30 +830,17 @@ enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash,
{ {
char buffer[API_BUFSIZE]; char buffer[API_BUFSIZE];
char hash_str[ED2K_HASH_SIZE * 2 + 1]; char hash_str[ED2K_HASH_SIZE * 2 + 1];
size_t res_len;
enum error err = NOERR; enum error err = NOERR;
long code;
pthread_mutex_lock(&api_work_mx);
util_byte2hex(hash, ED2K_HASH_SIZE, false, hash_str); util_byte2hex(hash, ED2K_HASH_SIZE, false, hash_str);
/* Wiki says file size is 4 bytes, but no way that's true lol */ /* Wiki says file size is 4 bytes, but no way that's true lol */
res_len = api_send(buffer, snprintf(buffer, sizeof(buffer), err = api_cmd_base(buffer, res,
"MYLISTADD s=%s&size=%ld&ed2k=%s&state=%hu&viewed=%d", "MYLISTADD s=%s&size=%ld&ed2k=%s&state=%hu&viewed=%d",
api_session, size, hash_str, ml_state, watched), api_session, size, hash_str, ml_state, watched);
sizeof(buffer)); if (err != NOERR)
return err;
if (res_len == -1) { if (res->code == APICODE_MYLIST_ENTRY_ADDED) {
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]; char *ls, id_str[12];
size_t ll; size_t ll;
bool glr = api_get_line(buffer, 2, &ls, &ll); bool glr = api_get_line(buffer, 2, &ls, &ll);
@ -754,7 +854,7 @@ enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash,
/* Wiki says these id's are 4 bytes, which is untrue... /* Wiki says these id's are 4 bytes, which is untrue...
* that page may be a little out of date (or they just * that page may be a little out of date (or they just
* expect us to use common sense lmao */ * expect us to use common sense lmao */
} else if (code == 310) { } else if (res->code == APICODE_FILE_ALREADY_IN_MYLIST) {
/* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}| /* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}|
* {int4 date}|{int2 state}|{int4 viewdate}|{str storage}| * {int4 date}|{int2 state}|{int4 viewdate}|{str storage}|
* {str source}|{str other}|{int2 filestate} */ * {str source}|{str other}|{int2 filestate} */
@ -802,10 +902,6 @@ enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash,
} }
} }
res->code = (uint16_t)code;
end:
pthread_mutex_unlock(&api_work_mx);
return err; return err;
} }

165
src/api.h
View File

@ -11,9 +11,11 @@
#define API_BUFSIZE 1400 #define API_BUFSIZE 1400
/* Session key maximum size, including '\0' */ /* Session key maximum size, including '\0' */
#define API_SMAXSIZE 16 #define API_SMAXSIZE 16
/* Encryption salt maximum size, including '\0' */
#define API_SALTMAXSIZE 16
/* The session timeout in miliseconds */ /* The session timeout in miliseconds */
#define API_TIMEOUT 30 * 60 * 1000 #define API_TIMEOUT 30 * 60 * 1000
/* How many miliseconds to wait between sends */ /* How many miliseconds to wait between sends */
#define API_SENDWAIT 2 * 1000 #define API_SENDWAIT 2 * 1000
/* The number of packets that are exccempt from the ratelimit */ /* The number of packets that are exccempt from the ratelimit */
@ -23,6 +25,163 @@
/* After this many packets has been sent, use the longterm ratelimit */ /* After this many packets has been sent, use the longterm ratelimit */
#define API_LONGTERM_PACKETS 100 #define API_LONGTERM_PACKETS 100
/* How much to wait between try agains? in ms */
#define API_TRYAGAIN_TIME 60 * 1000
#define API_MAX_TRYAGAIN 5
enum api_code {
APICODE_LOGIN_ACCEPTED = 200,
APICODE_LOGIN_ACCEPTED_NEW_VERSION = 201,
APICODE_LOGGED_OUT = 203,
APICODE_RESOURCE = 205,
APICODE_STATS = 206,
APICODE_TOP = 207,
APICODE_UPTIME = 208,
APICODE_ENCRYPTION_ENABLED = 209,
APICODE_MYLIST_ENTRY_ADDED = 210,
APICODE_MYLIST_ENTRY_DELETED = 211,
APICODE_ADDED_FILE = 214,
APICODE_ADDED_STREAM = 215,
APICODE_EXPORT_QUEUED = 217,
APICODE_EXPORT_CANCELLED = 218,
APICODE_ENCODING_CHANGED = 219,
APICODE_FILE = 220,
APICODE_MYLIST = 221,
APICODE_MYLIST_STATS = 222,
APICODE_WISHLIST = 223,
APICODE_NOTIFICATION = 224,
APICODE_GROUP_STATUS = 225,
APICODE_WISHLIST_ENTRY_ADDED = 226,
APICODE_WISHLIST_ENTRY_DELETED = 227,
APICODE_WISHLIST_ENTRY_UPDATED = 228,
APICODE_MULTIPLE_WISHLIST = 229,
APICODE_ANIME = 230,
APICODE_ANIME_BEST_MATCH = 231,
APICODE_RANDOM_ANIME = 232,
APICODE_ANIME_DESCRIPTION = 233,
APICODE_REVIEW = 234,
APICODE_CHARACTER = 235,
APICODE_SONG = 236,
APICODE_ANIMETAG = 237,
APICODE_CHARACTERTAG = 238,
APICODE_EPISODE = 240,
APICODE_UPDATED = 243,
APICODE_TITLE = 244,
APICODE_CREATOR = 245,
APICODE_NOTIFICATION_ENTRY_ADDED = 246,
APICODE_NOTIFICATION_ENTRY_DELETED = 247,
APICODE_NOTIFICATION_ENTRY_UPDATE = 248,
APICODE_MULTIPLE_NOTIFICATION = 249,
APICODE_GROUP = 250,
APICODE_CATEGORY = 251,
APICODE_BUDDY_LIST = 253,
APICODE_BUDDY_STATE = 254,
APICODE_BUDDY_ADDED = 255,
APICODE_BUDDY_DELETED = 256,
APICODE_BUDDY_ACCEPTED = 257,
APICODE_BUDDY_DENIED = 258,
APICODE_VOTED = 260,
APICODE_VOTE_FOUND = 261,
APICODE_VOTE_UPDATED = 262,
APICODE_VOTE_REVOKED = 263,
APICODE_HOT_ANIME = 265,
APICODE_RANDOM_RECOMMENDATION = 266,
APICODE_RANDOM_SIMILAR = 267,
APICODE_NOTIFICATION_ENABLED = 270,
APICODE_NOTIFYACK_SUCCESSFUL_MESSAGE = 281,
APICODE_NOTIFYACK_SUCCESSFUL_NOTIFICATION = 282,
APICODE_NOTIFICATION_STATE = 290,
APICODE_NOTIFYLIST = 291,
APICODE_NOTIFYGET_MESSAGE = 292,
APICODE_NOTIFYGET_NOTIFY = 293,
APICODE_SENDMESSAGE_SUCCESSFUL = 294,
APICODE_USER_ID = 295,
APICODE_CALENDAR = 297,
APICODE_PONG = 300,
APICODE_AUTHPONG = 301,
APICODE_NO_SUCH_RESOURCE = 305,
APICODE_API_PASSWORD_NOT_DEFINED = 309,
APICODE_FILE_ALREADY_IN_MYLIST = 310,
APICODE_MYLIST_ENTRY_EDITED = 311,
APICODE_MULTIPLE_MYLIST_ENTRIES = 312,
APICODE_WATCHED = 313,
APICODE_SIZE_HASH_EXISTS = 314,
APICODE_INVALID_DATA = 315,
APICODE_STREAMNOID_USED = 316,
APICODE_EXPORT_NO_SUCH_TEMPLATE = 317,
APICODE_EXPORT_ALREADY_IN_QUEUE = 318,
APICODE_EXPORT_NO_EXPORT_QUEUED_OR_IS_PROCESSING = 319,
APICODE_NO_SUCH_FILE = 320,
APICODE_NO_SUCH_ENTRY = 321,
APICODE_MULTIPLE_FILES_FOUND = 322,
APICODE_NO_SUCH_WISHLIST = 323,
APICODE_NO_SUCH_NOTIFICATION = 324,
APICODE_NO_GROUPS_FOUND = 325,
APICODE_NO_SUCH_ANIME = 330,
APICODE_NO_SUCH_DESCRIPTION = 333,
APICODE_NO_SUCH_REVIEW = 334,
APICODE_NO_SUCH_CHARACTER = 335,
APICODE_NO_SUCH_SONG = 336,
APICODE_NO_SUCH_ANIMETAG = 337,
APICODE_NO_SUCH_CHARACTERTAG = 338,
APICODE_NO_SUCH_EPISODE = 340,
APICODE_NO_SUCH_UPDATES = 343,
APICODE_NO_SUCH_TITLES = 344,
APICODE_NO_SUCH_CREATOR = 345,
APICODE_NO_SUCH_GROUP = 350,
APICODE_NO_SUCH_CATEGORY = 351,
APICODE_BUDDY_ALREADY_ADDED = 355,
APICODE_NO_SUCH_BUDDY = 356,
APICODE_BUDDY_ALREADY_ACCEPTED = 357,
APICODE_BUDDY_ALREADY_DENIED = 358,
APICODE_NO_SUCH_VOTE = 360,
APICODE_INVALID_VOTE_TYPE = 361,
APICODE_INVALID_VOTE_VALUE = 362,
APICODE_PERMVOTE_NOT_ALLOWED = 363,
APICODE_ALREADY_PERMVOTED = 364,
APICODE_HOT_ANIME_EMPTY = 365,
APICODE_RANDOM_RECOMMENDATION_EMPTY = 366,
APICODE_RANDOM_SIMILAR_EMPTY = 367,
APICODE_NOTIFICATION_DISABLED = 370,
APICODE_NO_SUCH_ENTRY_MESSAGE = 381,
APICODE_NO_SUCH_ENTRY_NOTIFICATION = 382,
APICODE_NO_SUCH_MESSAGE = 392,
APICODE_NO_SUCH_NOTIFY = 393,
APICODE_NO_SUCH_USER = 394,
APICODE_CALENDAR_EMPTY = 397,
APICODE_NO_CHANGES = 399,
APICODE_NOT_LOGGED_IN = 403,
APICODE_NO_SUCH_MYLIST_FILE = 410,
APICODE_NO_SUCH_MYLIST_ENTRY = 411,
APICODE_MYLIST_UNAVAILABLE = 412,
APICODE_LOGIN_FAILED = 500,
APICODE_LOGIN_FIRST = 501,
APICODE_ACCESS_DENIED = 502,
APICODE_CLIENT_VERSION_OUTDATED = 503,
APICODE_CLIENT_BANNED = 504,
APICODE_ILLEGAL_INPUT_OR_ACCESS_DENIED = 505,
APICODE_INVALID_SESSION = 506,
APICODE_NO_SUCH_ENCRYPTION_TYPE = 509,
APICODE_ENCODING_NOT_SUPPORTED = 519,
APICODE_BANNED = 555,
APICODE_UNKNOWN_COMMAND = 598,
APICODE_INTERNAL_SERVER_ERROR = 600,
APICODE_ANIDB_OUT_OF_SERVICE = 601,
APICODE_SERVER_BUSY = 602,
APICODE_NO_DATA = 603,
APICODE_TIMEOUT = 604,
APICODE_API_VIOLATION = 666,
APICODE_PUSHACK_CONFIRMED = 701,
APICODE_NO_SUCH_PACKET_PENDING = 702,
APICODE_VERSION = 998,
};
enum mylist_state { enum mylist_state {
MYLIST_STATE_UNKNOWN = 0, MYLIST_STATE_UNKNOWN = 0,
MYLIST_STATE_INTERNAL, MYLIST_STATE_INTERNAL,
@ -54,6 +213,9 @@ struct api_auth_result {
char *banned_reason; char *banned_reason;
}; };
}; };
struct api_encrypt_result {
char salt[API_SALTMAXSIZE];
};
struct api_uptime_result { struct api_uptime_result {
int32_t ms; int32_t ms;
}; };
@ -78,6 +240,7 @@ struct api_result {
struct api_auth_result auth; struct api_auth_result auth;
struct api_uptime_result uptime; struct api_uptime_result uptime;
e(mylistadd); e(mylistadd);
e(encrypt);
}; };
}; };
#undef e #undef e

View File

@ -1,17 +1,39 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <signal.h> #include <signal.h>
#include <errno.h>
#include <string.h>
#include "config.h" #include "config.h"
#include "error.h" #include "error.h"
#include "uio.h" #include "uio.h"
#include "cmd.h" #include "cmd.h"
#include "globals.h"
bool should_exit = false;
static void signal_handler(int signum, siginfo_t *info, void *ctx)
{
should_exit = true;
printf("\033[0GGot C-c. Press again to force exit\n");
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int exit_code = EXIT_SUCCESS; int exit_code = EXIT_SUCCESS;
enum error err = config_parse(argc, argv); enum error err;
struct sigaction sact = {
.sa_flags = SA_SIGINFO | SA_RESETHAND,
//.sa_flags = SA_SIGINFO,
.sa_sigaction = signal_handler,
};
if (sigaction(SIGINT, &sact, NULL) != 0) {
uio_error("Cannot set up signal handler: %s", strerror(errno));
return EXIT_FAILURE;
}
err = config_parse(argc, argv);
if (err == ERR_OPT_EXIT) if (err == ERR_OPT_EXIT)
return EXIT_SUCCESS; return EXIT_SUCCESS;
else if (err != NOERR) else if (err != NOERR)
@ -20,7 +42,9 @@ int main(int argc, char **argv)
//config_dump(); //config_dump();
err = cmd_main(); err = cmd_main();
if (err != NOERR) if (err == ERR_SHOULD_EXIT)
uio_debug("Exiting as requested orz");
else if (err != NOERR)
exit_code = EXIT_FAILURE; exit_code = EXIT_FAILURE;
config_free(); config_free();

View File

@ -3,10 +3,12 @@
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <ftw.h> #include <ftw.h>
#include <assert.h>
#include "ed2k.h" #include "ed2k.h"
#include "ed2k_util.h" #include "ed2k_util.h"
#include "uio.h" #include "uio.h"
#include "globals.h"
static struct ed2k_util_opts l_opts; static struct ed2k_util_opts l_opts;
@ -15,11 +17,13 @@ static enum error ed2k_util_hash(const char *file_path, blksize_t blksize,
{ {
unsigned char buf[blksize], hash[ED2K_HASH_SIZE]; unsigned char buf[blksize], hash[ED2K_HASH_SIZE];
struct ed2k_ctx ed2k; struct ed2k_ctx ed2k;
enum error err;
FILE *f; FILE *f;
size_t read_len; size_t read_len;
int en;
if (l_opts.pre_hash_fn) { if (l_opts.pre_hash_fn) {
enum error err = l_opts.pre_hash_fn(file_path, st, l_opts.data); err = l_opts.pre_hash_fn(file_path, st, l_opts.data);
if (err == ED2KUTIL_DONTHASH) if (err == ED2KUTIL_DONTHASH)
return NOERR; return NOERR;
else if (err != NOERR) else if (err != NOERR)
@ -28,24 +32,52 @@ static enum error ed2k_util_hash(const char *file_path, blksize_t blksize,
f = fopen(file_path, "rb"); f = fopen(file_path, "rb");
if (!f) { if (!f) {
uio_error("Failed to open file: %s (%s)", file_path, strerror(errno)); en = errno;
return ERR_ED2KUTIL_FS;
uio_error("Failed to open file: %s (%s)", file_path, strerror(en));
if (en == EINTR && should_exit)
return ERR_SHOULD_EXIT;
else
return ERR_ED2KUTIL_FS;
} }
ed2k_init(&ed2k); ed2k_init(&ed2k);
read_len = fread(buf, 1, sizeof(buf), f); read_len = fread(buf, 1, sizeof(buf), f);
while (read_len > 0) { /* From my test, fread wont return anything special on signal interrupt */
while (read_len > 0 && !should_exit) {
ed2k_update(&ed2k, buf, read_len); ed2k_update(&ed2k, buf, read_len);
read_len = fread(buf, 1, sizeof(buf), f); read_len = fread(buf, 1, sizeof(buf), f);
} }
// TODO check if eof or error if (should_exit) {
err = ERR_SHOULD_EXIT;
goto fail;
}
if (ferror(f)) { /* Loop stopped bcuz of error, not EOF */
uio_error("Failure while reading file");
err = ERR_ED2KUTIL_FS;
goto fail;
}
assert(feof(f));
ed2k_final(&ed2k, hash); ed2k_final(&ed2k, hash);
fclose(f); if (fclose(f) != 0) {
en = errno;
uio_debug("Fclose failed: %s", strerror(en));
if (en == EINTR && should_exit)
return ERR_SHOULD_EXIT;
else
return ERR_ED2KUTIL_FS;
}
if (l_opts.post_hash_fn) if (l_opts.post_hash_fn)
return l_opts.post_hash_fn(file_path, hash, st, l_opts.data); return l_opts.post_hash_fn(file_path, hash, st, l_opts.data);
return NOERR; return NOERR;
fail:
if (f) /* We can't get a 2nd interrupt now */
fclose(f);
return err;
} }
static int ed2k_util_walk(const char *fpath, const struct stat *sb, static int ed2k_util_walk(const char *fpath, const struct stat *sb,

View File

@ -35,6 +35,16 @@
E(ERR_API_LOGOUT) /* Logout failed */ \ E(ERR_API_LOGOUT) /* Logout failed */ \
E(ERR_API_PRINTFFUNC) /* New printf function registration failed */ \ E(ERR_API_PRINTFFUNC) /* New printf function registration failed */ \
E(ERR_API_CLOCK) /* Some error with clocks */ \ E(ERR_API_CLOCK) /* Some error with clocks */ \
E(ERR_API_INVCOMM) /* Invalid command or command arguments */ \
E(ERR_API_BANNED) /* Got banned */ \
E(ERR_API_CMD_UNK) /* Unknown command */ \
E(ERR_API_INT_SRV) /* Internal server error */ \
E(ERR_API_OOS) /* AniDB is out of service rn */ \
E(ERR_API_SRV_BUSY) /* Server is too busy, try again later */ \
E(ERR_API_TIMEOUT) /* Timed out, delay and resubmit */ \
E(ERR_API_NOLOGIN) /* Login is required for this command */ \
E(ERR_API_AXX_DENIED) /* Access is denied */ \
E(ERR_API_INV_SESSION) /* Session is invalid */ \
\ \
E(ERR_CACHE_SQLITE) /* Generic sqlite error code */ \ E(ERR_CACHE_SQLITE) /* Generic sqlite error code */ \
E(ERR_CACHE_EXISTS) /* Entry already exists, as determined by lid */ \ E(ERR_CACHE_EXISTS) /* Entry already exists, as determined by lid */ \
@ -45,6 +55,8 @@
E(ERR_THRD) /* Generic pthread error */ \ E(ERR_THRD) /* Generic pthread error */ \
\ \
E(ERR_LIBEVENT) /* There are some problem with a libevent function */ \ E(ERR_LIBEVENT) /* There are some problem with a libevent function */ \
\
E(ERR_SHOULD_EXIT) /* Probably got a C-c, program should exit now */ \
E(_ERR_COUNT) \ E(_ERR_COUNT) \

7
src/globals.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef _GLOBALS_H
#define _GLOBALS_H
#include <stdbool.h>
extern bool should_exit;
#endif /* _GLOBALS_H */

View File

@ -248,7 +248,11 @@ ssize_t net_send(const void *msg, size_t msg_len)
{ {
ssize_t w_len = send(net_socket, msg, msg_len, 0); ssize_t w_len = send(net_socket, msg, msg_len, 0);
if (w_len == -1) { if (w_len == -1) {
uio_error("{net} Send failed: %s", strerror(errno)); int en = errno;
uio_error("{net} Send failed: %s", strerror(en));
if (en == EINTR)
return -2;
return -1; return -1;
} }
return w_len; return w_len;
@ -258,8 +262,14 @@ ssize_t net_read(void* out_data, size_t read_size)
{ {
ssize_t read = recv(net_socket, out_data, read_size, 0); ssize_t read = recv(net_socket, out_data, read_size, 0);
if (read == -1) { if (read == -1) {
int en = errno;
uio_error("{net} Read failed: %s", strerror(errno)); uio_error("{net} Read failed: %s", strerror(errno));
if (en == EINTR)
return -2;
return -1; return -1;
} }
if (read == read_size)
uio_warning("{net} Data may have been discarded!");
return read; return read;
} }

View File

@ -12,6 +12,8 @@ enum error net_init();
/* /*
* Send and read data to and from the api * Send and read data to and from the api
* Returns the number of bytes sent/read or -1 on error
* If the error is EINTR, it returns -2
*/ */
ssize_t net_send(const void *msg, size_t msg_len); ssize_t net_send(const void *msg, size_t msg_len);
ssize_t net_read(void *out_data, size_t read_size); ssize_t net_read(void *out_data, size_t read_size);

View File

@ -4,6 +4,16 @@
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#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; \
}
/* /*
* Convert bytes to a hex string * Convert bytes to a hex string
* out needs to be at least (bytes_len * 2 + 1) bytes * out needs to be at least (bytes_len * 2 + 1) bytes