A tool for adding anime to your anidb list.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1108 lines
31KB

  1. #include <stdbool.h>
  2. #include <string.h>
  3. #include <stdarg.h>
  4. #include <assert.h>
  5. #include <printf.h>
  6. #include <time.h>
  7. #include <pthread.h>
  8. #include <errno.h>
  9. #include <md5.h>
  10. #include <aes.h>
  11. #include "api.h"
  12. #include "net.h"
  13. #include "uio.h"
  14. #include "config.h"
  15. #include "ed2k.h"
  16. #include "util.h"
  17. #include "globals.h"
  18. /* Needed, bcuz of custom %B format */
  19. #pragma GCC diagnostic push
  20. #pragma GCC diagnostic ignored "-Wformat"
  21. #pragma GCC diagnostic ignored "-Wformat-extra-args"
  22. #ifdef CLOCK_MONOTONIC_COARSE
  23. #define API_CLOCK CLOCK_MONOTONIC_COARSE
  24. #elif defined(CLOCK_MONOTONIC)
  25. #warn "No coarse monotonic clock"
  26. #define API_CLOCK CLOCK_MONOTONIC
  27. #else
  28. #error "No monotonic clock"
  29. #endif
  30. static enum error api_cmd_base(char buffer[API_BUFSIZE],
  31. struct api_result *res, const char *fmt, ...)
  32. __attribute__((format (printf, 3, 4)));
  33. static enum error api_cmd_logout(struct api_result *res);
  34. static enum error api_cmd_auth(const char *uname, const char *pass,
  35. struct api_result *res);
  36. static enum error api_cmd_encrypt(const char *uname, struct api_result *res);
  37. static bool api_authed = false;
  38. static char api_session[API_SMAXSIZE] = {0}; /* No escaping is needed */
  39. static uint8_t e_key[16] = {0};
  40. static bool api_encryption = false;
  41. static pthread_t api_ka_thread = 0;
  42. static pthread_mutex_t api_work_mx;
  43. static struct timespec api_last_packet = {0}; /* Last packet time */
  44. static int32_t api_packet_count = 0; /* Only increment */
  45. //static int32_t api_fast_packet_count = 0; /* Increment or decrement */
  46. /* For some commands, we need a global retry counter */
  47. static int32_t api_g_retry_count = 0;
  48. static int api_escaped_string(FILE *io, const struct printf_info *info,
  49. const void *const *args)
  50. {
  51. /* Ignore newline escapes for now */
  52. char *str = *(char**)args[0];
  53. char *and_pos = strchr(str, '&');
  54. size_t w_chars = 0;
  55. if (and_pos == NULL)
  56. return fprintf(io, "%s", str);
  57. while (and_pos) {
  58. w_chars += fprintf(io, "%.*s", (int)(and_pos - str), str);
  59. w_chars += fprintf(io, "&amp;");
  60. str = and_pos + 1;
  61. and_pos = strchr(str, '&');
  62. }
  63. if (*str)
  64. w_chars += fprintf(io, "%s", str);
  65. return w_chars;
  66. }
  67. static int api_escaped_sring_info(const struct printf_info *info, size_t n,
  68. int *argtypes, int *size)
  69. {
  70. if (n > 0) {
  71. argtypes[0] = PA_STRING;
  72. size[0] = sizeof(const char*);
  73. }
  74. return 1;
  75. }
  76. static enum error api_init_encrypt(const char *api_key, const char *uname)
  77. {
  78. MD5Context md5_ctx;
  79. struct api_result res;
  80. enum error err;
  81. size_t salt_len;
  82. if (api_cmd_encrypt(uname, &res) != NOERR)
  83. return ERR_API_ENCRYPTFAIL;
  84. if (res.code != 209) {
  85. err = ERR_API_ENCRYPTFAIL;
  86. switch (res.code) {
  87. case 309:
  88. uio_error("You'r API key is not defined. Define it here: "
  89. "http://anidb.net/perl-bin/animedb.pl?show=profile");
  90. break;
  91. case 509:
  92. uio_error("No such encryption type. Maybe client is outdated?");
  93. break;
  94. case 394:
  95. uio_error("No user with name: '%s' found by AniDB", uname);
  96. break;
  97. default:
  98. uio_error("Unknown encrypt failure: %ld", res.code);
  99. }
  100. return err;
  101. }
  102. salt_len = strlen(res.encrypt.salt);
  103. md5Init(&md5_ctx);
  104. md5Update(&md5_ctx, (uint8_t*)api_key, strlen(api_key));
  105. md5Update(&md5_ctx, (uint8_t*)res.encrypt.salt, salt_len);
  106. md5Finalize(&md5_ctx);
  107. memcpy(e_key, md5_ctx.digest, sizeof(e_key));
  108. #if 1
  109. char bf[sizeof(e_key) * 2 + 1];
  110. util_byte2hex(e_key, sizeof(e_key), false, bf);
  111. uio_debug("Encryption key is: '%s'", bf);
  112. #endif
  113. api_encryption = true;
  114. return NOERR;
  115. }
  116. static size_t api_encrypt(char *buffer, size_t data_len)
  117. {
  118. struct AES_ctx actx;
  119. size_t rem_data_len = data_len, ret_len = data_len;
  120. char pad_value;
  121. AES_init_ctx(&actx, e_key);
  122. while (rem_data_len >= AES_BLOCKLEN) {
  123. AES_ECB_encrypt(&actx, (uint8_t*)buffer);
  124. buffer += AES_BLOCKLEN;
  125. rem_data_len -= AES_BLOCKLEN;
  126. }
  127. /* Possible BOF here? maybe? certanly. */
  128. pad_value = AES_BLOCKLEN - rem_data_len;
  129. ret_len += pad_value;
  130. memset(buffer + rem_data_len, pad_value, pad_value);
  131. AES_ECB_encrypt(&actx, (uint8_t*)buffer);
  132. assert(ret_len % AES_BLOCKLEN == 0);
  133. return ret_len;
  134. }
  135. static size_t api_decrypt(char *buffer, size_t data_len)
  136. {
  137. assert(data_len % AES_BLOCKLEN == 0);
  138. struct AES_ctx actx;
  139. size_t ret_len = data_len;
  140. char pad_value;
  141. AES_init_ctx(&actx, e_key);
  142. while (data_len) {
  143. AES_ECB_decrypt(&actx, (uint8_t*)buffer);
  144. buffer += AES_BLOCKLEN;
  145. data_len -= AES_BLOCKLEN;
  146. }
  147. pad_value = buffer[data_len - 1];
  148. ret_len -= pad_value;
  149. return ret_len;
  150. }
  151. static enum error api_auth()
  152. {
  153. struct api_result res;
  154. enum error err = NOERR;
  155. char **uname, **passw;
  156. if (config_get("username", (void**)&uname) != NOERR) {
  157. uio_error("Username is not specified, but it is required!");
  158. return ERR_OPT_REQUIRED;
  159. }
  160. if (config_get("password", (void**)&passw) != NOERR) {
  161. uio_error("Password is not specified, but it is required!");
  162. return ERR_OPT_REQUIRED;
  163. }
  164. /*
  165. * We could try passing in a session key, if we are executing
  166. * a login in response to a nologin or inv session error code?
  167. */
  168. if (!api_encryption)
  169. uio_warning("Logging in without encryption!");
  170. if (api_cmd_auth(*uname, *passw, &res) != NOERR)
  171. return ERR_API_AUTH_FAIL;
  172. switch (res.code) {
  173. case 201:
  174. uio_warning("A new client version is available!");
  175. case 200:
  176. memcpy(api_session, res.auth.session_key, sizeof(api_session));
  177. api_authed = true;
  178. uio_debug("Succesfully logged in. Session key: '%s'", api_session);
  179. break;
  180. default:
  181. err = ERR_API_AUTH_FAIL;
  182. switch (res.code) {
  183. case 500:
  184. uio_error("Login failed. Please check your credentials again");
  185. break;
  186. case 503:
  187. uio_error("Client is outdated. You're probably out of luck here.");
  188. break;
  189. case 504:
  190. uio_error("Client is banned :( Reason: %s", res.auth.banned_reason);
  191. free(res.auth.banned_reason);
  192. break;
  193. default:
  194. uio_error("Unknown error: %hu", res.code);
  195. break;
  196. }
  197. }
  198. return err;
  199. }
  200. enum error api_logout()
  201. {
  202. struct api_result res;
  203. enum error err = NOERR;
  204. if (api_cmd_logout(&res) != NOERR) {
  205. return ERR_API_AUTH_FAIL;
  206. }
  207. switch (res.code) {
  208. case 203:
  209. uio_debug("Succesfully logged out");
  210. api_authed = false;
  211. break;
  212. case 403:
  213. uio_error("Cannot log out, because we aren't logged in");
  214. api_authed = false;
  215. break;
  216. default:
  217. err = ERR_API_LOGOUT;
  218. uio_error("Unknown error: %hu", res.code);
  219. break;
  220. }
  221. return err;
  222. }
  223. static void api_keepalive(struct timespec *out_next)
  224. {
  225. struct timespec ts = {0};
  226. uint64_t msdiff;
  227. clock_gettime(API_CLOCK, &ts);
  228. msdiff = util_timespec_diff(&api_last_packet, &ts);
  229. if (msdiff >= API_TIMEOUT) {
  230. struct api_result r;
  231. MS_TO_TIMESPEC(out_next, API_TIMEOUT);
  232. uio_debug("Sending uptime command for keep alive");
  233. // TODO what if another action is already in progress?
  234. api_cmd_uptime(&r);
  235. } else {
  236. uint64_t msnext = API_TIMEOUT - msdiff;
  237. uio_debug("Got keepalive request, but time is not up yet");
  238. MS_TO_TIMESPEC(out_next, msnext);
  239. }
  240. }
  241. void *api_keepalive_main(void *arg)
  242. {
  243. struct timespec ka_time;
  244. MS_TO_TIMESPEC_L(ka_time, API_TIMEOUT);
  245. uio_debug("Hi from keepalie thread");
  246. for (;;) {
  247. if (nanosleep(&ka_time, NULL) != 0) {
  248. int e = errno;
  249. uio_error("Nanosleep failed: %s", strerror(e));
  250. }
  251. /* Needed, because the thread could be canceled while in recv or send
  252. * and in that case, the mutex will remain locked
  253. * Could be replaced with a pthread_cleanup_push ? */
  254. pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
  255. pthread_mutex_lock(&api_work_mx);
  256. uio_debug("G'moooooning! Is it time to keep our special connection alive?");
  257. api_keepalive(&ka_time);
  258. uio_debug("Next wakey-wakey in %ld seconds", ka_time.tv_sec);
  259. pthread_mutex_unlock(&api_work_mx);
  260. pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  261. }
  262. return NULL;
  263. }
  264. enum error api_clock_init()
  265. {
  266. struct timespec ts;
  267. memset(&api_last_packet, 0, sizeof(api_last_packet));
  268. api_packet_count = 0;
  269. if (clock_getres(API_CLOCK, &ts) != 0) {
  270. uio_error("Cannot get clock resolution: %s", strerror(errno));
  271. return ERR_API_CLOCK;
  272. }
  273. uio_debug("Clock resolution: %f ms",
  274. (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000.0));
  275. return NOERR;
  276. }
  277. enum error api_init(bool auth)
  278. {
  279. enum error err = NOERR;
  280. const char **api_key, **uname;
  281. err = api_clock_init();
  282. if (err != NOERR)
  283. return err;
  284. err = net_init();
  285. if (err != NOERR)
  286. return err;
  287. if (config_get("api-key", (void**)&api_key) == NOERR) {
  288. if (config_get("username", (void**)&uname) != NOERR) {
  289. uio_error("Api key is specified, but that also requires "
  290. "the username!");
  291. err = ERR_OPT_REQUIRED;
  292. goto fail;
  293. }
  294. err = api_init_encrypt(*api_key, *uname);
  295. if (err != NOERR) {
  296. if (err != ERR_SHOULD_EXIT)
  297. uio_error("Cannot init api encryption");
  298. goto fail;
  299. }
  300. }
  301. /* Define an escaped string printf type */
  302. if (register_printf_specifier('B', api_escaped_string,
  303. api_escaped_sring_info) != 0) {
  304. uio_error("Failed to register escaped printf string function");
  305. err = ERR_API_PRINTFFUNC;
  306. goto fail;
  307. }
  308. if (auth) {
  309. pthread_mutexattr_t attr;
  310. int mxres;
  311. err = api_auth();
  312. if (err != NOERR)
  313. goto fail;
  314. /* Only do keep alive if we have a session */
  315. pthread_mutexattr_init(&attr);
  316. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  317. mxres = pthread_mutex_init(&api_work_mx, NULL);
  318. pthread_mutexattr_destroy(&attr);
  319. if (mxres != 0) {
  320. uio_error("Cannot create mutex");
  321. err = ERR_THRD;
  322. goto fail;
  323. }
  324. if (pthread_create(&api_ka_thread, NULL, api_keepalive_main, NULL) != 0) {
  325. uio_error("Cannot create api keepalive thread");
  326. err = ERR_THRD;
  327. goto fail;
  328. }
  329. }
  330. #if 0
  331. printf("Testings: %B\n", "oi&ha=hi&wooooowz&");
  332. printf("Testings: %B\n", "oi&ha=hi&wooooowz");
  333. printf("Testings: %B\n", "&oi&ha=hi&wooooowz");
  334. printf("Testings: %B\n", "oooooooooiiiiii");
  335. #endif
  336. return err;
  337. fail:
  338. api_free();
  339. return err;
  340. }
  341. void api_free()
  342. {
  343. if (api_authed) {
  344. if (pthread_cancel(api_ka_thread) != 0) {
  345. uio_error("Cannot cancel api keepalive thread");
  346. } else {
  347. int je = pthread_join(api_ka_thread, NULL);
  348. if (je != 0)
  349. uio_error("Cannot join api keepalive thread: %s",
  350. strerror(je));
  351. else
  352. uio_debug("Keepalive thread ended");
  353. if (pthread_mutex_destroy(&api_work_mx) != 0)
  354. uio_error("Cannot destroy api work mutex");
  355. }
  356. api_logout();
  357. memset(api_session, 0, sizeof(api_session));
  358. api_authed = false; /* duplicate */
  359. }
  360. if (api_encryption) {
  361. api_encryption = false;
  362. memset(e_key, 0, sizeof(e_key));
  363. }
  364. register_printf_specifier('B', NULL, NULL);
  365. net_free();
  366. }
  367. /*
  368. * We just sent a packet, so update the last packet time here
  369. */
  370. static void api_ratelimit_sent()
  371. {
  372. api_packet_count++;
  373. clock_gettime(API_CLOCK, &api_last_packet);
  374. }
  375. static enum error api_ratelimit()
  376. {
  377. struct timespec ts = {0};
  378. uint64_t msdiff, mswait;
  379. uint64_t msrate = api_packet_count >= API_LONGTERM_PACKETS ?
  380. API_SENDWAIT_LONG : API_SENDWAIT;
  381. /* First of all, the first N packets are unmetered */
  382. if (api_packet_count <= API_FREESEND) {
  383. uio_debug("This packet is for free! Yay :D (%d/%d)",
  384. api_packet_count, API_FREESEND);
  385. return NOERR;
  386. }
  387. clock_gettime(API_CLOCK, &ts);
  388. msdiff = util_timespec_diff(&api_last_packet, &ts);
  389. uio_debug("Time since last packet: %ld ms", msdiff);
  390. if (msdiff >= msrate)
  391. return NOERR; /* No ratelimiting is needed */
  392. /* Need ratelimit, so do it here for now */
  393. mswait = msrate - msdiff;
  394. uio_debug("Ratelimit is needed, sleeping for %ld ms", mswait);
  395. MS_TO_TIMESPEC_L(ts, mswait);
  396. if (nanosleep(&ts, NULL) == -1) {
  397. if (errno == EINTR && should_exit)
  398. return ERR_SHOULD_EXIT;
  399. else
  400. uio_error("Nanosleep failed: %s", strerror(errno));
  401. }
  402. return NOERR;
  403. }
  404. /*
  405. * Returns the written byte count
  406. * Or negative errno value on error
  407. */
  408. static ssize_t api_send(char *buffer, size_t data_len, size_t buf_size)
  409. {
  410. pthread_mutex_lock(&api_work_mx);
  411. ssize_t read_len;
  412. int en;
  413. if (api_ratelimit() == ERR_SHOULD_EXIT) {
  414. read_len = -2;
  415. goto end;
  416. }
  417. uio_debug("{Api}: Sending: %.*s", (int)data_len, buffer);
  418. if (api_encryption)
  419. data_len = api_encrypt(buffer, data_len);
  420. en = net_send(buffer, data_len);
  421. if (en < 0) {
  422. read_len = en;
  423. goto end;
  424. }
  425. read_len = net_read(buffer, buf_size);
  426. if (read_len < 0) {
  427. if (read_len == -EINTR)
  428. uio_error("!!! BAD PLACE EINTR !!! report pls");
  429. goto end; /* This could lead so some problems if we also want to
  430. log out. If we hit this, the msg got sent, but we
  431. couldn't read the response. That means, in the
  432. logout call, this msg's data will be read
  433. Let's see if this ever comes up */
  434. }
  435. api_ratelimit_sent();
  436. if (api_encryption)
  437. read_len = api_decrypt(buffer, read_len);
  438. uio_debug("{Api}: Reading: %.*s", (int)read_len, buffer);
  439. end:
  440. pthread_mutex_unlock(&api_work_mx);
  441. return read_len;
  442. }
  443. long api_res_code(const char *buffer)
  444. {
  445. char *end;
  446. long res = strtol(buffer, &end, 10);
  447. if (res == 0 && buffer == end) {
  448. uio_error("No error codes in the response");
  449. return -1;
  450. }
  451. assert(*end == ' ');
  452. return res;
  453. }
  454. static bool api_get_fl(const char *buffer, int32_t index, const char *delim,
  455. char **const out_start, size_t *const out_len)
  456. {
  457. assert(index > 0);
  458. size_t len = strcspn(buffer, delim);
  459. while (--index > 0) {
  460. buffer += len + 1;
  461. len = strcspn(buffer, delim);
  462. }
  463. *out_start = (char*)buffer;
  464. *out_len = len;
  465. return true;
  466. }
  467. static bool api_get_line(const char *buffer, int32_t line_num,
  468. char **const out_line_start, size_t *const out_line_len)
  469. {
  470. return api_get_fl(buffer, line_num, "\n", out_line_start, out_line_len);
  471. }
  472. static bool api_get_field(const char *buffer, int32_t field_num,
  473. char **const out_field_start, size_t *const out_field_len)
  474. {
  475. return api_get_fl(buffer, field_num, " |\n", out_field_start, out_field_len);
  476. }
  477. /*
  478. * Parse a line, where fields are separated by '|'
  479. * Varargs should be:
  480. * A scanf like format specifier - out ptr, ...
  481. * Ended with a NULL format specifier
  482. *
  483. * %s is overridden, so if the string is 0 length, it will return NULL
  484. *
  485. * Returns the number of fields parsed
  486. */
  487. int api_field_parse(const char *line, ...)
  488. {
  489. int f_count = 1;
  490. va_list ap;
  491. const char *fmt;
  492. va_start(ap, line);
  493. fmt = va_arg(ap, const char*);
  494. while (fmt) {
  495. char *fs;
  496. size_t fl;
  497. void *ptr;
  498. char scan_fmt[32];
  499. assert(fmt[0] == '%');
  500. api_get_fl(line, f_count, "|\n", &fs, &fl);
  501. assert(!(fl == 0 && fmt[1] != 's'));
  502. ptr = va_arg(ap, void*);
  503. if (fl == 0) {
  504. *(void**)ptr = NULL;
  505. goto next;
  506. }
  507. if (fmt[1] == 's') {
  508. *(char**)ptr = strndup(fs, fl);
  509. goto next;
  510. }
  511. snprintf(scan_fmt, sizeof(scan_fmt), "%%%d%s", fl, &fmt[1]);
  512. if (sscanf(fs, scan_fmt, ptr) != 1)
  513. goto end;
  514. next:
  515. f_count++;
  516. fmt = va_arg(ap, const char*);
  517. }
  518. end:
  519. va_end(ap);
  520. return f_count - 1;
  521. }
  522. #if 0
  523. static char *api_get_field_mod(char *buffer, int32_t field_num)
  524. {
  525. char *sptr = NULL;
  526. char *f_start;
  527. f_start = strtok_r(buffer, " ", &sptr);
  528. if (!f_start)
  529. return NULL;
  530. while (field_num --> 0) {
  531. f_start = strtok_r(NULL, " ", &sptr);
  532. if (!f_start)
  533. return NULL;
  534. }
  535. return f_start;
  536. }
  537. #endif
  538. /* Basically convert remote codes into local error codes */
  539. static enum error api_cmd_base_errorc(long code, char buffer[API_BUFSIZE])
  540. {
  541. switch (code) {
  542. case APICODE_ILLEGAL_INPUT_OR_ACCESS_DENIED:
  543. uio_error("Got unretryable error code: %d", code);
  544. return ERR_API_INVCOMM;
  545. case APICODE_BANNED:
  546. {
  547. char *ls;
  548. size_t ll;
  549. api_get_line(buffer, 2, &ls, &ll);
  550. uio_error("Banned: %.*s", (int)ll, ls);
  551. return ERR_API_BANNED;
  552. }
  553. case APICODE_UNKNOWN_COMMAND:
  554. uio_error("The sent command is unknown");
  555. return ERR_API_CMD_UNK;
  556. case APICODE_INTERNAL_SERVER_ERROR:
  557. uio_error("Internal server error!");
  558. return ERR_API_INT_SRV;
  559. case APICODE_ANIDB_OUT_OF_SERVICE:
  560. uio_error("AniDB is currently out of service");
  561. return ERR_API_OOS;
  562. case APICODE_SERVER_BUSY:
  563. uio_warning("Server is busy rn, trying again later");
  564. return ERR_API_SRV_BUSY;
  565. case APICODE_TIMEOUT:
  566. uio_debug("Timed out, retrying");
  567. return ERR_API_TIMEOUT;
  568. case APICODE_LOGIN_FIRST:
  569. uio_error("This command required AUTH");
  570. return ERR_API_NOLOGIN;
  571. case APICODE_ACCESS_DENIED:
  572. uio_error("Access is denied for this info");
  573. return ERR_API_AXX_DENIED;
  574. case APICODE_INVALID_SESSION:
  575. uio_error("The login session is invalid");
  576. return ERR_API_INV_SESSION;
  577. default:
  578. /* Not an error, or at least not a base error */
  579. return NOERR;
  580. }
  581. }
  582. /*
  583. * Base for all api_cmd's. This will also execute the default
  584. * error code handers, like 505, 555, 604...
  585. * If success, res.code will be filled out
  586. */
  587. static enum error api_cmd_base_pref(char buffer[API_BUFSIZE], int send_len,
  588. struct api_result *res)
  589. {
  590. enum error err = NOERR;
  591. int retry_count = 0;
  592. api_g_retry_count = 0;
  593. while (retry_count < API_MAX_TRYAGAIN &&
  594. api_g_retry_count < API_MAX_TRYAGAIN) {
  595. long code;
  596. char l_buff[API_BUFSIZE];
  597. ssize_t res_len;
  598. /* Need the copy because the response will be written here too,
  599. * and we will send the response back when retrying */
  600. memcpy(l_buff, buffer, send_len);
  601. res_len = api_send(buffer, send_len, API_BUFSIZE);
  602. if (res_len < 0) {
  603. if (res_len == -EINTR && should_exit)
  604. err = ERR_SHOULD_EXIT;
  605. else
  606. err = ERR_API_COMMFAIL;
  607. goto end;
  608. }
  609. code = api_res_code(buffer);
  610. if (code == -1) {
  611. err = ERR_API_RESP_INVALID;
  612. goto end;
  613. }
  614. res->code = code;
  615. err = api_cmd_base_errorc(code, buffer);
  616. if (err == ERR_API_OOS || err == ERR_API_SRV_BUSY ||
  617. err == ERR_API_TIMEOUT) {
  618. struct timespec ts;
  619. time_t wait_ms = API_TRYAGAIN_TIME;
  620. if (err == ERR_API_OOS) {
  621. /* In this case, we should wait 30 minutes as per the wiki */
  622. wait_ms = 30 * 60 * 1000;
  623. }
  624. MS_TO_TIMESPEC_L(ts, wait_ms);
  625. retry_count++;
  626. uio_debug("Retry after %ld ms (%d/%d)", wait_ms,
  627. retry_count, API_MAX_TRYAGAIN);
  628. if (nanosleep(&ts, NULL) == -1) {
  629. if (errno == EINTR && should_exit) {
  630. err = ERR_SHOULD_EXIT;
  631. goto end;
  632. }
  633. }
  634. /* Copy the saved command back */
  635. memcpy(buffer, l_buff, send_len);
  636. continue;
  637. }
  638. if (err == ERR_API_NOLOGIN || err == ERR_API_INV_SESSION) {
  639. if (api_authed == false) {
  640. /* If this happens and we are not logged in, don't retry */
  641. err = ERR_API_COMMFAIL;
  642. break;
  643. }
  644. api_g_retry_count++;
  645. if (api_g_retry_count < API_MAX_TRYAGAIN) {
  646. struct timespec ts;
  647. MS_TO_TIMESPEC_L(ts, 30000);
  648. uio_debug("Let's try loggin in agane after waiting some");
  649. api_authed = false; /* We got logged out probably */
  650. if (nanosleep(&ts, NULL) == -1) {
  651. if (errno == EINTR && should_exit) {
  652. err = ERR_SHOULD_EXIT;
  653. goto end;
  654. }
  655. }
  656. err = api_auth(); /* -> will call this function */
  657. if (api_g_retry_count < API_MAX_TRYAGAIN) {
  658. memcpy(buffer, l_buff, send_len);
  659. continue;
  660. }
  661. }
  662. break;
  663. }
  664. break;
  665. };
  666. if (retry_count >= API_MAX_TRYAGAIN ||
  667. api_g_retry_count >= API_MAX_TRYAGAIN) {
  668. uio_debug("Max retry count reached");
  669. goto end;
  670. }
  671. end:
  672. return err;
  673. }
  674. static enum error api_cmd_base(char buffer[API_BUFSIZE], struct api_result *res,
  675. const char *fmt, ...)
  676. {
  677. va_list ap;
  678. int send_len;
  679. va_start(ap, fmt);
  680. send_len = vsnprintf(buffer, API_BUFSIZE, fmt, ap);
  681. va_end(ap);
  682. return api_cmd_base_pref(buffer, send_len, res);
  683. }
  684. static enum error api_cmd_encrypt(const char *uname, struct api_result *res)
  685. {
  686. char buffer[API_BUFSIZE];
  687. enum error err;
  688. /* Usernames can't contain '&' */
  689. err = api_cmd_base(buffer, res, "ENCRYPT user=%s&type=1", uname);
  690. if (err != NOERR)
  691. return err;
  692. if (res->code == APICODE_ENCRYPTION_ENABLED) {
  693. char *fs;
  694. size_t fl;
  695. bool gfl = api_get_field(buffer, 2, &fs, &fl);
  696. assert(gfl);
  697. (void)gfl;
  698. assert(sizeof(res->encrypt.salt) > fl);
  699. memcpy(res->encrypt.salt, fs, fl);
  700. res->encrypt.salt[fl] = '\0';
  701. }
  702. return err;
  703. }
  704. enum error api_cmd_version(struct api_result *res)
  705. {
  706. char buffer[API_BUFSIZE];
  707. enum error err;
  708. err = api_cmd_base(buffer, res, "VERSION");
  709. if (err != NOERR)
  710. return err;
  711. if (res->code == APICODE_VERSION) {
  712. char *ver_start;
  713. size_t ver_len;
  714. bool glr = api_get_line(buffer, 2, &ver_start, &ver_len);
  715. assert(glr);
  716. (void)glr;
  717. assert(ver_len < sizeof(res->version.version_str));
  718. memcpy(res->version.version_str, ver_start, ver_len);
  719. res->version.version_str[ver_len] = '\0';
  720. }
  721. return err;
  722. }
  723. static enum error api_cmd_auth(const char *uname, const char *pass,
  724. struct api_result *res)
  725. {
  726. char buffer[API_BUFSIZE];
  727. enum error err;
  728. err = api_cmd_base(buffer, res, "AUTH user=%s&pass=%B&protover="
  729. API_VERSION "&client=caniadd&clientver=" PROG_VERSION
  730. "&enc=UTF-8", uname, pass);
  731. if (err != NOERR)
  732. return err;
  733. if (res->code == APICODE_LOGIN_ACCEPTED ||
  734. res->code == APICODE_LOGIN_ACCEPTED_NEW_VERSION) {
  735. char *sess;
  736. size_t sess_len;
  737. bool gfr = api_get_field(buffer, 2, &sess, &sess_len);
  738. assert(gfr);
  739. (void)gfr;
  740. assert(sess_len < sizeof(res->auth.session_key));
  741. memcpy(res->auth.session_key, sess, sess_len);
  742. res->auth.session_key[sess_len] = '\0';
  743. } else if (res->code == APICODE_CLIENT_BANNED) {
  744. char *reason;
  745. size_t reason_len;
  746. bool gfr = api_get_field(buffer, 5, &reason, &reason_len);
  747. assert(gfr);
  748. (void)gfr;
  749. res->auth.banned_reason = strndup(reason, reason_len);
  750. }
  751. return err;
  752. }
  753. static enum error api_cmd_logout(struct api_result *res)
  754. {
  755. char buffer[API_BUFSIZE];
  756. enum error err;
  757. err = api_cmd_base(buffer, res, "LOGOUT s=%s", api_session);
  758. if (err != NOERR)
  759. return err;
  760. return err;
  761. }
  762. enum error api_cmd_uptime(struct api_result *res)
  763. {
  764. char buffer[API_BUFSIZE];
  765. enum error err;
  766. err = api_cmd_base(buffer, res, "UPTIME s=%s", api_session);
  767. if (err != NOERR)
  768. return err;
  769. if (res->code == APICODE_UPTIME) {
  770. char *ls;
  771. size_t ll;
  772. bool glf = api_get_line(buffer, 2, &ls, &ll);
  773. assert(glf);
  774. (void)glf;
  775. res->uptime.ms = strtol(ls, NULL, 10);
  776. }
  777. return err;
  778. }
  779. static enum error api_cmd_mylist_resp_parse(const char *buffer,
  780. struct api_mylist_result *mr)
  781. {
  782. /* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}|
  783. * {int4 date}|{int2 state}|{int4 viewdate}|{str storage}|
  784. * {str source}|{str other}|{int2 filestate} */
  785. int fc; /* the Freedom Club */
  786. char *ls;
  787. size_t ll;
  788. enum error err = NOERR;
  789. bool glr = api_get_line(buffer, 2, &ls, &ll);
  790. assert(glr);
  791. assert(ll < API_BUFSIZE - 1);
  792. (void)glr;
  793. fc = api_field_parse(ls,
  794. "%Lu", &mr->lid, "%Lu", &mr->fid, "%Lu", &mr->eid,
  795. "%Lu", &mr->aid, "%Lu", &mr->gid, "%Lu", &mr->date,
  796. "%hu", &mr->state, "%Lu", &mr->viewdate, "%s", &mr->storage,
  797. "%s", &mr->source, "%s", &mr->other, "%hu", &mr->filestate,
  798. NULL);
  799. uio_debug("Fc is: %d", fc);
  800. if (fc != 12) {
  801. if (fc >= 9)
  802. free(mr->storage);
  803. if (fc >= 10)
  804. free(mr->source);
  805. if (fc >= 11)
  806. free(mr->other);
  807. uio_error("Scanf only parsed %d", fc);
  808. err = ERR_API_RESP_INVALID;
  809. }
  810. return err;
  811. }
  812. enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash,
  813. struct api_mylistadd_opts *opts, struct api_result *res)
  814. {
  815. char buffer[API_BUFSIZE];
  816. char hash_str[ED2K_HASH_SIZE * 2 + 1];
  817. enum error err = NOERR;
  818. int send_len;
  819. util_byte2hex(hash, ED2K_HASH_SIZE, false, hash_str);
  820. /* Wiki says file size is 4 bytes, but no way that's true lol */
  821. send_len = snprintf(buffer, sizeof(buffer),
  822. "MYLISTADD s=%s&size=%ld&ed2k=%s",
  823. api_session, size, hash_str);
  824. if (opts->state_set)
  825. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  826. "&state=%hu", opts->state);
  827. if (opts->watched_set)
  828. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  829. "&viewed=%d", opts->watched);
  830. if (opts->wdate_set)
  831. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  832. "&viewdate=%ld", opts->wdate);
  833. err = api_cmd_base_pref(buffer, send_len, res);
  834. if (err != NOERR)
  835. return err;
  836. if (res->code == APICODE_MYLIST_ENTRY_ADDED) {
  837. char *ls, id_str[12];
  838. size_t ll;
  839. bool glr = api_get_line(buffer, 2, &ls, &ll);
  840. assert(glr);
  841. (void)glr;
  842. assert(sizeof(id_str) > ll);
  843. memcpy(id_str, ls, ll);
  844. id_str[ll] = '\0';
  845. res->mylistadd.new_id = strtoll(id_str, NULL, 10);
  846. /* Wiki says these id's are 4 bytes, which is untrue...
  847. * that page may be a little out of date (or they just
  848. * expect us to use common sense lmao */
  849. } else if (res->code == APICODE_FILE_ALREADY_IN_MYLIST) {
  850. err = api_cmd_mylist_resp_parse(buffer, &res->mylist);
  851. }
  852. return err;
  853. }
  854. enum error api_cmd_mylist(uint64_t lid, struct api_result *res)
  855. {
  856. char buffer[API_BUFSIZE];
  857. enum error err = NOERR;
  858. err = api_cmd_base(buffer, res, "MYLIST s=%s&lid=%lu", api_session, lid);
  859. if (err != NOERR)
  860. return err;
  861. if (res->code == APICODE_MYLIST)
  862. err = api_cmd_mylist_resp_parse(buffer, &res->mylist);
  863. return err;
  864. }
  865. enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts,
  866. struct api_result *res)
  867. {
  868. char buffer[API_BUFSIZE];
  869. enum error err = NOERR;
  870. int send_len;
  871. send_len = snprintf(buffer, sizeof(buffer),
  872. "MYLISTADD s=%s&lid=%lu&edit=1", api_session, lid);
  873. if (opts->state_set)
  874. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  875. "&state=%hu", opts->state);
  876. if (opts->watched_set)
  877. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  878. "&viewed=%d", opts->watched);
  879. if (opts->wdate_set)
  880. send_len += snprintf(buffer + send_len, sizeof(buffer) - send_len,
  881. "&viewdate=%ld", opts->wdate);
  882. err = api_cmd_base_pref(buffer, send_len, res);
  883. return err;
  884. }
  885. static enum error api_cmd_myliststats_resp_parse(const char *buffer, struct api_myliststats_result *stats)
  886. {
  887. /* all int
  888. *
  889. * {animes}|{eps}|{files}|{size of files}|{added animes}|{added eps}|
  890. * {added files}|{added groups}|{leech %}|{glory %}|{viewed % of db}|
  891. * {mylist % of db}|{viewed % of mylist}|{number of viewed eps}|
  892. * {votes}|{reviews}|{viewed length in minutes}
  893. */
  894. bool glr;
  895. char *line;
  896. size_t line_len;
  897. int fc;
  898. glr = api_get_line(buffer, 2, &line, &line_len);
  899. if (!glr)
  900. return ERR_API_RESP_INVALID;
  901. fc = api_field_parse(line,
  902. "%lu", &stats->animes,
  903. "%lu", &stats->eps,
  904. "%lu", &stats->files,
  905. "%lu", &stats->size_of_files,
  906. "%lu", &stats->added_animes,
  907. "%lu", &stats->added_eps,
  908. "%lu", &stats->added_files,
  909. "%lu", &stats->added_groups,
  910. "%lu", &stats->leech_prcnt,
  911. "%lu", &stats->glory_prcnt,
  912. "%lu", &stats->viewed_prcnt_of_db,
  913. "%lu", &stats->mylist_prcnt_of_db,
  914. "%lu", &stats->viewed_prcnt_of_mylist,
  915. "%lu", &stats->num_of_viewed_eps,
  916. "%lu", &stats->votes,
  917. "%lu", &stats->reviews,
  918. "%lu", &stats->viewed_minutes,
  919. NULL);
  920. if (fc != 17) {
  921. uio_error("Scanf only parsed %d", fc);
  922. return ERR_API_RESP_INVALID;
  923. }
  924. return NOERR;
  925. }
  926. enum error api_cmd_myliststats(struct api_result *res)
  927. {
  928. char buffer[API_BUFSIZE];
  929. enum error err = NOERR;
  930. err = api_cmd_base(buffer, res, "MYLISTSTATS s=%s", api_session);
  931. //err = api_cmd_base(buffer, res, "MYLISTSTATS");
  932. if (err != NOERR)
  933. return err;
  934. if (res->code == APICODE_MYLIST_STATS)
  935. return api_cmd_myliststats_resp_parse(buffer, &res->myliststats);
  936. else
  937. return ERR_API_RESP_INVALID;
  938. }
  939. #pragma GCC diagnostic pop