data.c (109290B)
1 /* snac - A simple, minimalistic ActivityPub instance */ 2 /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ 3 4 #include "xs.h" 5 #include "xs_hex.h" 6 #include "xs_io.h" 7 #include "xs_json.h" 8 #include "xs_openssl.h" 9 #include "xs_glob.h" 10 #include "xs_set.h" 11 #include "xs_time.h" 12 #include "xs_regex.h" 13 #include "xs_match.h" 14 #include "xs_unicode.h" 15 #include "xs_random.h" 16 #include "xs_po.h" 17 18 #include "snac.h" 19 20 #include <time.h> 21 #include <sys/stat.h> 22 #include <sys/file.h> 23 #include <sys/time.h> 24 #include <sys/mman.h> 25 #include <fcntl.h> 26 #include <pthread.h> 27 #include <dirent.h> 28 29 double disk_layout = 2.7; 30 31 /* storage serializer */ 32 pthread_mutex_t data_mutex = {0}; 33 34 int snac_upgrade(xs_str **error); 35 36 #define md5_fn(input, suffix) _md5_fn((char[MD5_HEX_SIZE + sizeof(suffix) - 1]){ 0 }, input, "" suffix) 37 38 const char *_md5_fn(char *buffer, const char *input, const char *suffix) 39 { 40 _xs_md5_buf(buffer, input, NULL); 41 strcpy(buffer + MD5_HEX_SIZE - 1, suffix); 42 return buffer; 43 } 44 45 int srv_open(const char *basedir, int auto_upgrade) 46 /* opens a server */ 47 { 48 int ret = 0; 49 xs *cfg_file = NULL; 50 FILE *f; 51 xs_str *error = NULL; 52 53 pthread_mutex_init(&data_mutex, NULL); 54 55 srv_basedir = xs_str_new(basedir); 56 57 if (xs_endswith(srv_basedir, "/")) 58 srv_basedir = xs_crop_i(srv_basedir, 0, -1); 59 60 cfg_file = xs_fmt("%s/server.json", basedir); 61 62 if ((f = fopen(cfg_file, "r")) == NULL) 63 error = xs_fmt("ERROR: cannot open '%s'", cfg_file); 64 else { 65 /* read full config file */ 66 srv_config = xs_json_load(f); 67 fclose(f); 68 69 /* parse */ 70 71 if (srv_config == NULL) 72 error = xs_fmt("ERROR: cannot parse '%s'", cfg_file); 73 else { 74 const char *host; 75 const char *prefix; 76 const char *dbglvl; 77 const char *proto; 78 79 host = xs_dict_get(srv_config, "host"); 80 prefix = xs_dict_get(srv_config, "prefix"); 81 dbglvl = xs_dict_get(srv_config, "dbglevel"); 82 proto = xs_dict_get_def(srv_config, "protocol", "https"); 83 84 if (host == NULL || prefix == NULL) 85 error = xs_str_new("ERROR: cannot get server data"); 86 else { 87 srv_baseurl = xs_fmt("%s:/" "/%s%s", proto, host, prefix); 88 89 dbglevel = (int) xs_number_get(dbglvl); 90 91 if ((dbglvl = getenv("DEBUG")) != NULL) { 92 dbglevel = atoi(dbglvl); 93 error = xs_fmt("DEBUG level set to %d from environment", dbglevel); 94 } 95 96 if (auto_upgrade) 97 ret = snac_upgrade(&error); 98 else { 99 if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout) 100 error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first"); 101 else 102 ret = 1; 103 } 104 } 105 106 } 107 } 108 109 if (error != NULL) 110 srv_log(error); 111 112 if (!ret) 113 return ret; 114 115 /* create the queue/ subdir, just in case */ 116 xs *qdir = xs_fmt("%s/queue", srv_basedir); 117 mkdirx(qdir); 118 119 xs *ibdir = xs_fmt("%s/inbox", srv_basedir); 120 mkdirx(ibdir); 121 122 xs *tmpdir = xs_fmt("%s/tmp", srv_basedir); 123 mkdirx(tmpdir); 124 125 #ifdef __APPLE__ 126 /* Apple uses st_atimespec instead of st_atim etc */ 127 #define st_atim st_atimespec 128 #define st_ctim st_ctimespec 129 #define st_mtim st_mtimespec 130 #endif 131 132 sbox_enter(srv_basedir); 133 134 /* read (and drop) emojis.json, possibly creating it */ 135 xs_free(emojis()); 136 137 /* if style.css does not exist, create it */ 138 xs *css_fn = xs_fmt("%s/style.css", srv_basedir); 139 140 if (mtime(css_fn) == 0) { 141 srv_log(xs_fmt("Writing style.css")); 142 write_default_css(); 143 } 144 145 /* create the proxy token seed */ 146 { 147 char rnd[16]; 148 xs_rnd_buf(rnd, sizeof(rnd)); 149 150 srv_proxy_token_seed = xs_hex_enc(rnd, sizeof(rnd)); 151 } 152 153 /* ensure user directories include important subdirectories */ 154 xs *users = user_list(); 155 const char *uid; 156 157 xs_list_foreach(users, uid) { 158 xs *impdir = xs_fmt("%s/user/%s/import", srv_basedir, uid); 159 xs *expdir = xs_fmt("%s/user/%s/export", srv_basedir, uid); 160 161 mkdirx(impdir); 162 mkdirx(expdir); 163 } 164 165 /* languages */ 166 srv_langs = xs_dict_new(); 167 srv_langs = xs_dict_set(srv_langs, "en", xs_stock(XSTYPE_NULL)); 168 169 xs *l_dir = xs_fmt("%s/lang/", srv_basedir); 170 mkdirx(l_dir); 171 172 l_dir = xs_str_cat(l_dir, "*.po"); 173 xs *pos = xs_glob(l_dir, 0, 0); 174 const char *po; 175 176 xs_list_foreach(pos, po) { 177 xs *d = xs_po_to_dict(po); 178 179 if (xs_is_dict(d)) { 180 xs *l = xs_split(po, "/"); 181 xs *id = xs_dup(xs_list_get(l, -1)); 182 id = xs_replace_i(id, ".po", ""); 183 184 srv_langs = xs_dict_set(srv_langs, id, d); 185 } 186 } 187 188 return ret; 189 } 190 191 192 void srv_free(void) 193 { 194 xs_free(srv_basedir); 195 xs_free(srv_config); 196 xs_free(srv_baseurl); 197 xs_free(srv_langs); 198 xs_free(srv_proxy_token_seed); 199 200 pthread_mutex_destroy(&data_mutex); 201 } 202 203 204 void user_free(snac *snac) 205 /* frees a user snac */ 206 { 207 xs_free(snac->uid); 208 xs_free(snac->basedir); 209 xs_free(snac->config); 210 xs_free(snac->config_o); 211 xs_free(snac->key); 212 xs_free(snac->links); 213 xs_free(snac->actor); 214 xs_free(snac->md5); 215 close(snac->basedfd); 216 } 217 218 FILE *user_open_file(snac *user, const char *file, int wr) 219 { 220 int fd = openat(user->basedfd, file, wr ? O_RDWR | O_CREAT : O_RDONLY, 0660); 221 if (fd < 0) 222 return NULL; 223 FILE *rv = fdopen(fd, wr ? "r+" : "r"); 224 if (rv) 225 return rv; 226 227 close(fd); 228 return NULL; 229 } 230 231 int user_link_subfile(snac *user, const char *sub, const char *file, int todfd, const char *to) 232 { 233 int subfd; 234 int ret; 235 236 if (mkdiratx(user->basedfd, sub) || (subfd = openat(user->basedfd, sub, O_RDONLY | O_DIRECTORY)) < 0) 237 return - 1; 238 239 ret = linkat(todfd, to, subfd, file, 0); 240 close(subfd); 241 242 return ret; 243 } 244 245 int user_unlink_subfile(snac *user, const char *sub, const char *file) 246 { 247 int subfd; 248 int ret; 249 250 if ((subfd = openat(user->basedfd, sub, O_RDONLY | O_DIRECTORY)) < 0) 251 return -1; 252 253 ret = unlinkat(subfd, file, 0); 254 close(subfd); 255 256 return ret; 257 } 258 259 FILE *user_open_subfile(snac *user, const char *sub, const char *file, int wr) 260 { 261 int subfd; 262 int fd; 263 264 if (mkdiratx(user->basedfd, sub) || (subfd = openat(user->basedfd, sub, O_RDONLY | O_DIRECTORY)) < 0) 265 return NULL; 266 267 fd = openat(subfd, file, wr ? O_RDWR | O_CREAT : O_RDONLY, 0660); 268 close(subfd); 269 if (fd < 0) 270 return NULL; 271 FILE *rv = fdopen(fd, wr ? "r+" : "r"); 272 if (rv) 273 return rv; 274 275 close(fd); 276 return NULL; 277 } 278 279 xs_val *user_parse_json(snac *user, const char *fn, xs_val *(*deflt)(void)) 280 { 281 xs_val *rv; 282 FILE *f; 283 FILE *bf; 284 285 struct stat cached; 286 struct stat current; 287 288 if ((f = user_open_file(user, fn, 0)) == NULL) { 289 if (!deflt) { 290 srv_debug(2, xs_fmt("error opening '%s/%s' %d", user->basedir, fn, errno)); 291 return NULL; 292 } 293 else { 294 return deflt(); 295 } 296 } 297 298 if ((bf = user_open_subfile(user, "cache", fn, 0))) { 299 if (fstat(fileno(bf), &cached) == 0 && 300 fstat(fileno(f), ¤t) == 0 && 301 cached.st_ctime > current.st_ctime) { 302 rv = xs_realloc(NULL, cached.st_size); 303 if (fread(rv, cached.st_size, 1, bf) == 1) { 304 fclose(bf); 305 fclose(f); 306 return rv; 307 } 308 } 309 fclose(bf); 310 } 311 312 rv = xs_json_load(f); 313 fclose(f); 314 315 if (!rv) { 316 srv_log(xs_fmt("error parsing '%s/%s'", user->basedir, fn)); 317 if (deflt) 318 rv = deflt(); 319 } 320 321 if ((bf = user_open_subfile(user, "cache", fn, 1))) { 322 flock(fileno(bf), LOCK_EX); 323 ftruncate(fileno(bf), 0); 324 fwrite(rv, 1, xs_size(rv), bf); 325 fclose(bf); 326 } 327 328 return rv; 329 } 330 331 int user_open(snac *user, const char *uid) 332 /* opens a user */ 333 { 334 int ret = 0; 335 336 *user = (snac){0}; 337 338 if (validate_uid(uid)) { 339 FILE *f; 340 341 xs *t = xs_fmt("%s/user/%s", srv_basedir, uid); 342 343 if (mtime(t) == 0.0) { 344 /* user folder does not exist; try with a different case */ 345 xs *lcuid = xs_tolower_i(xs_dup(uid)); 346 xs *ulist = user_list(); 347 xs_list *p = ulist; 348 const xs_str *v; 349 350 while (xs_list_iter(&p, &v)) { 351 xs *v2 = xs_tolower_i(xs_dup(v)); 352 353 if (strcmp(lcuid, v2) == 0) { 354 user->uid = xs_dup(v); 355 break; 356 } 357 } 358 } 359 else 360 user->uid = xs_str_new(uid); 361 362 if (user->uid == NULL) 363 return ret; 364 365 user->basedir = xs_fmt("%s/user/%s", srv_basedir, user->uid); 366 user->basedfd = open(user->basedir, O_DIRECTORY); 367 368 do { 369 /* read full config file */ 370 if ((user->config = user_parse_json(user, "user.json", NULL)) == NULL) 371 break; 372 373 if ((user->key = user_parse_json(user, "key.json", NULL)) == NULL) 374 break; 375 376 user->actor = xs_fmt("%s/%s", srv_baseurl, user->uid); 377 user->md5 = xs_md5_hex(user->actor, strlen(user->actor)); 378 379 /* everything is ok right now */ 380 ret = 1; 381 382 /* does it have a configuration override? */ 383 user->config_o = user_parse_json(user, "user_o.json", xs_dict_new); 384 385 user->tz = xs_dict_get_def(user->config, "tz", "UTC"); 386 } while (0); 387 388 /* verified links */ 389 if ((f = user_open_file(user, "links.json", 0)) != NULL) { 390 user->links = xs_json_load(f); 391 fclose(f); 392 } 393 } 394 else 395 srv_debug(1, xs_fmt("invalid user '%s'", uid)); 396 397 if (!ret) 398 user_free(user); 399 400 return ret; 401 } 402 403 struct user_iter { 404 DIR *d; 405 struct dirent de; 406 }; 407 408 void user_iter(struct user_iter *i) 409 { 410 xs *d = xs_fmt("%s/user", srv_basedir); 411 i->d = opendir(d); 412 } 413 414 const char *user_iter_next(struct user_iter *i) 415 { 416 struct dirent *d; 417 do { 418 if (readdir_r(i->d, &i->de, &d) != 0) 419 return NULL; 420 } while (d && (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))); 421 422 if (d) 423 return d->d_name; 424 return NULL; 425 } 426 427 void user_iter_close(struct user_iter *i) 428 { 429 closedir(i->d); 430 } 431 432 xs_list *user_list(void) 433 /* returns the list of user ids */ 434 { 435 xs *spec = xs_fmt("%s/user/" "*", srv_basedir); 436 return xs_glob(spec, 1, 0); 437 } 438 439 int user_open_by_md5(snac *snac, const char *md5) 440 /* iterates all users searching by md5 */ 441 { 442 __attribute__ ((__cleanup__(user_iter_close))) struct user_iter i = { NULL }; 443 const char *v; 444 user_iter(&i); 445 while ((v = user_iter_next(&i))) { 446 user_open(snac, v); 447 448 if (strcmp(snac->md5, md5) == 0) 449 return 1; 450 451 user_free(snac); 452 } 453 454 return 0; 455 } 456 457 int object_del_by_md5(const char *md5); 458 int user_persist(snac *user, int publish) 459 /* store user */ 460 { 461 FILE *f; 462 463 if (publish) { 464 /* check if any of the relevant fields have really changed */ 465 if ((f = user_open_file(user, "user.json", 0)) != NULL) { 466 xs *old = xs_json_load(f); 467 fclose(f); 468 469 if (old != NULL) { 470 int nw = 0; 471 const char *fields[] = { "header", "avatar", "name", "bio", 472 "metadata", "latitude", "longitude", NULL }; 473 474 for (int n = 0; fields[n]; n++) { 475 const char *of = xs_dict_get(old, fields[n]); 476 const char *nf = xs_dict_get(user->config, fields[n]); 477 478 if (of == NULL && nf == NULL) 479 continue; 480 481 if (xs_type(of) != XSTYPE_STRING || xs_type(nf) != XSTYPE_STRING || strcmp(of, nf)) { 482 nw = 1; 483 break; 484 } 485 } 486 487 if (!nw) 488 publish = 0; 489 else { 490 /* uncache the actor object */ 491 object_del_by_md5(user->md5); 492 } 493 } 494 } 495 } 496 497 renameat(user->basedfd, "user.json", user->basedfd, "user.json.bak"); 498 499 if ((f = user_open_file(user, "user.json", 1)) != NULL) { 500 xs_json_dump(user->config, 4, f); 501 fclose(f); 502 } 503 else 504 renameat(user->basedfd, "user.json.bak", user->basedfd, "user.json"); 505 506 history_del(user, "timeline.html_"); 507 timeline_touch(user); 508 509 if (publish) { 510 xs *a_msg = msg_actor(user); 511 xs *u_msg = msg_update(user, a_msg); 512 513 enqueue_message(user, u_msg); 514 515 enqueue_verify_links(user); 516 } 517 518 return 0; 519 } 520 521 522 double mtime_f_nl(FILE *f, int *n_link) 523 /* returns the mtime and number of links of a file or directory, or 0.0 */ 524 { 525 struct stat st; 526 double r = 0.0; 527 int n = 0; 528 529 if (f && fstat(fileno(f), &st) != -1) { 530 r = (double) st.st_mtim.tv_sec; 531 n = st.st_nlink; 532 } 533 534 if (n_link) 535 *n_link = n; 536 537 return r; 538 } 539 540 double mtime_nl(const char *fn, int *n_link) 541 /* returns the mtime and number of links of a file or directory, or 0.0 */ 542 { 543 struct stat st; 544 double r = 0.0; 545 int n = 0; 546 547 if (fn && stat(fn, &st) != -1) { 548 r = (double) st.st_mtim.tv_sec; 549 n = st.st_nlink; 550 } 551 552 if (n_link) 553 *n_link = n; 554 555 return r; 556 } 557 558 559 #define MIN(v1, v2) ((v1) < (v2) ? (v1) : (v2)) 560 561 double f_ctime(const char *fn) 562 /* returns the ctime of a file or directory, or 0.0 */ 563 { 564 struct stat st; 565 double r = 0.0; 566 567 if (fn && stat(fn, &st) != -1) { 568 /* return the lowest of ctime and mtime; 569 there are operations that change the ctime, like link() */ 570 r = (double) MIN(st.st_ctim.tv_sec, st.st_mtim.tv_sec); 571 } 572 573 return r; 574 } 575 576 577 int is_md5_hex(const char *md5) 578 { 579 return xs_is_hex(md5) && strlen(md5) == MD5_HEX_SIZE - 1; 580 } 581 582 583 /** database 2.1+ **/ 584 585 /** indexes **/ 586 587 588 int index_add_md5(const char *fn, const char *md5) 589 /* adds an md5 to an index */ 590 { 591 int status = HTTP_STATUS_CREATED; 592 FILE *f; 593 594 if (!is_md5_hex(md5)) { 595 srv_log(xs_fmt("index_add_md5: bad md5 %s %s", fn, md5)); 596 return HTTP_STATUS_BAD_REQUEST; 597 } 598 599 pthread_mutex_lock(&data_mutex); 600 601 if ((f = fopen(fn, "a")) != NULL) { 602 flock(fileno(f), LOCK_EX); 603 604 /* ensure the position is at the end after getting the lock */ 605 fseek(f, 0, SEEK_END); 606 607 fprintf(f, "%s\n", md5); 608 fclose(f); 609 } 610 else 611 status = HTTP_STATUS_INTERNAL_SERVER_ERROR; 612 613 pthread_mutex_unlock(&data_mutex); 614 615 return status; 616 } 617 618 619 int index_add(const char *fn, const char *id) 620 /* adds an id to an index */ 621 { 622 const char *md5 = xs_md5(id); 623 return index_add_md5(fn, md5); 624 } 625 626 627 int index_del_md5(const char *fn, const char *md5) 628 /* deletes an md5 from an index */ 629 { 630 int status = HTTP_STATUS_NOT_FOUND; 631 FILE *f; 632 633 pthread_mutex_lock(&data_mutex); 634 635 if ((f = fopen(fn, "r+")) != NULL) { 636 char line[256]; 637 638 while (fgets(line, sizeof(line), f) != NULL) { 639 line[MD5_HEX_SIZE - 1] = '\0'; 640 641 if (strcmp(line, md5) == 0) { 642 /* found! just rewind, overwrite it with garbage 643 and an eventual call to index_gc() will clean it 644 [yes: this breaks index_len()] */ 645 fseek(f, -MD5_HEX_SIZE, SEEK_CUR); 646 fwrite("-", 1, 1, f); 647 status = HTTP_STATUS_OK; 648 649 break; 650 } 651 } 652 653 fclose(f); 654 } 655 else 656 status = HTTP_STATUS_GONE; 657 658 pthread_mutex_unlock(&data_mutex); 659 660 return status; 661 } 662 663 664 int index_del(const char *fn, const char *id) 665 /* deletes an id from an index */ 666 { 667 const char *md5 = xs_md5(id); 668 return index_del_md5(fn, md5); 669 } 670 671 672 int index_gc(const char *fn) 673 /* garbage-collects an index, deleting objects that are not here */ 674 { 675 FILE *i, *o; 676 int gc = -1; 677 678 pthread_mutex_lock(&data_mutex); 679 680 if ((i = fopen(fn, "r")) != NULL) { 681 xs *nfn = xs_fmt("%s.new", fn); 682 char line[256]; 683 684 if ((o = fopen(nfn, "w")) != NULL) { 685 gc = 0; 686 687 while (fgets(line, sizeof(line), i) != NULL) { 688 line[MD5_HEX_SIZE - 1] = '\0'; 689 690 if (line[0] != '-' && object_here_by_md5(line)) 691 fprintf(o, "%s\n", line); 692 else 693 gc++; 694 } 695 696 fclose(o); 697 698 xs *ofn = xs_fmt("%s.bak", fn); 699 700 unlink(ofn); 701 if (link(fn, ofn) == -1 || rename(nfn, fn) == -1) { 702 fprintf(stderr, "Updating link during garbage collection failed: %s.\n", strerror(errno)); 703 } 704 } 705 706 fclose(i); 707 } 708 709 pthread_mutex_unlock(&data_mutex); 710 711 return gc; 712 } 713 714 715 int index_in_md5(const char *fn, const char *md5) 716 /* checks if the md5 is already in the index */ 717 { 718 FILE *f; 719 int ret = 0; 720 721 if ((f = fopen(fn, "r")) != NULL) { 722 flock(fileno(f), LOCK_SH); 723 724 char line[256]; 725 726 while (!ret && fgets(line, sizeof(line), f) != NULL) { 727 line[MD5_HEX_SIZE - 1] = '\0'; 728 729 if (strcmp(line, md5) == 0) 730 ret = 1; 731 } 732 733 fclose(f); 734 } 735 736 return ret; 737 } 738 739 740 int index_in(const char *fn, const char *id) 741 /* checks if the object id is already in the index */ 742 { 743 const char *md5 = xs_md5(id); 744 return index_in_md5(fn, md5); 745 } 746 747 748 int index_first(const char *fn, char md5[MD5_HEX_SIZE]) 749 /* reads the first entry of an index */ 750 { 751 FILE *f; 752 int ret = 0; 753 754 if ((f = fopen(fn, "r")) != NULL) { 755 if (fread(md5, MD5_HEX_SIZE, 1, f)) { 756 md5[MD5_HEX_SIZE - 1] = '\0'; 757 ret = 1; 758 } 759 760 fclose(f); 761 } 762 763 return ret; 764 } 765 766 767 int index_len(const char *fn) 768 /* returns the number of elements in an index */ 769 { 770 struct stat st; 771 int len = 0; 772 773 if (stat(fn, &st) != -1) 774 len = st.st_size / MD5_HEX_SIZE; 775 776 return len; 777 } 778 779 780 xs_list *index_list(const char *fn, int max) 781 /* returns an index as a list */ 782 { 783 xs_list *list = xs_list_new(); 784 FILE *f; 785 int n = 0; 786 787 if ((f = fopen(fn, "r")) != NULL) { 788 flock(fileno(f), LOCK_SH); 789 790 char line[256]; 791 792 while (n < max && fgets(line, sizeof(line), f) != NULL) { 793 if (line[0] != '-') { 794 line[MD5_HEX_SIZE - 1] = '\0'; 795 list = xs_list_append(list, line); 796 n++; 797 } 798 } 799 800 fclose(f); 801 } 802 803 return list; 804 } 805 806 807 int index_desc_next(FILE *f, char md5[MD5_HEX_SIZE]) 808 /* reads the next entry of a desc index */ 809 { 810 for (;;) { 811 /* move backwards 2 entries */ 812 if (fseek(f, MD5_HEX_SIZE * -2, SEEK_CUR) == -1) 813 return 0; 814 815 /* read and md5 */ 816 if (!fread(md5, MD5_HEX_SIZE, 1, f)) 817 return 0; 818 819 if (md5[0] != '-') 820 break; 821 } 822 823 md5[MD5_HEX_SIZE - 1] = '\0'; 824 825 return 1; 826 } 827 828 829 int index_desc_first(FILE *f, char md5[MD5_HEX_SIZE], int skip) 830 /* reads the first entry of a desc index */ 831 { 832 /* try to position at the end and then back to the first element */ 833 if (fseek(f, 0, SEEK_END) || fseek(f, (skip + 1) * -MD5_HEX_SIZE, SEEK_CUR)) 834 return 0; 835 836 /* try to read an md5 */ 837 if (!fread(md5, MD5_HEX_SIZE, 1, f)) 838 return 0; 839 840 /* null-terminate */ 841 md5[MD5_HEX_SIZE - 1] = '\0'; 842 843 /* deleted? retry next */ 844 if (md5[0] == '-') 845 return index_desc_next(f, md5); 846 847 return 1; 848 } 849 850 int index_asc_first(FILE *f,char md5[MD5_HEX_SIZE], const char *seek_md5) 851 /* reads the first entry of an ascending index, starting from a given md5 */ 852 { 853 fseek(f, SEEK_SET, 0); 854 while (fread(md5, MD5_HEX_SIZE, 1, f)) { 855 md5[MD5_HEX_SIZE - 1] = '\0'; 856 if (strcmp(md5,seek_md5) == 0) { 857 return index_asc_next(f, md5); 858 } 859 } 860 return 0; 861 } 862 863 int index_asc_next(FILE *f, char md5[MD5_HEX_SIZE]) 864 /* reads the next entry of an ascending index */ 865 { 866 for (;;) { 867 /* read an md5 */ 868 if (!fread(md5, MD5_HEX_SIZE, 1, f)) 869 return 0; 870 871 /* deleted, skip */ 872 if (md5[0] != '-') 873 break; 874 } 875 876 md5[MD5_HEX_SIZE - 1] = '\0'; 877 878 return 1; 879 } 880 881 882 xs_list *index_list_desc(const char *fn, int skip, int show) 883 /* returns an index as a list, in reverse order */ 884 { 885 xs_list *list = xs_list_new(); 886 FILE *f; 887 888 if ((f = fopen(fn, "r")) != NULL) { 889 char md5[MD5_HEX_SIZE]; 890 891 if (index_desc_first(f, md5, skip)) { 892 int n = 1; 893 894 do { 895 list = xs_list_append(list, md5); 896 } while (n++ < show && index_desc_next(f, md5)); 897 } 898 899 fclose(f); 900 } 901 902 return list; 903 } 904 905 906 /** objects **/ 907 908 static xs_str *_object_fn_by_md5(const char *md5, const char *func) 909 { 910 xs *bfn = xs_fmt("%s/object/%c%c", srv_basedir, md5[0], md5[1]); 911 xs_str *ret; 912 int ok = 1; 913 914 /* an object deleted from an index; fail but don't bark */ 915 if (md5[0] == '-') 916 ok = 0; 917 else 918 if (!is_md5_hex(md5)) { 919 srv_log(xs_fmt("_object_fn_by_md5() [from %s()]: bad md5 '%s'", func, md5)); 920 ok = 0; 921 } 922 923 if (ok) { 924 mkdirx(bfn); 925 ret = xs_fmt("%s/%s.json", bfn, md5); 926 } 927 else 928 ret = xs_fmt("%s/object/invalid/invalid.json", srv_basedir); 929 930 return ret; 931 } 932 933 934 static xs_str *_object_fn(const char *id) 935 { 936 const char *md5 = xs_md5(id); 937 return _object_fn_by_md5(md5, "_object_fn"); 938 } 939 940 941 int object_here_by_md5(const char *id) 942 /* checks if an object is already downloaded */ 943 { 944 xs *fn = _object_fn_by_md5(id, "object_here_by_md5"); 945 return mtime(fn) > 0.0; 946 } 947 948 949 int object_here(const char *id) 950 /* checks if an object is already downloaded */ 951 { 952 xs *fn = _object_fn(id); 953 return mtime(fn) > 0.0; 954 } 955 956 957 int object_get_by_md5(const char *md5, xs_dict **obj) 958 /* returns a stored object, optionally of the requested type */ 959 { 960 int status = HTTP_STATUS_NOT_FOUND; 961 xs *fn = _object_fn_by_md5(md5, "object_get_by_md5"); 962 FILE *f; 963 964 if ((f = fopen(fn, "r")) != NULL) { 965 *obj = xs_json_load(f); 966 fclose(f); 967 968 if (*obj) 969 status = HTTP_STATUS_OK; 970 } 971 else 972 *obj = NULL; 973 974 return status; 975 } 976 977 978 int object_get(const char *id, xs_dict **obj) 979 /* returns a stored object, optionally of the requested type */ 980 { 981 const char *md5 = xs_md5(id); 982 return object_get_by_md5(md5, obj); 983 } 984 985 986 int _object_add(const char *id, const xs_dict *obj, int ow) 987 /* stores an object */ 988 { 989 int status = HTTP_STATUS_CREATED; /* Created */ 990 xs *fn = _object_fn(id); 991 FILE *f; 992 993 if (mtime(fn) > 0.0) { 994 if (!ow) { 995 /* object already here */ 996 srv_debug(1, xs_fmt("object_add object already here %s", id)); 997 return HTTP_STATUS_NO_CONTENT; 998 } 999 else 1000 status = HTTP_STATUS_OK; 1001 } 1002 1003 if ((f = fopen(fn, "w")) != NULL) { 1004 flock(fileno(f), LOCK_EX); 1005 1006 xs_json_dump(obj, 4, f); 1007 fclose(f); 1008 1009 /* does this object has a parent? */ 1010 const char *in_reply_to = get_in_reply_to(obj); 1011 1012 if (!xs_is_null(in_reply_to) && *in_reply_to) { 1013 /* update the children index of the parent */ 1014 xs *c_idx = _object_fn(in_reply_to); 1015 1016 c_idx = xs_replace_i(c_idx, ".json", "_c.idx"); 1017 1018 if (!index_in(c_idx, id)) { 1019 index_add(c_idx, id); 1020 srv_debug(1, xs_fmt("object_add added child %s to %s", id, c_idx)); 1021 } 1022 else 1023 srv_debug(1, xs_fmt("object_add %s child already in %s", id, c_idx)); 1024 1025 /* create a one-element index with the parent */ 1026 xs *p_idx = xs_replace(fn, ".json", "_p.idx"); 1027 1028 if (mtime(p_idx) == 0.0) { 1029 index_add(p_idx, in_reply_to); 1030 srv_debug(1, xs_fmt("object_add added parent %s to %s", in_reply_to, p_idx)); 1031 } 1032 } 1033 } 1034 else { 1035 srv_log(xs_fmt("object_add error writing %s (errno: %d)", fn, errno)); 1036 status = HTTP_STATUS_INTERNAL_SERVER_ERROR; 1037 } 1038 1039 srv_debug(1, xs_fmt("object_add %s %s %d", id, fn, status)); 1040 1041 return status; 1042 } 1043 1044 1045 int object_add(const char *id, const xs_dict *obj) 1046 /* stores an object */ 1047 { 1048 return _object_add(id, obj, 0); 1049 } 1050 1051 1052 int object_add_ow(const char *id, const xs_dict *obj) 1053 /* stores an object (overwriting allowed) */ 1054 { 1055 return _object_add(id, obj, 1); 1056 } 1057 1058 1059 int object_del_by_md5(const char *md5) 1060 /* deletes an object by its md5 */ 1061 { 1062 int status = HTTP_STATUS_NOT_FOUND; 1063 xs *fn = _object_fn_by_md5(md5, "object_del_by_md5"); 1064 1065 if (unlink(fn) != -1) { 1066 status = HTTP_STATUS_OK; 1067 1068 /* also delete associated indexes */ 1069 xs *spec = xs_dup(fn); 1070 spec = xs_replace_i(spec, ".json", "*.idx"); 1071 xs *files = xs_glob(spec, 0, 0); 1072 char *p; 1073 const char *v; 1074 1075 p = files; 1076 while (xs_list_iter(&p, &v)) { 1077 srv_debug(1, xs_fmt("object_del index %s", v)); 1078 unlink(v); 1079 } 1080 } 1081 1082 srv_debug(1, xs_fmt("object_del %s %d", fn, status)); 1083 1084 return status; 1085 } 1086 1087 1088 int object_del(const char *id) 1089 /* deletes an object */ 1090 { 1091 const char *md5 = xs_md5(id); 1092 return object_del_by_md5(md5); 1093 } 1094 1095 1096 int object_del_if_unref(const char *id) 1097 /* deletes an object if its n_links < 2 */ 1098 { 1099 xs *fn = _object_fn(id); 1100 int n_links; 1101 int ret = 0; 1102 1103 if (mtime_nl(fn, &n_links) > 0.0 && n_links < 2) 1104 ret = object_del(id); 1105 1106 return ret; 1107 } 1108 1109 1110 double object_ctime_by_md5(const char *md5) 1111 { 1112 xs *fn = _object_fn_by_md5(md5, "object_ctime_by_md5"); 1113 return f_ctime(fn); 1114 } 1115 1116 1117 double object_ctime(const char *id) 1118 { 1119 const char *md5 = xs_md5(id); 1120 return object_ctime_by_md5(md5); 1121 } 1122 1123 1124 double object_mtime_by_md5(const char *md5) 1125 { 1126 xs *fn = _object_fn_by_md5(md5, "object_mtime_by_md5"); 1127 return mtime(fn); 1128 } 1129 1130 1131 double object_mtime(const char *id) 1132 { 1133 const char *md5 = xs_md5(id); 1134 return object_mtime_by_md5(md5); 1135 } 1136 1137 1138 void object_touch(const char *id) 1139 { 1140 const char *md5 = xs_md5(id); 1141 xs *fn = _object_fn_by_md5(md5, "object_touch"); 1142 1143 if (mtime(fn)) 1144 utimes(fn, NULL); 1145 } 1146 1147 1148 xs_str *_object_index_fn(const char *id, const char *idxsfx) 1149 /* returns the filename of an object's index */ 1150 { 1151 xs_str *fn = _object_fn(id); 1152 return xs_replace_i(fn, ".json", idxsfx); 1153 } 1154 1155 1156 int object_likes_len(const char *id) 1157 /* returns the number of likes (without reading the index) */ 1158 { 1159 xs *fn = _object_index_fn(id, "_l.idx"); 1160 return index_len(fn); 1161 } 1162 1163 1164 int object_announces_len(const char *id) 1165 /* returns the number of announces (without reading the index) */ 1166 { 1167 xs *fn = _object_index_fn(id, "_a.idx"); 1168 return index_len(fn); 1169 } 1170 1171 1172 xs_list *object_children(const char *id) 1173 /* returns the list of an object's children */ 1174 { 1175 xs *fn = _object_index_fn(id, "_c.idx"); 1176 return index_list(fn, XS_ALL); 1177 } 1178 1179 1180 xs_list *object_likes(const char *id) 1181 { 1182 xs *fn = _object_index_fn(id, "_l.idx"); 1183 return index_list(fn, XS_ALL); 1184 } 1185 1186 1187 xs_list *object_announces(const char *id) 1188 { 1189 xs *fn = _object_index_fn(id, "_a.idx"); 1190 return index_list(fn, XS_ALL); 1191 } 1192 1193 1194 int object_parent(const char *md5, char parent[MD5_HEX_SIZE]) 1195 /* returns the object parent, if any */ 1196 { 1197 xs *fn = _object_fn_by_md5(md5, "object_parent"); 1198 1199 fn = xs_replace_i(fn, ".json", "_p.idx"); 1200 return index_first(fn, parent); 1201 } 1202 1203 1204 int object_admire(const char *id, const char *actor, int like) 1205 /* actor likes or announces this object */ 1206 { 1207 int status = HTTP_STATUS_OK; 1208 xs *fn = _object_fn(id); 1209 1210 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); 1211 1212 if (!index_in(fn, actor)) { 1213 status = index_add(fn, actor); 1214 1215 srv_debug(1, xs_fmt("object_admire (%s) %s %s", like ? "Like" : "Announce", actor, fn)); 1216 } 1217 1218 return status; 1219 } 1220 1221 1222 int object_unadmire(const char *id, const char *actor, int like) 1223 /* actor no longer likes or announces this object */ 1224 { 1225 int status; 1226 xs *fn = _object_fn(id); 1227 1228 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); 1229 1230 status = index_del(fn, actor); 1231 1232 if (valid_status(status)) 1233 index_gc(fn); 1234 1235 srv_debug(0, 1236 xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); 1237 1238 return status; 1239 } 1240 1241 1242 xs_str *object_user_cache_fn_by_md5(snac *user, const char *md5, const char *cachedir) 1243 { 1244 return xs_fmt("%s/%s/%s.json", user->basedir, cachedir, md5); 1245 } 1246 1247 1248 xs_str *object_user_cache_fn(snac *user, const char *id, const char *cachedir) 1249 { 1250 const char *md5 = xs_md5(id); 1251 return object_user_cache_fn_by_md5(user, md5, cachedir); 1252 } 1253 1254 1255 xs_str *object_user_cache_index_fn(snac *user, const char *cachedir) 1256 { 1257 return xs_fmt("%s/%s.idx", user->basedir, cachedir); 1258 } 1259 1260 1261 int _object_user_cache(snac *user, const char *id, const char *cachedir, int del) 1262 /* adds or deletes from a user cache */ 1263 { 1264 int ret; 1265 xs *idx = object_user_cache_index_fn(user, cachedir); 1266 1267 if (del) { 1268 ret = user_unlink_subfile(user, cachedir, md5_fn(id, ".json")); 1269 index_del(idx, id); 1270 } 1271 else { 1272 xs *ofn = _object_fn(id); 1273 ret = user_link_subfile(user, cachedir, md5_fn(id, ".json"), AT_FDCWD, ofn); 1274 index_add(idx, id); 1275 } 1276 1277 return ret; 1278 #if 0 1279 xs *ofn = _object_fn(id); 1280 xs *cfn = object_user_cache_fn(user, id, cachedir); 1281 xs *idx = object_user_cache_index_fn(user, cachedir); 1282 int ret; 1283 1284 if (del) { 1285 ret = unlink(cfn); 1286 index_del(idx, id); 1287 } 1288 else { 1289 /* create the subfolder, if it does not exist */ 1290 xs *dir = xs_fmt("%s/%s/", user->basedir, cachedir); 1291 mkdirx(dir); 1292 1293 if ((ret = link(ofn, cfn)) != -1) 1294 index_add(idx, id); 1295 } 1296 1297 return ret; 1298 #endif 1299 } 1300 1301 1302 int object_user_cache_add(snac *user, const char *id, const char *cachedir) 1303 /* caches an object into a user cache */ 1304 { 1305 return _object_user_cache(user, id, cachedir, 0); 1306 } 1307 1308 1309 int object_user_cache_del(snac *user, const char *id, const char *cachedir) 1310 /* deletes an object from a user cache */ 1311 { 1312 return _object_user_cache(user, id, cachedir, 1); 1313 } 1314 1315 1316 int object_user_cache_in(snac *user, const char *id, const char *cachedir) 1317 /* checks if an object is stored in a cache */ 1318 { 1319 xs *cfn = object_user_cache_fn(user, id, cachedir); 1320 return !!(mtime(cfn) != 0.0); 1321 } 1322 1323 1324 int object_user_cache_in_by_md5(snac *user, const char *md5, const char *cachedir) 1325 /* checks if an object is stored in a cache */ 1326 { 1327 xs *cfn = object_user_cache_fn_by_md5(user, md5, cachedir); 1328 return !!(mtime(cfn) != 0.0); 1329 } 1330 1331 1332 xs_list *object_user_cache_list(snac *user, const char *cachedir, int max, int inv) 1333 /* returns the objects in a cache as a list */ 1334 { 1335 xs *idx = xs_fmt("%s/%s.idx", user->basedir, cachedir); 1336 return inv ? index_list_desc(idx, 0, max) : index_list(idx, max); 1337 } 1338 1339 1340 /** specialized functions **/ 1341 1342 /** followers **/ 1343 1344 int follower_add(snac *snac, const char *actor) 1345 /* adds a follower */ 1346 { 1347 int ret = object_user_cache_add(snac, actor, "followers"); 1348 1349 snac_debug(snac, 2, xs_fmt("follower_add %s", actor)); 1350 1351 return ret == -1 ? HTTP_STATUS_INTERNAL_SERVER_ERROR : HTTP_STATUS_OK; 1352 } 1353 1354 1355 int follower_del(snac *snac, const char *actor) 1356 /* deletes a follower */ 1357 { 1358 int ret = object_user_cache_del(snac, actor, "followers"); 1359 1360 snac_debug(snac, 2, xs_fmt("follower_del %s", actor)); 1361 1362 return ret == -1 ? HTTP_STATUS_NOT_FOUND : HTTP_STATUS_OK; 1363 } 1364 1365 1366 int follower_check(snac *snac, const char *actor) 1367 /* checks if someone is a follower */ 1368 { 1369 return object_user_cache_in(snac, actor, "followers"); 1370 } 1371 1372 1373 int follower_list_len(snac *snac) 1374 /* returns the number of followers */ 1375 { 1376 xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0); 1377 return xs_list_len(list); 1378 } 1379 1380 1381 xs_list *follower_list(snac *snac) 1382 /* returns the list of followers */ 1383 { 1384 xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0); 1385 xs_list *fwers = xs_list_new(); 1386 char *p; 1387 const char *v; 1388 1389 /* resolve the list of md5 to be a list of actors */ 1390 p = list; 1391 while (xs_list_iter(&p, &v)) { 1392 xs *a_obj = NULL; 1393 1394 if (valid_status(object_get_by_md5(v, &a_obj))) { 1395 const char *actor = xs_dict_get(a_obj, "id"); 1396 1397 if (!xs_is_null(actor)) { 1398 /* check if the actor is still cached */ 1399 xs *fn = xs_fmt("%s/followers/%s.json", snac->basedir, v); 1400 1401 if (mtime(fn) > 0.0) 1402 fwers = xs_list_append(fwers, actor); 1403 } 1404 } 1405 } 1406 1407 return fwers; 1408 } 1409 1410 1411 /** pending followers **/ 1412 1413 int pending_add(snac *user, const char *actor, const xs_dict *msg) 1414 /* stores the follow message for later confirmation */ 1415 { 1416 xs *dir = xs_fmt("%s/pending", user->basedir); 1417 const char *md5 = xs_md5(actor); 1418 xs *fn = xs_fmt("%s/%s.json", dir, md5); 1419 FILE *f; 1420 1421 mkdirx(dir); 1422 1423 if ((f = fopen(fn, "w")) == NULL) 1424 return -1; 1425 1426 xs_json_dump(msg, 4, f); 1427 fclose(f); 1428 1429 return 0; 1430 } 1431 1432 1433 int pending_check(snac *user, const char *actor) 1434 /* checks if there is a pending follow confirmation for the actor */ 1435 { 1436 const char *md5 = xs_md5(actor); 1437 xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); 1438 1439 return mtime(fn) != 0; 1440 } 1441 1442 1443 xs_dict *pending_get(snac *user, const char *actor) 1444 /* returns the pending follow confirmation for the actor */ 1445 { 1446 const char *md5 = xs_md5(actor); 1447 xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); 1448 xs_dict *msg = NULL; 1449 FILE *f; 1450 1451 if ((f = fopen(fn, "r")) != NULL) { 1452 msg = xs_json_load(f); 1453 fclose(f); 1454 } 1455 1456 return msg; 1457 } 1458 1459 1460 void pending_del(snac *user, const char *actor) 1461 /* deletes a pending follow confirmation for the actor */ 1462 { 1463 const char *md5 = xs_md5(actor); 1464 xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); 1465 1466 unlink(fn); 1467 } 1468 1469 1470 xs_list *pending_list(snac *user) 1471 /* returns a list of pending follow confirmations */ 1472 { 1473 xs *spec = xs_fmt("%s/pending/""*.json", user->basedir); 1474 xs *l = xs_glob(spec, 0, 0); 1475 xs_list *r = xs_list_new(); 1476 const char *v; 1477 1478 xs_list_foreach(l, v) { 1479 FILE *f; 1480 xs *msg = NULL; 1481 1482 if ((f = fopen(v, "r")) == NULL) 1483 continue; 1484 1485 msg = xs_json_load(f); 1486 fclose(f); 1487 1488 if (msg == NULL) 1489 continue; 1490 1491 const char *actor = xs_dict_get(msg, "actor"); 1492 1493 if (xs_type(actor) == XSTYPE_STRING) 1494 r = xs_list_append(r, actor); 1495 } 1496 1497 return r; 1498 } 1499 1500 1501 int pending_count(snac *user) 1502 /* returns the number of pending follow confirmations */ 1503 { 1504 xs *spec = xs_fmt("%s/pending/""*.json", user->basedir); 1505 xs *l = xs_glob(spec, 0, 0); 1506 1507 return xs_list_len(l); 1508 } 1509 1510 1511 /** timeline **/ 1512 1513 double timeline_mtime(snac *snac) 1514 { 1515 xs *fn = xs_fmt("%s/private.idx", snac->basedir); 1516 return mtime(fn); 1517 } 1518 1519 1520 int timeline_touch(snac *snac) 1521 /* changes the date of the timeline index */ 1522 { 1523 xs *fn = xs_fmt("%s/private.idx", snac->basedir); 1524 return utimes(fn, NULL); 1525 } 1526 1527 1528 xs_str *timeline_fn_by_md5(snac *snac, const char *md5) 1529 /* get the filename of an entry by md5 from any timeline */ 1530 { 1531 xs_str *fn = NULL; 1532 1533 if (is_md5_hex(md5)) { 1534 fn = xs_fmt("%s/private/%s.json", snac->basedir, md5); 1535 1536 if (mtime(fn) == 0.0) { 1537 fn = xs_free(fn); 1538 fn = xs_fmt("%s/public/%s.json", snac->basedir, md5); 1539 1540 if (mtime(fn) == 0.0) 1541 fn = xs_free(fn); 1542 } 1543 } 1544 1545 return fn; 1546 } 1547 1548 1549 int timeline_here_by_md5(snac *snac, const char *md5) 1550 /* checks if an object is in the user cache */ 1551 { 1552 xs *fn = timeline_fn_by_md5(snac, md5); 1553 1554 return !(fn == NULL); 1555 } 1556 1557 1558 int timeline_here(snac *user, const char *id) 1559 { 1560 const char *md5 = xs_md5(id); 1561 1562 return timeline_here_by_md5(user, md5); 1563 } 1564 1565 1566 int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg) 1567 /* gets a message from the timeline */ 1568 { 1569 int status = HTTP_STATUS_NOT_FOUND; 1570 FILE *f = NULL; 1571 1572 xs *fn = timeline_fn_by_md5(snac, md5); 1573 1574 if (fn != NULL && (f = fopen(fn, "r")) != NULL) { 1575 *msg = xs_json_load(f); 1576 fclose(f); 1577 1578 if (*msg != NULL) 1579 status = HTTP_STATUS_OK; 1580 } 1581 1582 return status; 1583 } 1584 1585 1586 int timeline_del(snac *snac, const char *id) 1587 /* deletes a message from the timeline */ 1588 { 1589 /* delete from the user's caches */ 1590 object_user_cache_del(snac, id, "public"); 1591 object_user_cache_del(snac, id, "private"); 1592 1593 unpin(snac, id); 1594 unbookmark(snac, id); 1595 1596 /* try to delete the object if it's not used elsewhere */ 1597 return object_del_if_unref(id); 1598 } 1599 1600 1601 void timeline_update_indexes(snac *snac, const char *id) 1602 /* updates the indexes */ 1603 { 1604 object_user_cache_add(snac, id, "private"); 1605 1606 if (xs_startswith(id, snac->actor)) { 1607 xs *msg = NULL; 1608 1609 if (valid_status(object_get(id, &msg))) { 1610 /* if its ours and is public, also store in public */ 1611 if (is_msg_public(msg)) { 1612 if (object_user_cache_add(snac, id, "public") >= 0) { 1613 /* also add it to the instance public timeline */ 1614 xs *ipt = xs_fmt("%s/public.idx", srv_basedir); 1615 index_add(ipt, id); 1616 } 1617 else 1618 srv_debug(1, xs_fmt("Not added to public instance index %s", id)); 1619 } 1620 } 1621 } 1622 } 1623 1624 1625 int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) 1626 /* adds a message to the timeline */ 1627 { 1628 int ret = object_add(id, o_msg); 1629 timeline_update_indexes(snac, id); 1630 1631 tag_index(id, o_msg); 1632 1633 list_distribute(snac, NULL, o_msg); 1634 1635 snac_debug(snac, 1, xs_fmt("timeline_add %s", id)); 1636 1637 return ret; 1638 } 1639 1640 1641 int timeline_admire(snac *snac, const char *id, const char *admirer, int like) 1642 /* updates a timeline entry with a new admiration */ 1643 { 1644 /* if we are admiring this, add to both timelines */ 1645 if (!like && strcmp(admirer, snac->actor) == 0) { 1646 object_user_cache_add(snac, id, "public"); 1647 object_user_cache_add(snac, id, "private"); 1648 } 1649 1650 int ret = object_admire(id, admirer, like); 1651 1652 snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", 1653 like ? "Like" : "Announce", id, admirer)); 1654 1655 return ret; 1656 } 1657 1658 1659 xs_list *timeline_top_level(snac *snac, const xs_list *list) 1660 /* returns the top level md5 entries from this index */ 1661 { 1662 xs_set seen; 1663 const xs_str *v; 1664 1665 xs_set_init(&seen); 1666 1667 int c = 0; 1668 while (xs_list_next(list, &v, &c)) { 1669 char line[MD5_HEX_SIZE] = ""; 1670 1671 strncpy(line, v, sizeof(line) - 1); 1672 1673 for (;;) { 1674 char line2[MD5_HEX_SIZE]; 1675 1676 /* if it doesn't have a parent, use this */ 1677 if (!object_parent(line, line2)) 1678 break; 1679 1680 /* well, there is a parent... but is it here? */ 1681 if (!timeline_here_by_md5(snac, line2)) 1682 break; 1683 1684 /* it's here! try again with its own parent */ 1685 memcpy(line, line2, sizeof(line)); 1686 } 1687 1688 xs_set_add(&seen, line); 1689 } 1690 1691 return xs_set_result(&seen); 1692 } 1693 1694 1695 xs_str *user_index_fn(snac *user, const char *idx_name) 1696 /* returns the filename of a user index */ 1697 { 1698 return xs_fmt("%s/%s.idx", user->basedir, idx_name); 1699 } 1700 1701 1702 xs_list *timeline_simple_list(snac *user, const char *idx_name, int skip, int show, int *more) 1703 /* returns a timeline (with all entries) */ 1704 { 1705 xs *idx = user_index_fn(user, idx_name); 1706 1707 /* if a more flag is sent, request one more */ 1708 xs_list *lst = index_list_desc(idx, skip, show + (more != NULL ? 1 : 0)); 1709 1710 if (more != NULL) { 1711 if (xs_list_len(lst) > show) { 1712 *more = 1; 1713 lst = xs_list_del(lst, -1); 1714 } 1715 else 1716 *more = 0; 1717 } 1718 1719 return lst; 1720 } 1721 1722 1723 xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show, int *more) 1724 /* returns a timeline (only top level entries) */ 1725 { 1726 int c_max; 1727 1728 /* maximum number of items in the timeline */ 1729 c_max = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 1730 1731 /* never more timeline entries than the configured maximum */ 1732 if (show > c_max) 1733 show = c_max; 1734 1735 xs *list = timeline_simple_list(snac, idx_name, skip, show, more); 1736 1737 return timeline_top_level(snac, list); 1738 } 1739 1740 1741 void timeline_add_mark(snac *user) 1742 /* adds an "already seen" mark to the private timeline */ 1743 { 1744 xs *fn = xs_fmt("%s/private.idx", user->basedir); 1745 char last_entry[MD5_HEX_SIZE] = ""; 1746 FILE *f; 1747 1748 /* get the last entry in the index */ 1749 if ((f = fopen(fn, "r")) != NULL) { 1750 index_desc_first(f, last_entry, 0); 1751 fclose(f); 1752 } 1753 1754 /* is the last entry *not* a mark? */ 1755 if (strcmp(last_entry, MD5_ALREADY_SEEN_MARK) != 0) { 1756 /* add it */ 1757 index_add_md5(fn, MD5_ALREADY_SEEN_MARK); 1758 } 1759 } 1760 1761 1762 xs_str *instance_index_fn(void) 1763 { 1764 return xs_fmt("%s/public.idx", srv_basedir); 1765 } 1766 1767 1768 xs_list *timeline_instance_list(int skip, int show) 1769 /* returns the timeline for the full instance */ 1770 { 1771 xs *idx = instance_index_fn(); 1772 xs *lst = index_list_desc(idx, skip, show); 1773 1774 /* make the list unique */ 1775 xs_set rep; 1776 xs_set_init(&rep); 1777 const char *md5; 1778 1779 xs_list_foreach(lst, md5) 1780 xs_set_add(&rep, md5); 1781 1782 return xs_set_result(&rep); 1783 } 1784 1785 1786 /** following **/ 1787 1788 /* this needs special treatment and cannot use the object db as is, 1789 with a link to a cached author, because we need the Follow object 1790 in case we need to unfollow (Undo + original Follow) */ 1791 1792 xs_str *_following_fn(snac *snac, const char *actor) 1793 { 1794 const char *md5 = xs_md5(actor); 1795 return xs_fmt("%s/following/%s.json", snac->basedir, md5); 1796 } 1797 1798 1799 int following_add(snac *snac, const char *actor, const xs_dict *msg) 1800 /* adds to the following list */ 1801 { 1802 int ret = HTTP_STATUS_CREATED; 1803 xs *fn = _following_fn(snac, actor); 1804 FILE *f; 1805 xs *p_object = NULL; 1806 1807 if (valid_status(following_get(snac, actor, &p_object))) { 1808 /* object already exists; if it's of type Accept, 1809 the actor is already being followed and confirmed, 1810 so do nothing */ 1811 const char *type = xs_dict_get(p_object, "type"); 1812 1813 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { 1814 snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor)); 1815 return HTTP_STATUS_OK; 1816 } 1817 } 1818 1819 if ((f = fopen(fn, "w")) != NULL) { 1820 xs_json_dump(msg, 4, f); 1821 fclose(f); 1822 1823 /* get the filename of the actor object */ 1824 xs *actor_fn = _object_fn(actor); 1825 1826 /* increase its reference count */ 1827 fn = xs_replace_i(fn, ".json", "_a.json"); 1828 if (link(actor_fn, fn) == -1) { 1829 fprintf(stderr, "Failed to create actor link: %s\n", strerror(errno)); 1830 } 1831 } 1832 else 1833 ret = HTTP_STATUS_INTERNAL_SERVER_ERROR; 1834 1835 snac_debug(snac, 2, xs_fmt("following_add %s %s", actor, fn)); 1836 1837 return ret; 1838 } 1839 1840 1841 int following_del(snac *snac, const char *actor) 1842 /* we're not following this actor any longer */ 1843 { 1844 xs *fn = _following_fn(snac, actor); 1845 1846 snac_debug(snac, 2, xs_fmt("following_del %s %s", actor, fn)); 1847 1848 unlink(fn); 1849 1850 /* also delete the reference to the author */ 1851 fn = xs_replace_i(fn, ".json", "_a.json"); 1852 unlink(fn); 1853 1854 return HTTP_STATUS_OK; 1855 } 1856 1857 1858 int following_check(snac *snac, const char *actor) 1859 /* checks if we are following this actor */ 1860 { 1861 xs *fn = _following_fn(snac, actor); 1862 1863 return !!(mtime(fn) != 0.0); 1864 } 1865 1866 1867 int following_get(snac *snac, const char *actor, xs_dict **data) 1868 /* returns the 'Follow' object */ 1869 { 1870 xs *fn = _following_fn(snac, actor); 1871 FILE *f; 1872 int status = HTTP_STATUS_OK; 1873 1874 if ((f = fopen(fn, "r")) != NULL) { 1875 *data = xs_json_load(f); 1876 fclose(f); 1877 } 1878 else 1879 status = HTTP_STATUS_NOT_FOUND; 1880 1881 return status; 1882 } 1883 1884 1885 int following_list_len(snac *snac) 1886 /* returns number of people being followed */ 1887 { 1888 xs *spec = xs_fmt("%s/following/" "*_a.json", snac->basedir); 1889 xs *glist = xs_glob(spec, 0, 0); 1890 return xs_list_len(glist); 1891 } 1892 1893 1894 xs_list *following_list(snac *snac) 1895 /* returns the list of people being followed */ 1896 { 1897 xs *spec = xs_fmt("%s/following/" "*.json", snac->basedir); 1898 xs *glist = xs_glob(spec, 0, 0); 1899 xs_list *p; 1900 const xs_str *v; 1901 xs_list *list = xs_list_new(); 1902 1903 /* iterate the list of files */ 1904 p = glist; 1905 while (xs_list_iter(&p, &v)) { 1906 FILE *f; 1907 1908 /* load the follower data */ 1909 if ((f = fopen(v, "r")) != NULL) { 1910 xs *o = xs_json_load(f); 1911 fclose(f); 1912 1913 if (o != NULL) { 1914 const char *type = xs_dict_get(o, "type"); 1915 1916 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { 1917 const char *actor = xs_dict_get(o, "actor"); 1918 1919 if (!xs_is_null(actor)) { 1920 list = xs_list_append(list, actor); 1921 1922 /* check if there is a link to the actor object */ 1923 xs *v2 = xs_replace(v, ".json", "_a.json"); 1924 1925 if (mtime(v2) == 0.0) { 1926 /* no; add a link to it */ 1927 xs *actor_fn = _object_fn(actor); 1928 if (link(actor_fn, v2) == -1) { 1929 fprintf(stderr, "Actor link creation failed: %s\n", strerror(errno)); 1930 } 1931 } 1932 } 1933 } 1934 } 1935 } 1936 } 1937 1938 return list; 1939 } 1940 1941 1942 xs_str *_muted_fn(snac *snac, const char *actor) 1943 { 1944 const char *md5 = xs_md5(actor); 1945 return xs_fmt("%s/muted/%s", snac->basedir, md5); 1946 } 1947 1948 1949 void mute(snac *snac, const char *actor) 1950 /* mutes a moron */ 1951 { 1952 xs *fn = _muted_fn(snac, actor); 1953 FILE *f; 1954 1955 if ((f = fopen(fn, "w")) != NULL) { 1956 fprintf(f, "%s\n", actor); 1957 fclose(f); 1958 1959 snac_debug(snac, 2, xs_fmt("muted %s %s", actor, fn)); 1960 } 1961 } 1962 1963 1964 void unmute(snac *snac, const char *actor) 1965 /* actor is no longer a moron */ 1966 { 1967 xs *fn = _muted_fn(snac, actor); 1968 1969 unlink(fn); 1970 1971 snac_debug(snac, 2, xs_fmt("unmuted %s %s", actor, fn)); 1972 } 1973 1974 1975 int is_muted(snac *snac, const char *actor) 1976 /* check if someone is muted */ 1977 { 1978 xs *fn = _muted_fn(snac, actor); 1979 1980 return !!(mtime(fn) != 0.0); 1981 } 1982 1983 1984 xs_list *muted_list(snac *user) 1985 /* returns the list (actor URLs) of the muted morons */ 1986 { 1987 xs_list *l = xs_list_new(); 1988 xs *spec = xs_fmt("%s/muted/" "*", user->basedir); 1989 xs *files = xs_glob(spec, 0, 0); 1990 const char *fn; 1991 1992 xs_list_foreach(files, fn) { 1993 FILE *f; 1994 1995 if ((f = fopen(fn, "r")) != NULL) { 1996 xs *actor = xs_strip_i(xs_readline(f)); 1997 fclose(f); 1998 1999 l = xs_list_append(l, actor); 2000 } 2001 } 2002 2003 return l; 2004 } 2005 2006 2007 /** bookmarking **/ 2008 2009 int is_bookmarked(snac *user, const char *id) 2010 /* returns true if this note is bookmarked */ 2011 { 2012 return object_user_cache_in(user, id, "bookmark"); 2013 } 2014 2015 2016 int bookmark(snac *user, const char *id) 2017 /* bookmarks a post */ 2018 { 2019 if (is_bookmarked(user, id)) 2020 return -3; 2021 2022 return object_user_cache_add(user, id, "bookmark"); 2023 } 2024 2025 2026 int unbookmark(snac *user, const char *id) 2027 /* unbookmarks a post */ 2028 { 2029 return object_user_cache_del(user, id, "bookmark"); 2030 } 2031 2032 2033 xs_list *bookmark_list(snac *user) 2034 /* return the lists of bookmarked posts */ 2035 { 2036 return object_user_cache_list(user, "bookmark", XS_ALL, 1); 2037 } 2038 2039 2040 xs_str *bookmark_index_fn(snac *user) 2041 { 2042 return object_user_cache_index_fn(user, "bookmark"); 2043 } 2044 2045 2046 /** pinning **/ 2047 2048 int is_pinned(snac *user, const char *id) 2049 /* returns true if this note is pinned */ 2050 { 2051 return object_user_cache_in(user, id, "pinned"); 2052 } 2053 2054 2055 int is_pinned_by_md5(snac *user, const char *md5) 2056 { 2057 return object_user_cache_in_by_md5(user, md5, "pinned"); 2058 } 2059 2060 2061 int pin(snac *user, const char *id) 2062 /* pins a message */ 2063 { 2064 int ret = -2; 2065 2066 if (xs_startswith(id, user->actor)) { 2067 if (is_pinned(user, id)) 2068 ret = -3; 2069 else 2070 ret = object_user_cache_add(user, id, "pinned"); 2071 } 2072 2073 return ret; 2074 } 2075 2076 2077 int unpin(snac *user, const char *id) 2078 /* unpin a message */ 2079 { 2080 return object_user_cache_del(user, id, "pinned"); 2081 } 2082 2083 2084 xs_list *pinned_list(snac *user) 2085 /* return the lists of pinned posts */ 2086 { 2087 return object_user_cache_list(user, "pinned", XS_ALL, 1); 2088 } 2089 2090 2091 /** drafts **/ 2092 2093 int is_draft(snac *user, const char *id) 2094 /* returns true if this note is a draft */ 2095 { 2096 return object_user_cache_in(user, id, "draft"); 2097 } 2098 2099 2100 void draft_del(snac *user, const char *id) 2101 /* delete a message from the draft cache */ 2102 { 2103 object_user_cache_del(user, id, "draft"); 2104 } 2105 2106 2107 void draft_add(snac *user, const char *id, const xs_dict *msg) 2108 /* store the message as a draft */ 2109 { 2110 /* delete from the index, in case it was already there */ 2111 draft_del(user, id); 2112 2113 /* overwrite object */ 2114 object_add_ow(id, msg); 2115 2116 /* [re]add to the index */ 2117 object_user_cache_add(user, id, "draft"); 2118 } 2119 2120 2121 xs_list *draft_list(snac *user) 2122 /* return the lists of drafts */ 2123 { 2124 return object_user_cache_list(user, "draft", XS_ALL, 1); 2125 } 2126 2127 2128 /** scheduled posts **/ 2129 2130 int is_scheduled(snac *user, const char *id) 2131 /* returns true if this note is scheduled for future sending */ 2132 { 2133 return object_user_cache_in(user, id, "sched"); 2134 } 2135 2136 2137 void schedule_del(snac *user, const char *id) 2138 /* deletes an scheduled post */ 2139 { 2140 object_user_cache_del(user, id, "sched"); 2141 } 2142 2143 2144 void schedule_add(snac *user, const char *id, const xs_dict *msg) 2145 /* schedules this post for later */ 2146 { 2147 /* delete from the index, in case it was already there */ 2148 schedule_del(user, id); 2149 2150 /* overwrite object */ 2151 object_add_ow(id, msg); 2152 2153 /* [re]add to the index */ 2154 object_user_cache_add(user, id, "sched"); 2155 } 2156 2157 2158 xs_list *scheduled_list(snac *user) 2159 /* return the list of scheduled posts */ 2160 { 2161 return object_user_cache_list(user, "sched", XS_ALL, 1); 2162 } 2163 2164 2165 void scheduled_process(snac *user) 2166 /* processes the scheduled list, sending those ready to be sent */ 2167 { 2168 xs *posts = scheduled_list(user); 2169 const char *md5; 2170 xs *right_now = xs_str_utctime(0, ISO_DATE_SPEC); 2171 2172 xs_list_foreach(posts, md5) { 2173 xs *msg = NULL; 2174 2175 if (valid_status(object_get_by_md5(md5, &msg))) { 2176 if (strcmp(xs_dict_get(msg, "published"), right_now) < 0) { 2177 /* due date! */ 2178 const char *id = xs_dict_get(msg, "id"); 2179 2180 timeline_add(user, id, msg); 2181 2182 xs *c_msg = msg_create(user, msg); 2183 enqueue_message(user, c_msg); 2184 2185 schedule_del(user, id); 2186 } 2187 } 2188 } 2189 } 2190 2191 2192 /** hiding **/ 2193 2194 xs_str *_hidden_fn(snac *snac, const char *id) 2195 { 2196 const char *md5 = xs_md5(id); 2197 return xs_fmt("%s/hidden/%s", snac->basedir, md5); 2198 } 2199 2200 2201 void hide(snac *snac, const char *id) 2202 /* hides a message tree */ 2203 { 2204 xs *fn = _hidden_fn(snac, id); 2205 FILE *f; 2206 2207 if ((f = fopen(fn, "w")) != NULL) { 2208 fprintf(f, "%s\n", id); 2209 fclose(f); 2210 2211 snac_debug(snac, 2, xs_fmt("hidden %s %s", id, fn)); 2212 2213 /* hide all the children */ 2214 xs *chld = object_children(id); 2215 char *p; 2216 const char *v; 2217 2218 p = chld; 2219 while (xs_list_iter(&p, &v)) { 2220 xs *co = NULL; 2221 2222 /* resolve to get the id */ 2223 if (valid_status(object_get_by_md5(v, &co))) { 2224 const char *id = xs_dict_get(co, "id"); 2225 if (id != NULL) 2226 hide(snac, id); 2227 } 2228 } 2229 } 2230 } 2231 2232 2233 int is_hidden(snac *snac, const char *id) 2234 /* check is id is hidden */ 2235 { 2236 xs *fn = _hidden_fn(snac, id); 2237 2238 return !!(mtime(fn) != 0.0); 2239 } 2240 2241 2242 int actor_add(const char *actor, const xs_dict *msg) 2243 /* adds an actor */ 2244 { 2245 return object_add_ow(actor, msg); 2246 } 2247 2248 2249 int actor_get(const char *actor, xs_dict **data) 2250 /* returns an already downloaded actor */ 2251 { 2252 int status = HTTP_STATUS_OK; 2253 xs_dict *d = NULL; 2254 2255 if (xs_startswith(actor, srv_baseurl)) { 2256 /* it's a (possible) local user */ 2257 xs *l = xs_split(actor, "/"); 2258 const char *uid = xs_list_get(l, -1); 2259 snac user; 2260 2261 if (!xs_is_null(uid) && user_open(&user, uid)) { 2262 if (data) 2263 *data = msg_actor(&user); 2264 2265 user_free(&user); 2266 return HTTP_STATUS_OK; 2267 } 2268 else 2269 return HTTP_STATUS_NOT_FOUND; 2270 } 2271 2272 /* read the object */ 2273 if (!valid_status(status = object_get(actor, &d))) { 2274 d = xs_free(d); 2275 return status; 2276 } 2277 2278 /* if the object is corrupted, discard it */ 2279 if (xs_is_null(xs_dict_get(d, "id")) || xs_is_null(xs_dict_get(d, "type"))) { 2280 srv_debug(1, xs_fmt("corrupted actor object %s", actor)); 2281 d = xs_free(d); 2282 return HTTP_STATUS_NOT_FOUND; 2283 } 2284 2285 if (data) 2286 *data = d; 2287 else 2288 d = xs_free(d); 2289 2290 xs *fn = _object_fn(actor); 2291 double max_time; 2292 2293 /* maximum time for the actor data to be considered stale */ 2294 max_time = 3600.0 * 36.0; 2295 2296 if (mtime(fn) + max_time < (double) time(NULL)) { 2297 /* actor data exists but also stinks */ 2298 status = HTTP_STATUS_RESET_CONTENT; /* "110: Response Is Stale" */ 2299 } 2300 2301 return status; 2302 } 2303 2304 2305 int actor_get_refresh(snac *user, const char *actor, xs_dict **data) 2306 /* gets an actor and requests a refresh if it's stale */ 2307 { 2308 int status = actor_get(actor, data); 2309 2310 if (status == HTTP_STATUS_RESET_CONTENT && user && !xs_startswith(actor, srv_baseurl)) 2311 enqueue_actor_refresh(user, actor, 0); 2312 2313 return status; 2314 } 2315 2316 2317 /** user limiting (announce blocks) **/ 2318 2319 int limited(snac *user, const char *id, int cmd) 2320 /* announce messages from a followed (0: check, 1: limit; 2: unlimit) */ 2321 { 2322 int ret = 0; 2323 xs *dir = xs_fmt("%s/limited", user->basedir); 2324 const char *md5 = xs_md5(id); 2325 xs *fn = xs_fmt("%s/%s", dir, md5); 2326 2327 switch (cmd) { 2328 case 0: /** check **/ 2329 ret = !!(mtime(fn) > 0.0); 2330 break; 2331 2332 case 1: /** limit **/ 2333 mkdirx(dir); 2334 2335 if (mtime(fn) > 0.0) 2336 ret = -1; 2337 else { 2338 FILE *f; 2339 2340 if ((f = fopen(fn, "w")) != NULL) { 2341 fprintf(f, "%s\n", id); 2342 fclose(f); 2343 } 2344 else 2345 ret = -2; 2346 } 2347 break; 2348 2349 case 2: /** unlimit **/ 2350 if (mtime(fn) > 0.0) 2351 ret = unlink(fn); 2352 else 2353 ret = -1; 2354 break; 2355 } 2356 2357 return ret; 2358 } 2359 2360 2361 /** tag indexing **/ 2362 2363 void tag_index(const char *id, const xs_dict *obj) 2364 /* update the tag indexes for this object */ 2365 { 2366 const xs_list *tags = xs_dict_get(obj, "tag"); 2367 const char *md5_id = xs_md5(id); 2368 2369 if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { 2370 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); 2371 2372 mkdirx(g_tag_dir); 2373 2374 const xs_dict *v; 2375 int ct = 0; 2376 while (xs_list_next(tags, &v, &ct)) { 2377 const char *type = xs_dict_get(v, "type"); 2378 const char *name = xs_dict_get(v, "name"); 2379 2380 if (!xs_is_null(type) && !xs_is_null(name) && strcmp(type, "Hashtag") == 0) { 2381 while (*name == '#' || *name == '@') 2382 name++; 2383 2384 if (*name == '\0') 2385 continue; 2386 2387 name = xs_utf8_to_lower((xs_str *)name); 2388 2389 const char *md5_tag = xs_md5(name); 2390 xs *tag_dir = xs_fmt("%s/%c%c", g_tag_dir, md5_tag[0], md5_tag[1]); 2391 mkdirx(tag_dir); 2392 2393 xs *g_tag_idx = xs_fmt("%s/%s.idx", tag_dir, md5_tag); 2394 2395 if (!index_in_md5(g_tag_idx, md5_id)) 2396 index_add_md5(g_tag_idx, md5_id); 2397 2398 FILE *f; 2399 xs *g_tag_name = xs_replace(g_tag_idx, ".idx", ".tag"); 2400 if ((f = fopen(g_tag_name, "w")) != NULL) { 2401 fprintf(f, "%s\n", name); 2402 fclose(f); 2403 } 2404 2405 srv_debug(1, xs_fmt("tagged %s #%s (#%s)", id, name, md5_tag)); 2406 } 2407 } 2408 } 2409 } 2410 2411 2412 xs_str *tag_fn(const char *tag) 2413 { 2414 if (*tag == '#') 2415 tag++; 2416 2417 xs *lw_tag = xs_utf8_to_lower(tag); 2418 const char *md5 = xs_md5(lw_tag); 2419 2420 return xs_fmt("%s/tag/%c%c/%s.idx", srv_basedir, md5[0], md5[1], md5); 2421 } 2422 2423 2424 xs_list *tag_search(const char *tag, int skip, int show) 2425 /* returns the list of posts tagged with tag */ 2426 { 2427 xs *idx = tag_fn(tag); 2428 2429 return index_list_desc(idx, skip, show); 2430 } 2431 2432 2433 /** lists **/ 2434 2435 xs_val *list_maint(snac *user, const char *list, int op) 2436 /* list maintenance */ 2437 { 2438 xs_val *l = NULL; 2439 2440 switch (op) { 2441 case 0: /** list of lists **/ 2442 { 2443 FILE *f; 2444 xs *spec = xs_fmt("%s/list/" "*.id", user->basedir); 2445 xs *ls = xs_glob(spec, 0, 0); 2446 int c = 0; 2447 const char *v; 2448 2449 l = xs_list_new(); 2450 2451 while (xs_list_next(ls, &v, &c)) { 2452 if ((f = fopen(v, "r")) != NULL) { 2453 xs *title = xs_readline(f); 2454 fclose(f); 2455 2456 title = xs_strip_i(title); 2457 2458 xs *v2 = xs_replace(v, ".id", ""); 2459 xs *l2 = xs_split(v2, "/"); 2460 2461 /* return [ list_id, list_title ] */ 2462 xs *tmp_list = xs_list_append(xs_list_new(), xs_list_get(l2, -1), title); 2463 l = xs_list_append(l, tmp_list); 2464 } 2465 } 2466 } 2467 2468 break; 2469 2470 case 1: /** create new list (list is the name) **/ 2471 { 2472 xs *lol = list_maint(user, NULL, 0); 2473 int c = 0; 2474 const xs_list *v; 2475 int add = 1; 2476 2477 /* check if this list name already exists */ 2478 while (xs_list_next(lol, &v, &c)) { 2479 if (strcmp(xs_list_get(v, 1), list) == 0) { 2480 add = 0; 2481 2482 l = xs_dup(xs_list_get(v, 0)); 2483 2484 break; 2485 } 2486 } 2487 2488 if (add) { 2489 FILE *f; 2490 xs *dir = xs_fmt("%s/list/", user->basedir); 2491 2492 l = xs_fmt("%010x", time(NULL)); 2493 2494 mkdirx(dir); 2495 2496 xs *fn = xs_fmt("%s%s.id", dir, l); 2497 2498 if ((f = fopen(fn, "w")) != NULL) { 2499 fprintf(f, "%s\n", list); 2500 fclose(f); 2501 } 2502 } 2503 } 2504 2505 break; 2506 2507 case 2: /** delete list (list is the id) **/ 2508 { 2509 if (xs_is_hex(list)) { 2510 xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); 2511 unlink(fn); 2512 2513 fn = xs_replace_i(fn, ".id", ".lst"); 2514 unlink(fn); 2515 2516 fn = xs_replace_i(fn, ".lst", ".idx"); 2517 unlink(fn); 2518 2519 fn = xs_str_cat(fn, ".bak"); 2520 unlink(fn); 2521 } 2522 } 2523 2524 break; 2525 2526 case 3: /** get list name **/ 2527 if (xs_is_hex(list)) { 2528 FILE *f; 2529 xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); 2530 2531 if ((f = fopen(fn, "r")) != NULL) { 2532 l = xs_strip_i(xs_readline(f)); 2533 fclose(f); 2534 } 2535 } 2536 2537 break; 2538 2539 case 4: /** find list id by name **/ 2540 if (xs_is_string(list)) { 2541 xs *lol = list_maint(user, NULL, 0); 2542 const xs_list *li; 2543 2544 xs_list_foreach(lol, li) { 2545 if (strcmp(list, xs_list_get(li, 1)) == 0) { 2546 l = xs_dup(xs_list_get(li, 0)); 2547 break; 2548 } 2549 } 2550 } 2551 } 2552 2553 return l; 2554 } 2555 2556 2557 xs_str *list_timeline_fn(snac *user, const char *list) 2558 { 2559 return xs_fmt("%s/list/%s.idx", user->basedir, list); 2560 } 2561 2562 2563 xs_list *list_timeline(snac *user, const char *list, int skip, int show) 2564 /* returns the timeline of a list */ 2565 { 2566 xs_list *l = NULL; 2567 2568 if (!xs_is_hex(list)) 2569 return NULL; 2570 2571 xs *fn = list_timeline_fn(user, list); 2572 2573 if (mtime(fn) > 0.0) 2574 l = index_list_desc(fn, skip, show); 2575 else 2576 l = xs_list_new(); 2577 2578 return l; 2579 } 2580 2581 2582 xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op) 2583 /* list content management */ 2584 { 2585 xs_val *l = NULL; 2586 2587 if (!xs_is_hex(list)) 2588 return NULL; 2589 2590 if (actor_md5 != NULL && !xs_is_hex(actor_md5)) 2591 return NULL; 2592 2593 xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list); 2594 2595 switch (op) { 2596 case 0: /** list content **/ 2597 l = index_list(fn, XS_ALL); 2598 2599 break; 2600 2601 case 1: /** append actor to list **/ 2602 if (xs_is_string(actor_md5) && xs_is_hex(actor_md5)) { 2603 if (!index_in_md5(fn, actor_md5)) 2604 index_add_md5(fn, actor_md5); 2605 } 2606 2607 break; 2608 2609 case 2: /** delete actor from list **/ 2610 if (xs_is_string(actor_md5) && xs_is_hex(actor_md5)) 2611 index_del_md5(fn, actor_md5); 2612 2613 break; 2614 2615 default: 2616 srv_log(xs_fmt("ERROR: list_content: bad op %d", op)); 2617 break; 2618 } 2619 2620 return l; 2621 } 2622 2623 2624 void list_distribute(snac *user, const char *who, const xs_dict *post) 2625 /* distributes the post to all appropriate lists */ 2626 { 2627 const char *id = xs_dict_get(post, "id"); 2628 2629 /* if who is not set, use the attributedTo in the message */ 2630 if (xs_is_null(who)) 2631 who = get_atto(post); 2632 2633 if (xs_type(who) == XSTYPE_STRING && xs_type(id) == XSTYPE_STRING) { 2634 const char *a_md5 = xs_md5(who); 2635 const char *i_md5 = xs_md5(id); 2636 xs *spec = xs_fmt("%s/list/" "*.lst", user->basedir); 2637 xs *ls = xs_glob(spec, 0, 0); 2638 int c = 0; 2639 const char *v; 2640 2641 while (xs_list_next(ls, &v, &c)) { 2642 /* is the actor in this list? */ 2643 if (index_in_md5(v, a_md5)) { 2644 /* it is; add post md5 to its timeline */ 2645 xs *idx = xs_replace(v, ".lst", ".idx"); 2646 index_add_md5(idx, i_md5); 2647 2648 snac_debug(user, 1, xs_fmt("listed post %s in %s", id, idx)); 2649 } 2650 } 2651 } 2652 } 2653 2654 2655 /** static data **/ 2656 2657 static int _load_raw_file(FILE *f, xs_val **data, int *size, 2658 const char *inm, xs_str **etag, int *mmapped) 2659 /* loads a cached file */ 2660 { 2661 int status = HTTP_STATUS_NOT_FOUND; 2662 *mmapped = 0; 2663 2664 if (f) { 2665 double tm = mtime_f(f); 2666 2667 if (tm > 0.0) { 2668 /* file exists; build the etag */ 2669 xs *e = xs_fmt("W/\"snac-%.0lf\"", tm); 2670 2671 /* if if-none-match is set, check if it's the same */ 2672 if (!xs_is_null(inm) && strcmp(e, inm) == 0) { 2673 /* client has the newest version */ 2674 status = HTTP_STATUS_NOT_MODIFIED; 2675 } 2676 else { 2677 struct stat st; 2678 if (fstat(fileno(f), &st) == 0 && (*data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fileno(f), 0)) != MAP_FAILED) { 2679 *mmapped = 1; 2680 *size = st.st_size; 2681 } 2682 else { 2683 *size = XS_ALL; 2684 *data = xs_read(f, size); 2685 } 2686 /* newer or never downloaded; read the full file */ 2687 status = HTTP_STATUS_OK; 2688 } 2689 2690 /* if caller wants the etag, return it */ 2691 if (etag != NULL) 2692 *etag = xs_dup(e); 2693 } 2694 fclose(f); 2695 } 2696 2697 return status; 2698 } 2699 2700 static int _load_raw_file_partial(const char *fn, xs_val **data, int *size, 2701 const char *inm, xs_str **etag, int start, int *end) 2702 /* loads a cached file */ 2703 { 2704 int status = HTTP_STATUS_NOT_FOUND; 2705 2706 if (fn) { 2707 double tm = mtime(fn); 2708 2709 if (tm > 0.0) { 2710 /* file exists; build the etag */ 2711 xs *e = xs_fmt("W/\"snac-%.0lf\"", tm); 2712 2713 /* if if-none-match is set, check if it's the same */ 2714 if (!xs_is_null(inm) && strcmp(e, inm) == 0) { 2715 /* client has the newest version */ 2716 status = HTTP_STATUS_NOT_MODIFIED; 2717 } 2718 else { 2719 /* newer or never downloaded; read the full file */ 2720 FILE *f; 2721 2722 if ((f = fopen(fn, "rb")) != NULL) { 2723 struct stat st; 2724 int n; 2725 if (fstat(fileno(f), &st) == 0) { 2726 *size = st.st_size; 2727 if (*end == XS_ALL) 2728 *end = *size - 1; 2729 } else { 2730 *size = XS_ALL; 2731 } 2732 2733 n = *end == XS_ALL ? XS_ALL : *end - start + 1; 2734 2735 if (*end != XS_ALL && *end > *size) 2736 status = HTTP_STATUS_RANGE_NOT_SATISFIABLE; 2737 else 2738 if (start > *size || fseek(f, start, SEEK_SET) != 0) 2739 status = HTTP_STATUS_RANGE_NOT_SATISFIABLE; 2740 else 2741 *data = xs_read(f, &n); 2742 2743 *end = (n + start - 1); 2744 fclose(f); 2745 2746 if (status == HTTP_STATUS_NOT_FOUND) 2747 status = HTTP_STATUS_PARTIAL_CONTENT; 2748 } 2749 } 2750 2751 /* if caller wants the etag, return it */ 2752 if (etag != NULL) 2753 *etag = xs_dup(e); 2754 2755 srv_debug(1, xs_fmt("_load_raw_file(): %s %d", fn, status)); 2756 } 2757 } 2758 2759 return status; 2760 } 2761 2762 2763 xs_str *_static_fn(snac *snac, const char *id) 2764 /* gets the filename for a static file */ 2765 { 2766 if (strchr(id, '/')) 2767 return NULL; 2768 else 2769 return xs_fmt("%s/static/%s", snac->basedir, id); 2770 } 2771 2772 2773 int static_get(snac *snac, const char *id, xs_val **data, int *size, 2774 const char *inm, xs_str **etag, int *mmapped) 2775 /* returns static content */ 2776 { 2777 return _load_raw_file(user_open_subfile(snac, "static", id, 0), data, size, inm, etag, mmapped); 2778 } 2779 2780 int static_get_partial(snac *snac, const char *id, xs_val **data, int *size, 2781 const char *inm, xs_str **etag, int start, int *end) 2782 /* returns static content */ 2783 { 2784 xs *fn = _static_fn(snac, id); 2785 2786 return _load_raw_file_partial(fn, data, size, inm, etag, start, end); 2787 } 2788 2789 2790 void static_put(snac *snac, const char *id, const char *data, int size) 2791 /* writes status content */ 2792 { 2793 xs *fn = _static_fn(snac, id); 2794 FILE *f; 2795 2796 if (fn && (f = fopen(fn, "wb")) != NULL) { 2797 fwrite(data, size, 1, f); 2798 fclose(f); 2799 } 2800 } 2801 2802 2803 void static_put_meta(snac *snac, const char *id, const char *str) 2804 /* puts metadata (i.e. a media description string) to id */ 2805 { 2806 xs *fn = _static_fn(snac, id); 2807 2808 if (fn) { 2809 fn = xs_str_cat(fn, ".txt"); 2810 FILE *f; 2811 2812 if ((f = fopen(fn, "w")) != NULL) { 2813 fprintf(f, "%s\n", str); 2814 fclose(f); 2815 } 2816 } 2817 } 2818 2819 2820 xs_str *static_get_meta(snac *snac, const char *id) 2821 /* gets metadata from a media */ 2822 { 2823 xs *fn = _static_fn(snac, id); 2824 xs_str *r = NULL; 2825 2826 if (fn) { 2827 fn = xs_str_cat(fn, ".txt"); 2828 FILE *f; 2829 2830 if ((f = fopen(fn, "r")) != NULL) { 2831 r = xs_strip_i(xs_readline(f)); 2832 fclose(f); 2833 } 2834 } 2835 else 2836 r = xs_str_new(""); 2837 2838 return r; 2839 } 2840 2841 2842 /** history **/ 2843 2844 xs_str *_history_fn(snac *snac, const char *id) 2845 /* gets the filename for the history */ 2846 { 2847 if (strchr(id, '/')) 2848 return NULL; 2849 else 2850 return xs_fmt("%s/history/%s", snac->basedir, id); 2851 } 2852 2853 2854 double history_mtime(snac *snac, const char *id) 2855 { 2856 double t = 0.0; 2857 xs *fn = _history_fn(snac, id); 2858 2859 if (fn != NULL) 2860 t = mtime(fn); 2861 2862 return t; 2863 } 2864 2865 2866 void history_add(snac *snac, const char *id, const char *content, int size, 2867 xs_str **etag) 2868 /* adds something to the history */ 2869 { 2870 xs *fn = _history_fn(snac, id); 2871 FILE *f; 2872 2873 if (fn && (f = fopen(fn, "w")) != NULL) { 2874 fwrite(content, size, 1, f); 2875 fclose(f); 2876 2877 if (etag) { 2878 double tm = mtime(fn); 2879 *etag = xs_fmt("W/\"snac-%.0lf\"", tm); 2880 } 2881 } 2882 } 2883 2884 2885 int history_get(snac *snac, const char *id, xs_str **content, int *size, 2886 const char *inm, xs_str **etag, int *mmapped) 2887 { 2888 return _load_raw_file(user_open_subfile(snac, "history", id, 0), content, size, inm, etag, mmapped); 2889 } 2890 2891 2892 int history_del(snac *snac, const char *id) 2893 { 2894 xs *fn = _history_fn(snac, id); 2895 2896 if (fn) 2897 return unlink(fn); 2898 else 2899 return -1; 2900 } 2901 2902 2903 xs_list *history_list(snac *snac) 2904 { 2905 xs *spec = xs_fmt("%s/history/" "*.html", snac->basedir); 2906 2907 return xs_glob(spec, 1, 1); 2908 } 2909 2910 2911 void lastlog_write(snac *snac, const char *source) 2912 /* writes the last time the user logged in */ 2913 { 2914 xs *fn = xs_fmt("%s/lastlog.txt", snac->basedir); 2915 FILE *f; 2916 2917 if ((f = fopen(fn, "w")) != NULL) { 2918 fprintf(f, "%lf %s\n", ftime(), source); 2919 fclose(f); 2920 } 2921 } 2922 2923 2924 /** inbox collection **/ 2925 2926 void inbox_add(const char *inbox) 2927 /* collects a shared inbox */ 2928 { 2929 /* don't collect ourselves */ 2930 if (xs_startswith(inbox, srv_baseurl)) 2931 return; 2932 2933 const char *md5 = xs_md5(inbox); 2934 xs *fn = xs_fmt("%s/inbox/%s", srv_basedir, md5); 2935 FILE *f; 2936 2937 if ((f = fopen(fn, "w")) != NULL) { 2938 fprintf(f, "%s\n", inbox); 2939 fclose(f); 2940 } 2941 } 2942 2943 2944 void inbox_add_by_actor(const xs_dict *actor) 2945 /* collects an actor's shared inbox, if it has one */ 2946 { 2947 const char *v; 2948 2949 if (!xs_is_null(v = xs_dict_get(actor, "endpoints")) && 2950 !xs_is_null(v = xs_dict_get(v, "sharedInbox"))) { 2951 /* only collect this inbox if its instance is not blocked */ 2952 if (!is_instance_blocked(v)) 2953 inbox_add(v); 2954 } 2955 } 2956 2957 2958 xs_list *inbox_list(void) 2959 /* returns the collected inboxes as a list */ 2960 { 2961 xs_list *ibl = xs_list_new(); 2962 xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir); 2963 xs *files = xs_glob(spec, 0, 0); 2964 const xs_val *v; 2965 2966 xs_list_foreach(files, v) { 2967 FILE *f; 2968 2969 if ((f = fopen(v, "r")) != NULL) { 2970 xs *line = xs_readline(f); 2971 2972 if (line && *line) { 2973 line = xs_strip_i(line); 2974 2975 if (!is_instance_blocked(line)) 2976 ibl = xs_list_append(ibl, line); 2977 } 2978 2979 fclose(f); 2980 } 2981 } 2982 2983 return ibl; 2984 } 2985 2986 2987 /** instance-wide operations **/ 2988 2989 xs_str *_instance_block_fn(const char *instance) 2990 { 2991 xs *s = xs_replace(instance, "http:/" "/", ""); 2992 xs *s1 = xs_replace(s, "https:/" "/", ""); 2993 xs *l = xs_split(s1, "/"); 2994 const char *p = xs_list_get(l, 0); 2995 const char *md5 = xs_md5(p); 2996 2997 return xs_fmt("%s/block/%s", srv_basedir, md5); 2998 } 2999 3000 3001 int is_instance_blocked(const char *instance) 3002 { 3003 xs *fn = _instance_block_fn(instance); 3004 3005 return !!(mtime(fn) != 0.0); 3006 } 3007 3008 3009 int instance_block(const char *instance) 3010 /* blocks a full instance */ 3011 { 3012 int ret; 3013 3014 /* create the subdir */ 3015 xs *dir = xs_fmt("%s/block/", srv_basedir); 3016 mkdirx(dir); 3017 3018 if (!is_instance_blocked(instance)) { 3019 xs *fn = _instance_block_fn(instance); 3020 FILE *f; 3021 3022 if ((f = fopen(fn, "w")) != NULL) { 3023 fprintf(f, "%s\n", instance); 3024 fclose(f); 3025 3026 ret = 0; 3027 } 3028 else 3029 ret = -1; 3030 } 3031 else 3032 ret = -2; 3033 3034 return ret; 3035 } 3036 3037 3038 int instance_unblock(const char *instance) 3039 /* unblocks a full instance */ 3040 { 3041 int ret; 3042 3043 if (is_instance_blocked(instance)) { 3044 xs *fn = _instance_block_fn(instance); 3045 ret = unlink(fn); 3046 } 3047 else 3048 ret = -2; 3049 3050 return ret; 3051 } 3052 3053 3054 /** operations by content **/ 3055 3056 int content_match(const char *file, const xs_dict *msg) 3057 /* checks if a message's content matches any of the regexes in file */ 3058 /* file format: one regex per line */ 3059 { 3060 xs *fn = xs_fmt("%s/%s", srv_basedir, file); 3061 FILE *f; 3062 int r = 0; 3063 const char *v = xs_dict_get(msg, "content"); 3064 3065 if (xs_type(v) == XSTYPE_STRING && *v) { 3066 if ((f = fopen(fn, "r")) != NULL) { 3067 srv_debug(1, xs_fmt("content_match: loading regexes from %s", fn)); 3068 3069 /* massage content (strip HTML tags, etc.) */ 3070 xs *c1 = xs_regex_replace(v, "<[^>]+>", " "); 3071 c1 = xs_regex_replace_i(c1, " {2,}", " "); 3072 xs *c = xs_utf8_to_lower(c1); 3073 3074 while (!r && !feof(f)) { 3075 xs *rx = xs_strip_i(xs_readline(f)); 3076 3077 if (*rx && xs_regex_match(c, rx)) { 3078 srv_debug(1, xs_fmt("content_match: match for '%s'", rx)); 3079 r = 1; 3080 } 3081 } 3082 3083 fclose(f); 3084 } 3085 } 3086 3087 return r; 3088 } 3089 3090 3091 xs_list *content_search(snac *user, const char *regex, 3092 int priv, int skip, int show, int max_secs, int *timeout) 3093 /* returns a list of posts which content matches the regex */ 3094 { 3095 if (regex == NULL || *regex == '\0') 3096 return xs_list_new(); 3097 3098 xs *i_regex = xs_utf8_to_lower(regex); 3099 3100 xs_set seen; 3101 3102 xs_set_init(&seen); 3103 3104 if (max_secs == 0) 3105 max_secs = 3; 3106 3107 time_t t = time(NULL) + max_secs; 3108 *timeout = 0; 3109 3110 /* iterate all timelines simultaneously */ 3111 xs_list *tls[3] = {0}; 3112 const char *md5s[3] = {0}; 3113 int c[3] = {0}; 3114 3115 tls[0] = timeline_simple_list(user, "public", 0, XS_ALL, NULL); /* public */ 3116 tls[1] = timeline_instance_list(0, XS_ALL); /* instance */ 3117 tls[2] = priv ? timeline_simple_list(user, "private", 0, XS_ALL, NULL) : xs_list_new(); /* private or none */ 3118 3119 /* first positioning */ 3120 for (int n = 0; n < 3; n++) 3121 xs_list_next(tls[n], &md5s[n], &c[n]); 3122 3123 show += skip; 3124 3125 while (show > 0) { 3126 /* timeout? */ 3127 if (time(NULL) > t) { 3128 *timeout = 1; 3129 break; 3130 } 3131 3132 /* find the newest post */ 3133 int newest = -1; 3134 double mtime = 0.0; 3135 3136 for (int n = 0; n < 3; n++) { 3137 if (md5s[n] != NULL) { 3138 xs *fn = _object_fn_by_md5(md5s[n], "content_search"); 3139 double mt; 3140 3141 while ((mt = mtime(fn)) == 0 && md5s[n] != NULL) { 3142 /* object is not here: move to the next one */ 3143 if (xs_list_next(tls[n], &md5s[n], &c[n])) { 3144 xs_free(fn); 3145 fn = _object_fn_by_md5(md5s[n], "content_search_2"); 3146 } 3147 else 3148 md5s[n] = NULL; 3149 } 3150 3151 if (mt > mtime) { 3152 newest = n; 3153 mtime = mt; 3154 } 3155 } 3156 } 3157 3158 if (newest == -1) 3159 break; 3160 3161 const char *md5 = md5s[newest]; 3162 3163 /* advance the chosen timeline */ 3164 if (!xs_list_next(tls[newest], &md5s[newest], &c[newest])) 3165 md5s[newest] = NULL; 3166 3167 xs *post = NULL; 3168 3169 if (!valid_status(object_get_by_md5(md5, &post))) 3170 continue; 3171 3172 if (!xs_match(xs_dict_get_def(post, "type", "-"), POSTLIKE_OBJECT_TYPE)) 3173 continue; 3174 3175 const char *id = xs_dict_get(post, "id"); 3176 3177 if (id == NULL || is_hidden(user, id)) 3178 continue; 3179 3180 /* recalculate the md5 id to be sure it's not repeated 3181 (it may have been searched by the "url" field instead of "id") */ 3182 md5 = xs_md5(id); 3183 3184 /* test for the post URL */ 3185 if (strcmp(id, regex) == 0) { 3186 if (xs_set_add(&seen, md5) == 1) 3187 show--; 3188 3189 continue; 3190 } 3191 3192 /* test for the alternate post id */ 3193 const char *url = xs_dict_get(post, "url"); 3194 if (xs_type(url) == XSTYPE_STRING && strcmp(url, regex) == 0) { 3195 if (xs_set_add(&seen, md5) == 1) 3196 show--; 3197 3198 continue; 3199 } 3200 3201 xs *c = xs_str_new(NULL); 3202 const char *content = xs_dict_get(post, "content"); 3203 const char *name = xs_dict_get(post, "name"); 3204 3205 if (!xs_is_null(content)) 3206 c = xs_str_cat(c, content); 3207 if (!xs_is_null(name)) 3208 c = xs_str_cat(c, " ", name); 3209 3210 /* add alt-texts from attachments */ 3211 const xs_list *atts = xs_dict_get(post, "attachment"); 3212 int tc = 0; 3213 const xs_dict *att; 3214 3215 while (xs_list_next(atts, &att, &tc)) { 3216 const char *name = xs_dict_get(att, "name"); 3217 3218 if (name != NULL) 3219 c = xs_str_cat(c, " ", name); 3220 } 3221 3222 /* strip HTML */ 3223 c = xs_regex_replace_i(c, "<[^>]+>", " "); 3224 c = xs_regex_replace_i(c, " {2,}", " "); 3225 3226 /* convert to lowercase */ 3227 xs *lc = xs_utf8_to_lower(c); 3228 3229 /* apply regex */ 3230 if (xs_regex_match(lc, i_regex)) { 3231 if (xs_set_add(&seen, md5) == 1) 3232 show--; 3233 } 3234 } 3235 3236 xs_list *r = xs_set_result(&seen); 3237 3238 if (skip) { 3239 /* BAD */ 3240 while (skip--) { 3241 r = xs_list_del(r, 0); 3242 } 3243 } 3244 3245 xs_free(tls[0]); 3246 xs_free(tls[1]); 3247 xs_free(tls[2]); 3248 3249 return r; 3250 } 3251 3252 3253 /** notifications **/ 3254 3255 xs_str *notify_check_time(snac *snac, int reset) 3256 /* gets or resets the latest notification check time */ 3257 { 3258 xs_str *t = NULL; 3259 xs *fn = xs_fmt("%s/notifydate.txt", snac->basedir); 3260 FILE *f; 3261 3262 if (reset) { 3263 if ((f = fopen(fn, "w")) != NULL) { 3264 t = tid(0); 3265 fprintf(f, "%s\n", t); 3266 fclose(f); 3267 } 3268 } 3269 else { 3270 if ((f = fopen(fn, "r")) != NULL) { 3271 t = xs_readline(f); 3272 fclose(f); 3273 } 3274 else 3275 /* never set before */ 3276 t = xs_fmt("%16.6f", 0.0); 3277 } 3278 3279 return t; 3280 } 3281 3282 xs_dict *markers_get(snac *snac, const xs_list *markers) 3283 { 3284 xs *data = NULL; 3285 xs_dict *returns = xs_dict_new(); 3286 xs *fn = xs_fmt("%s/markers.json", snac->basedir); 3287 const xs_str *v = NULL; 3288 FILE *f; 3289 3290 if ((f = fopen(fn, "r")) != NULL) { 3291 data = xs_json_load(f); 3292 fclose(f); 3293 } 3294 3295 if (xs_is_null(data)) 3296 data = xs_dict_new(); 3297 3298 xs_list_foreach(markers, v) { 3299 const xs_dict *mark = xs_dict_get(data, v); 3300 if (!xs_is_null(mark)) { 3301 returns = xs_dict_append(returns, v, mark); 3302 } 3303 } 3304 return returns; 3305 } 3306 3307 xs_dict *markers_set(snac *snac, const char *home_marker, const char *notify_marker) 3308 /* gets or sets notification marker */ 3309 { 3310 xs *data = NULL; 3311 xs_dict *written = xs_dict_new(); 3312 xs *fn = xs_fmt("%s/markers.json", snac->basedir); 3313 FILE *f; 3314 3315 if ((f = fopen(fn, "r")) != NULL) { 3316 data = xs_json_load(f); 3317 fclose(f); 3318 } 3319 3320 if (xs_is_null(data)) 3321 data = xs_dict_new(); 3322 3323 if (!xs_is_null(home_marker)) { 3324 xs *home = xs_dict_new(); 3325 xs *s_tid = tid(0); 3326 home = xs_dict_append(home, "last_read_id", home_marker); 3327 home = xs_dict_append(home, "version", xs_stock(0)); 3328 home = xs_dict_append(home, "updated_at", s_tid); 3329 data = xs_dict_set(data, "home", home); 3330 written = xs_dict_append(written, "home", home); 3331 } 3332 3333 if (!xs_is_null(notify_marker)) { 3334 xs *notify = xs_dict_new(); 3335 xs *s_tid = tid(0); 3336 notify = xs_dict_append(notify, "last_read_id", notify_marker); 3337 notify = xs_dict_append(notify, "version", xs_stock(0)); 3338 notify = xs_dict_append(notify, "updated_at", s_tid); 3339 data = xs_dict_set(data, "notifications", notify); 3340 written = xs_dict_append(written, "notifications", notify); 3341 } 3342 3343 if ((f = fopen(fn, "w")) != NULL) { 3344 xs_json_dump(data, 4, f); 3345 fclose(f); 3346 } 3347 3348 return written; 3349 } 3350 3351 void notify_add(snac *snac, const char *type, const char *utype, 3352 const char *actor, const char *objid, const xs_dict *msg) 3353 /* adds a new notification */ 3354 { 3355 xs *ntid = tid(0); 3356 xs *fn = xs_fmt("%s/notify/", snac->basedir); 3357 xs *date = xs_str_utctime(0, ISO_DATE_SPEC); 3358 FILE *f; 3359 3360 /* create the directory */ 3361 mkdirx(fn); 3362 fn = xs_str_cat(fn, ntid); 3363 fn = xs_str_cat(fn, ".json"); 3364 3365 xs *noti = xs_dict_new(); 3366 3367 noti = xs_dict_append(noti, "id", ntid); 3368 noti = xs_dict_append(noti, "type", type); 3369 noti = xs_dict_append(noti, "utype", utype); 3370 noti = xs_dict_append(noti, "actor", actor); 3371 noti = xs_dict_append(noti, "date", date); 3372 noti = xs_dict_append(noti, "msg", msg); 3373 3374 if (!xs_is_null(objid)) 3375 noti = xs_dict_append(noti, "objid", objid); 3376 3377 if ((f = fopen(fn, "w")) != NULL) { 3378 xs_json_dump(noti, 4, f); 3379 fclose(f); 3380 } 3381 3382 /* add it to the index if it already exists */ 3383 xs *idx = xs_fmt("%s/notify.idx", snac->basedir); 3384 3385 if (mtime(idx) != 0.0) { 3386 pthread_mutex_lock(&data_mutex); 3387 3388 if ((f = fopen(idx, "a")) != NULL) { 3389 fprintf(f, "%-32s\n", ntid); 3390 fclose(f); 3391 } 3392 3393 pthread_mutex_unlock(&data_mutex); 3394 } 3395 3396 if (!xs_is_true(xs_dict_get(srv_config, "disable_notify_webhook"))) 3397 enqueue_notify_webhook(snac, noti, 0); 3398 } 3399 3400 3401 xs_dict *notify_get(snac *snac, const char *id) 3402 /* gets a notification */ 3403 { 3404 /* base file */ 3405 xs *fn = xs_fmt("%s/notify/%s", snac->basedir, id); 3406 3407 /* strip spaces and add extension */ 3408 fn = xs_strip_i(fn); 3409 fn = xs_str_cat(fn, ".json"); 3410 3411 FILE *f; 3412 xs_dict *out = NULL; 3413 3414 if ((f = fopen(fn, "r")) != NULL) { 3415 out = xs_json_load(f); 3416 fclose(f); 3417 } 3418 3419 return out; 3420 } 3421 3422 3423 xs_list *notify_list(snac *snac, int skip, int show) 3424 /* returns a list of notification ids */ 3425 { 3426 xs *idx = xs_fmt("%s/notify.idx", snac->basedir); 3427 3428 if (mtime(idx) == 0.0) { 3429 /* create the index from scratch */ 3430 FILE *f; 3431 3432 pthread_mutex_lock(&data_mutex); 3433 3434 if ((f = fopen(idx, "w")) != NULL) { 3435 xs *spec = xs_fmt("%s/notify/" "*.json", snac->basedir); 3436 xs *lst = xs_glob(spec, 1, 0); 3437 xs_list *p = lst; 3438 const char *v; 3439 3440 while (xs_list_iter(&p, &v)) { 3441 char *p = strrchr(v, '.'); 3442 if (p) { 3443 *p = '\0'; 3444 fprintf(f, "%-32s\n", v); 3445 } 3446 } 3447 3448 fclose(f); 3449 } 3450 3451 pthread_mutex_unlock(&data_mutex); 3452 } 3453 3454 return index_list_desc(idx, skip, show); 3455 } 3456 3457 3458 int notify_new_num(snac *snac) 3459 /* counts the number of new notifications */ 3460 { 3461 xs *t = notify_check_time(snac, 0); 3462 xs *lst = notify_list(snac, 0, XS_ALL); 3463 int cnt = 0; 3464 3465 xs_list *p = lst; 3466 const xs_str *v; 3467 3468 while (xs_list_iter(&p, &v)) { 3469 xs *id = xs_strip_i(xs_dup(v)); 3470 3471 /* old? count no more */ 3472 if (strcmp(id, t) < 0) 3473 break; 3474 3475 cnt++; 3476 } 3477 3478 return cnt; 3479 } 3480 3481 3482 void notify_clear(snac *snac) 3483 /* clears all notifications */ 3484 { 3485 xs *spec = xs_fmt("%s/notify/" "*", snac->basedir); 3486 xs *lst = xs_glob(spec, 0, 0); 3487 xs_list *p = lst; 3488 const xs_str *v; 3489 3490 while (xs_list_iter(&p, &v)) 3491 unlink(v); 3492 3493 xs *idx = xs_fmt("%s/notify.idx", snac->basedir); 3494 3495 if (mtime(idx) != 0.0) { 3496 pthread_mutex_lock(&data_mutex); 3497 if (truncate(idx, 0)) { 3498 fprintf(stderr, "Notification index truncation failed\n"); 3499 } 3500 pthread_mutex_unlock(&data_mutex); 3501 } 3502 } 3503 3504 3505 /** the queue **/ 3506 3507 static xs_dict *_enqueue_put(const char *fn, xs_dict *msg) 3508 /* writes safely to the queue */ 3509 { 3510 xs *tfn = xs_fmt("%s.tmp", fn); 3511 FILE *f; 3512 3513 if ((f = fopen(tfn, "w")) != NULL) { 3514 xs_json_dump(msg, 4, f); 3515 fclose(f); 3516 3517 rename(tfn, fn); 3518 } 3519 3520 return msg; 3521 } 3522 3523 3524 static xs_dict *_new_qmsg(const char *type, const xs_val *msg, int retries) 3525 /* creates a queue message */ 3526 { 3527 int qrt = xs_number_get(xs_dict_get(srv_config, "queue_retry_minutes")); 3528 xs *ntid = tid(retries * 60 * qrt); 3529 xs *rn = xs_number_new(retries); 3530 3531 xs_dict *qmsg = xs_dict_new(); 3532 3533 qmsg = xs_dict_append(qmsg, "type", type); 3534 qmsg = xs_dict_append(qmsg, "message", msg); 3535 qmsg = xs_dict_append(qmsg, "retries", rn); 3536 qmsg = xs_dict_append(qmsg, "ntid", ntid); 3537 3538 return qmsg; 3539 } 3540 3541 3542 void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries) 3543 /* enqueues an input message */ 3544 { 3545 xs *qmsg = _new_qmsg("input", msg, retries); 3546 const char *ntid = xs_dict_get(qmsg, "ntid"); 3547 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); 3548 3549 qmsg = xs_dict_append(qmsg, "req", req); 3550 3551 qmsg = _enqueue_put(fn, qmsg); 3552 3553 snac_debug(snac, 1, xs_fmt("enqueue_input %s", xs_dict_get(msg, "id"))); 3554 } 3555 3556 3557 void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries) 3558 /* enqueues an input message from the shared input */ 3559 { 3560 xs *qmsg = _new_qmsg("input", msg, retries); 3561 const char *ntid = xs_dict_get(qmsg, "ntid"); 3562 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3563 3564 qmsg = xs_dict_append(qmsg, "req", req); 3565 3566 qmsg = _enqueue_put(fn, qmsg); 3567 3568 srv_debug(1, xs_fmt("enqueue_shared_input %s", xs_dict_get(msg, "id"))); 3569 } 3570 3571 3572 void enqueue_output_raw(const char *keyid, const char *seckey, 3573 const xs_dict *msg, const xs_str *inbox, 3574 int retries, int p_status) 3575 /* enqueues an output message to an inbox */ 3576 { 3577 xs *qmsg = _new_qmsg("output", msg, retries); 3578 const char *ntid = xs_dict_get(qmsg, "ntid"); 3579 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3580 3581 xs *ns = xs_number_new(p_status); 3582 qmsg = xs_dict_append(qmsg, "p_status", ns); 3583 3584 qmsg = xs_dict_append(qmsg, "inbox", inbox); 3585 qmsg = xs_dict_append(qmsg, "keyid", keyid); 3586 qmsg = xs_dict_append(qmsg, "seckey", seckey); 3587 3588 /* if it's to be sent right now, bypass the disk queue and post the job */ 3589 if (retries == 0 && p_state != NULL) 3590 job_post(qmsg, 0); 3591 else { 3592 qmsg = _enqueue_put(fn, qmsg); 3593 srv_debug(1, xs_fmt("enqueue_output %s %s %d", inbox, fn, retries)); 3594 } 3595 } 3596 3597 3598 void enqueue_output(snac *snac, const xs_dict *msg, 3599 const xs_str *inbox, int retries, int p_status) 3600 /* enqueues an output message to an inbox */ 3601 { 3602 if (xs_startswith(inbox, snac->actor)) { 3603 snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); 3604 return; 3605 } 3606 3607 const char *seckey = xs_dict_get(snac->key, "secret"); 3608 3609 enqueue_output_raw(snac->actor, seckey, msg, inbox, retries, p_status); 3610 } 3611 3612 3613 void enqueue_output_by_actor(snac *snac, const xs_dict *msg, 3614 const xs_str *actor, int retries) 3615 /* enqueues an output message for an actor */ 3616 { 3617 xs *inbox = get_actor_inbox(actor, 1); 3618 3619 if (!xs_is_null(inbox)) 3620 enqueue_output(snac, msg, inbox, retries, 0); 3621 else 3622 snac_log(snac, xs_fmt("enqueue_output_by_actor cannot get inbox %s", actor)); 3623 } 3624 3625 3626 void enqueue_email(const xs_dict *msg, int retries) 3627 /* enqueues an email message to be sent */ 3628 { 3629 xs *qmsg = _new_qmsg("email", msg, retries); 3630 const char *ntid = xs_dict_get(qmsg, "ntid"); 3631 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3632 3633 qmsg = _enqueue_put(fn, qmsg); 3634 3635 srv_debug(1, xs_fmt("enqueue_email %d", retries)); 3636 } 3637 3638 3639 void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id) 3640 /* enqueues a message to be sent via Telegram */ 3641 { 3642 xs *qmsg = _new_qmsg("telegram", msg, 0); 3643 const char *ntid = xs_dict_get(qmsg, "ntid"); 3644 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3645 3646 qmsg = xs_dict_append(qmsg, "bot", bot); 3647 qmsg = xs_dict_append(qmsg, "chat_id", chat_id); 3648 3649 qmsg = _enqueue_put(fn, qmsg); 3650 3651 srv_debug(1, xs_fmt("enqueue_telegram %s %s", bot, chat_id)); 3652 } 3653 3654 void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_token) 3655 /* enqueues a message to be sent via ntfy */ 3656 { 3657 xs *qmsg = _new_qmsg("ntfy", msg, 0); 3658 const char *ntid = xs_dict_get(qmsg, "ntid"); 3659 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3660 3661 qmsg = xs_dict_append(qmsg, "ntfy_server", ntfy_server); 3662 qmsg = xs_dict_append(qmsg, "ntfy_token", ntfy_token); 3663 3664 3665 qmsg = _enqueue_put(fn, qmsg); 3666 3667 srv_debug(1, xs_fmt("enqueue_ntfy %s %s", ntfy_server, ntfy_token)); 3668 } 3669 3670 void enqueue_message(snac *snac, const xs_dict *msg) 3671 /* enqueues an output message */ 3672 { 3673 xs *qmsg = _new_qmsg("message", msg, 0); 3674 const char *ntid = xs_dict_get(qmsg, "ntid"); 3675 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); 3676 3677 qmsg = _enqueue_put(fn, qmsg); 3678 3679 snac_debug(snac, 0, xs_fmt("enqueue_message %s", xs_dict_get(msg, "id"))); 3680 } 3681 3682 3683 void enqueue_close_question(snac *user, const char *id, int end_secs) 3684 /* enqueues the closing of a question */ 3685 { 3686 xs *qmsg = _new_qmsg("close_question", id, 0); 3687 xs *ntid = tid(end_secs); 3688 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 3689 3690 qmsg = xs_dict_set(qmsg, "ntid", ntid); 3691 3692 qmsg = _enqueue_put(fn, qmsg); 3693 3694 snac_debug(user, 0, xs_fmt("enqueue_close_question %s", id)); 3695 } 3696 3697 3698 void enqueue_object_request(snac *user, const char *id, int forward_secs) 3699 /* enqueues the request of an object in the future */ 3700 { 3701 xs *qmsg = _new_qmsg("object_request", id, 0); 3702 xs *ntid = tid(forward_secs); 3703 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 3704 3705 qmsg = xs_dict_set(qmsg, "ntid", ntid); 3706 3707 qmsg = _enqueue_put(fn, qmsg); 3708 3709 snac_debug(user, 0, xs_fmt("enqueue_object_request %s %d", id, forward_secs)); 3710 } 3711 3712 3713 void enqueue_verify_links(snac *user) 3714 /* enqueues a link verification */ 3715 { 3716 xs *qmsg = _new_qmsg("verify_links", "", 0); 3717 const char *ntid = xs_dict_get(qmsg, "ntid"); 3718 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 3719 3720 qmsg = _enqueue_put(fn, qmsg); 3721 3722 snac_debug(user, 1, xs_fmt("enqueue_verify_links %s", user->actor)); 3723 } 3724 3725 3726 void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs) 3727 /* enqueues an actor refresh */ 3728 { 3729 xs *qmsg = _new_qmsg("actor_refresh", "", 0); 3730 xs *ntid = tid(forward_secs); 3731 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 3732 3733 qmsg = xs_dict_set(qmsg, "ntid", ntid); 3734 qmsg = xs_dict_append(qmsg, "actor", actor); 3735 3736 qmsg = _enqueue_put(fn, qmsg); 3737 3738 snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); 3739 } 3740 3741 3742 void enqueue_webmention(const xs_dict *msg) 3743 /* enqueues a webmention for the post */ 3744 { 3745 xs *qmsg = _new_qmsg("webmention", msg, 0); 3746 const char *ntid = xs_dict_get(qmsg, "ntid"); 3747 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 3748 3749 qmsg = _enqueue_put(fn, qmsg); 3750 3751 srv_debug(1, xs_fmt("enqueue_webmention")); 3752 } 3753 3754 3755 void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries) 3756 /* enqueues a notification webhook */ 3757 { 3758 const char *webhook = xs_dict_get(user->config, "notify_webhook"); 3759 3760 if (xs_is_string(webhook) && xs_match(webhook, "https://*|http://*")) { /** **/ 3761 xs *msg = xs_dup(noti); 3762 3763 /* add more data */ 3764 msg = xs_dict_set(msg, "target", user->actor); 3765 msg = xs_dict_set(msg, "uid", user->uid); 3766 msg = xs_dict_set(msg, "basedir", srv_basedir); 3767 msg = xs_dict_set(msg, "baseurl", srv_baseurl); 3768 3769 xs *actor_obj = NULL; 3770 3771 if (valid_status(object_get(xs_dict_get(noti, "actor"), &actor_obj)) && actor_obj) 3772 msg = xs_dict_set(msg, "account", actor_obj); 3773 3774 xs *qmsg = _new_qmsg("notify_webhook", msg, retries); 3775 const char *ntid = xs_dict_get(qmsg, "ntid"); 3776 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 3777 3778 qmsg = _enqueue_put(fn, qmsg); 3779 3780 snac_debug(user, 1, xs_fmt("notify_webhook")); 3781 } 3782 } 3783 3784 3785 int was_question_voted(snac *user, const char *id) 3786 /* returns true if the user voted in this poll */ 3787 { 3788 xs *children = object_children(id); 3789 int voted = 0; 3790 xs_list *p; 3791 const xs_str *md5; 3792 3793 p = children; 3794 while (xs_list_iter(&p, &md5)) { 3795 xs *obj = NULL; 3796 3797 if (valid_status(object_get_by_md5(md5, &obj))) { 3798 const char *atto = get_atto(obj); 3799 if (atto && strcmp(atto, user->actor) == 0 && 3800 !xs_is_null(xs_dict_get(obj, "name"))) { 3801 voted = 1; 3802 break; 3803 } 3804 } 3805 } 3806 3807 return voted; 3808 } 3809 3810 3811 xs_list *user_queue(snac *snac) 3812 /* returns a list with filenames that can be dequeued */ 3813 { 3814 xs *spec = xs_fmt("%s/queue/" "*.json", snac->basedir); 3815 xs_list *list = xs_list_new(); 3816 time_t t = time(NULL); 3817 xs_list *p; 3818 const xs_val *v; 3819 3820 xs *fns = xs_glob(spec, 0, 0); 3821 3822 p = fns; 3823 while (xs_list_iter(&p, &v)) { 3824 /* get the retry time from the basename */ 3825 char *bn = strrchr(v, '/'); 3826 time_t t2 = atol(bn + 1); 3827 3828 if (t2 > t) 3829 snac_debug(snac, 2, xs_fmt("user_queue not yet time for %s [%ld]", v, t)); 3830 else { 3831 list = xs_list_append(list, v); 3832 snac_debug(snac, 2, xs_fmt("user_queue ready for %s", v)); 3833 } 3834 } 3835 3836 return list; 3837 } 3838 3839 3840 xs_list *queue(void) 3841 /* returns a list with filenames that can be dequeued */ 3842 { 3843 xs *spec = xs_fmt("%s/queue/" "*.json", srv_basedir); 3844 xs_list *list = xs_list_new(); 3845 time_t t = time(NULL); 3846 xs_list *p; 3847 const xs_val *v; 3848 3849 xs *fns = xs_glob(spec, 0, 0); 3850 3851 p = fns; 3852 while (xs_list_iter(&p, &v)) { 3853 /* get the retry time from the basename */ 3854 char *bn = strrchr(v, '/'); 3855 time_t t2 = atol(bn + 1); 3856 3857 if (t2 > t) 3858 srv_debug(2, xs_fmt("queue not yet time for %s [%ld]", v, t)); 3859 else { 3860 list = xs_list_append(list, v); 3861 srv_debug(2, xs_fmt("queue ready for %s", v)); 3862 } 3863 } 3864 3865 return list; 3866 } 3867 3868 3869 xs_dict *queue_get(const char *fn) 3870 /* gets a file from a queue */ 3871 { 3872 FILE *f; 3873 xs_dict *obj = NULL; 3874 3875 if ((f = fopen(fn, "r")) != NULL) { 3876 obj = xs_json_load(f); 3877 fclose(f); 3878 } 3879 3880 return obj; 3881 } 3882 3883 3884 xs_dict *dequeue(const char *fn) 3885 /* dequeues a message */ 3886 { 3887 xs_dict *obj = queue_get(fn); 3888 3889 unlink(fn); 3890 3891 return obj; 3892 } 3893 3894 3895 /** the purge **/ 3896 3897 static int _purge_file(const char *fn, time_t mt) 3898 /* purge fn if it's older than days */ 3899 { 3900 int ret = 0; 3901 3902 if (mtime(fn) < mt) { 3903 /* older than the minimum time: delete it */ 3904 unlink(fn); 3905 srv_debug(2, xs_fmt("purged %s", fn)); 3906 ret = 1; 3907 } 3908 3909 return ret; 3910 } 3911 3912 3913 static void _purge_dir(const char *dir, int days) 3914 /* purges all files in a directory older than days */ 3915 { 3916 int cnt = 0; 3917 3918 if (days) { 3919 time_t mt = time(NULL) - days * 24 * 3600; 3920 xs *spec = xs_fmt("%s/" "*", dir); 3921 xs *list = xs_glob(spec, 0, 0); 3922 xs_list *p; 3923 const xs_str *v; 3924 3925 p = list; 3926 while (xs_list_iter(&p, &v)) 3927 cnt += _purge_file(v, mt); 3928 3929 srv_debug(1, xs_fmt("purge: %s %d", dir, cnt)); 3930 } 3931 } 3932 3933 3934 static void _purge_user_subdir(snac *snac, const char *subdir, int days) 3935 /* purges all files in a user subdir older than days */ 3936 { 3937 xs *u_subdir = xs_fmt("%s/%s", snac->basedir, subdir); 3938 3939 _purge_dir(u_subdir, days); 3940 } 3941 3942 3943 void purge_server(void) 3944 /* purge global server data */ 3945 { 3946 xs *spec = xs_fmt("%s/object/??", srv_basedir); 3947 xs *dirs = xs_glob(spec, 0, 0); 3948 xs_list *p; 3949 const xs_str *v; 3950 int cnt = 0; 3951 int icnt = 0; 3952 3953 time_t mt = time(NULL) - 7 * 24 * 3600; 3954 3955 p = dirs; 3956 while (xs_list_iter(&p, &v)) { 3957 xs_list *p2; 3958 const xs_str *v2; 3959 3960 { 3961 xs *spec2 = xs_fmt("%s/" "*.json", v); 3962 xs *files = xs_glob(spec2, 0, 0); 3963 3964 p2 = files; 3965 while (xs_list_iter(&p2, &v2)) { 3966 int n_link; 3967 3968 /* old and with no hard links? */ 3969 if (mtime_nl(v2, &n_link) < mt && n_link < 2) { 3970 xs *s1 = xs_replace(v2, ".json", ""); 3971 xs *l = xs_split(s1, "/"); 3972 const char *md5 = xs_list_get(l, -1); 3973 3974 object_del_by_md5(md5); 3975 cnt++; 3976 } 3977 } 3978 } 3979 3980 { 3981 /* look for stray indexes */ 3982 xs *speci = xs_fmt("%s/" "*_?.idx", v); 3983 xs *idxfs = xs_glob(speci, 0, 0); 3984 3985 p2 = idxfs; 3986 while (xs_list_iter(&p2, &v2)) { 3987 /* old enough to consider? */ 3988 if (mtime(v2) < mt) { 3989 /* check if the indexed object is here */ 3990 xs *o = xs_dup(v2); 3991 char *ext = strchr(o, '_'); 3992 3993 if (ext) { 3994 *ext = '\0'; 3995 o = xs_str_cat(o, ".json"); 3996 3997 if (mtime(o) == 0.0) { 3998 /* delete */ 3999 unlink(v2); 4000 srv_debug(1, xs_fmt("purged %s", v2)); 4001 icnt++; 4002 } 4003 } 4004 } 4005 } 4006 4007 /* delete index backups */ 4008 xs *specb = xs_fmt("%s/" "*.bak", v); 4009 xs *bakfs = xs_glob(specb, 0, 0); 4010 4011 p2 = bakfs; 4012 while (xs_list_iter(&p2, &v2)) { 4013 unlink(v2); 4014 srv_debug(1, xs_fmt("purged %s", v2)); 4015 } 4016 } 4017 } 4018 4019 /* purge collected inboxes */ 4020 xs *ib_dir = xs_fmt("%s/inbox", srv_basedir); 4021 _purge_dir(ib_dir, 7); 4022 4023 /* purge the instance timeline */ 4024 xs *itl_fn = xs_fmt("%s/public.idx", srv_basedir); 4025 int itl_gc = index_gc(itl_fn); 4026 4027 /* purge tag indexes */ 4028 xs *tag_spec = xs_fmt("%s/tag/??", srv_basedir); 4029 xs *tag_dirs = xs_glob(tag_spec, 0, 0); 4030 p = tag_dirs; 4031 4032 int tag_gc = 0; 4033 while (xs_list_iter(&p, &v)) { 4034 xs *spec2 = xs_fmt("%s/" "*.idx", v); 4035 xs *files = xs_glob(spec2, 0, 0); 4036 xs_list *p2; 4037 const xs_str *v2; 4038 4039 p2 = files; 4040 while (xs_list_iter(&p2, &v2)) { 4041 tag_gc += index_gc(v2); 4042 xs *bak = xs_fmt("%s.bak", v2); 4043 unlink(bak); 4044 4045 if (index_len(v2) == 0) { 4046 /* there are no longer any entry with this tag; 4047 purge it completely */ 4048 unlink(v2); 4049 xs *dottag = xs_replace(v2, ".idx", ".tag"); 4050 unlink(dottag); 4051 } 4052 } 4053 } 4054 4055 srv_debug(1, xs_fmt("purge: global " 4056 "(obj: %d, idx: %d, itl: %d, tag: %d)", cnt, icnt, itl_gc, tag_gc)); 4057 } 4058 4059 4060 void purge_user(snac *snac) 4061 /* do the purge for this user */ 4062 { 4063 int priv_days, pub_days, user_days = 0; 4064 const char *v; 4065 int n; 4066 4067 priv_days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days")); 4068 pub_days = xs_number_get(xs_dict_get(srv_config, "local_purge_days")); 4069 4070 if ((v = xs_dict_get(snac->config_o, "purge_days")) != NULL || 4071 (v = xs_dict_get(snac->config, "purge_days")) != NULL) 4072 user_days = xs_number_get(v); 4073 4074 if (user_days) { 4075 /* override admin settings only if they are lesser */ 4076 if (priv_days == 0 || user_days < priv_days) 4077 priv_days = user_days; 4078 4079 if (pub_days == 0 || user_days < pub_days) 4080 pub_days = user_days; 4081 } 4082 4083 _purge_user_subdir(snac, "hidden", priv_days); 4084 _purge_user_subdir(snac, "private", priv_days); 4085 4086 _purge_user_subdir(snac, "public", pub_days); 4087 4088 const char *idxs[] = { "followers.idx", "private.idx", "public.idx", 4089 "pinned.idx", "bookmark.idx", "draft.idx", "sched.idx", NULL }; 4090 4091 for (n = 0; idxs[n]; n++) { 4092 xs *idx = xs_fmt("%s/%s", snac->basedir, idxs[n]); 4093 int gc = index_gc(idx); 4094 srv_debug(1, xs_fmt("purge: %s %d", idx, gc)); 4095 } 4096 4097 /* purge lists */ 4098 { 4099 xs *spec = xs_fmt("%s/list/" "*.idx", snac->basedir); 4100 xs *lol = xs_glob(spec, 0, 0); 4101 int c = 0; 4102 const char *v; 4103 4104 while (xs_list_next(lol, &v, &c)) { 4105 int gc = index_gc(v); 4106 srv_debug(1, xs_fmt("purge: %s %d", v, gc)); 4107 } 4108 } 4109 4110 /* unrelated to purging, but it's a janitorial process, so what the hell */ 4111 verify_links(snac); 4112 } 4113 4114 4115 void purge_all(void) 4116 /* purge all users */ 4117 { 4118 snac snac; 4119 xs *list = user_list(); 4120 char *p; 4121 const char *uid; 4122 4123 p = list; 4124 while (xs_list_iter(&p, &uid)) { 4125 if (user_open(&snac, uid)) { 4126 purge_user(&snac); 4127 user_free(&snac); 4128 } 4129 } 4130 4131 purge_server(); 4132 4133 #ifndef NO_MASTODON_API 4134 mastoapi_purge(); 4135 #endif 4136 } 4137 4138 4139 /** archive **/ 4140 4141 void srv_archive(const char *direction, const char *url, xs_dict *req, 4142 const char *payload, int p_size, 4143 int status, xs_dict *headers, 4144 const char *body, int b_size) 4145 /* archives a connection */ 4146 { 4147 /* obsessive archiving */ 4148 xs *date = tid(0); 4149 xs *dir = xs_fmt("%s/archive/%s_%s", srv_basedir, date, direction); 4150 FILE *f; 4151 4152 if (mkdirx(dir) != -1) { 4153 xs *meta_fn = xs_fmt("%s/_META", dir); 4154 4155 if ((f = fopen(meta_fn, "w")) != NULL) { 4156 xs *j1 = xs_json_dumps(req, 4); 4157 xs *j2 = xs_json_dumps(headers, 4); 4158 4159 fprintf(f, "dir: %s\n", direction); 4160 4161 if (url) 4162 fprintf(f, "url: %s\n", url); 4163 4164 fprintf(f, "req: %s\n", j1); 4165 fprintf(f, "p_size: %d\n", p_size); 4166 fprintf(f, "status: %d\n", status); 4167 fprintf(f, "response: %s\n", j2); 4168 fprintf(f, "b_size: %d\n", b_size); 4169 fclose(f); 4170 } 4171 4172 if (p_size && payload) { 4173 xs *payload_fn = NULL; 4174 xs *payload_fn_raw = NULL; 4175 const char *v = xs_dict_get(req, "content-type"); 4176 4177 if (v && xs_str_in(v, "json") != -1) { 4178 payload_fn = xs_fmt("%s/payload.json", dir); 4179 4180 if ((f = fopen(payload_fn, "w")) != NULL) { 4181 xs *v1 = xs_json_loads(payload); 4182 xs *j1 = NULL; 4183 4184 if (v1 != NULL) 4185 j1 = xs_json_dumps(v1, 4); 4186 4187 if (j1 != NULL) 4188 fwrite(j1, strlen(j1), 1, f); 4189 else 4190 fwrite(payload, p_size, 1, f); 4191 4192 fclose(f); 4193 } 4194 } 4195 4196 payload_fn_raw = xs_fmt("%s/payload", dir); 4197 4198 if ((f = fopen(payload_fn_raw, "w")) != NULL) { 4199 fwrite(payload, p_size, 1, f); 4200 fclose(f); 4201 } 4202 } 4203 4204 if (b_size && body) { 4205 xs *body_fn = NULL; 4206 const char *v = xs_dict_get(headers, "content-type"); 4207 4208 if (v && xs_str_in(v, "json") != -1) { 4209 body_fn = xs_fmt("%s/body.json", dir); 4210 4211 if ((f = fopen(body_fn, "w")) != NULL) { 4212 xs *v1 = xs_json_loads(body); 4213 xs *j1 = NULL; 4214 4215 if (v1 != NULL) 4216 j1 = xs_json_dumps(v1, 4); 4217 4218 if (j1 != NULL) 4219 fwrite(j1, strlen(j1), 1, f); 4220 else 4221 fwrite(body, b_size, 1, f); 4222 4223 fclose(f); 4224 } 4225 } 4226 else { 4227 body_fn = xs_fmt("%s/body", dir); 4228 4229 if ((f = fopen(body_fn, "w")) != NULL) { 4230 fwrite(body, b_size, 1, f); 4231 fclose(f); 4232 } 4233 } 4234 } 4235 } 4236 } 4237 4238 4239 void srv_archive_error(const char *prefix, const xs_str *err, 4240 const xs_dict *req, const xs_val *data) 4241 /* archives an error */ 4242 { 4243 xs *ntid = tid(0); 4244 xs *fn = xs_fmt("%s/error/%s_%s", srv_basedir, ntid, prefix); 4245 FILE *f; 4246 4247 if ((f = fopen(fn, "w")) != NULL) { 4248 fprintf(f, "Error: %s\n", err); 4249 4250 if (req) { 4251 fprintf(f, "Request headers:\n"); 4252 4253 xs_json_dump(req, 4, f); 4254 4255 fprintf(f, "\n"); 4256 } 4257 4258 if (data) { 4259 fprintf(f, "Data:\n"); 4260 4261 if (xs_type(data) == XSTYPE_LIST || xs_type(data) == XSTYPE_DICT) { 4262 xs_json_dump(data, 4, f); 4263 } 4264 else 4265 fprintf(f, "%s", data); 4266 4267 fprintf(f, "\n"); 4268 } 4269 4270 fclose(f); 4271 } 4272 } 4273 4274 4275 void srv_archive_qitem(const char *prefix, xs_dict *q_item) 4276 /* archives a q_item in the error folder */ 4277 { 4278 xs *ntid = tid(0); 4279 xs *fn = xs_fmt("%s/error/%s_qitem_%s", srv_basedir, ntid, prefix); 4280 FILE *f; 4281 4282 if ((f = fopen(fn, "w")) != NULL) { 4283 xs_json_dump(q_item, 4, f); 4284 fclose(f); 4285 } 4286 } 4287 4288 4289 t_announcement *announcement(const double after) 4290 /* returns announcement text or NULL if none exists or it is olde than "after" */ 4291 { 4292 static const long int MAX_SIZE = 2048; 4293 static t_announcement a = { 4294 .text = NULL, 4295 .timestamp = 0.0, 4296 }; 4297 static xs_str *fn = NULL; 4298 if (fn == NULL) 4299 fn = xs_fmt("%s/announcement.txt", srv_basedir); 4300 4301 const double ts = mtime(fn); 4302 4303 /* file does not exist or other than what was requested */ 4304 if (ts == 0.0 || ts <= after) 4305 return NULL; 4306 4307 /* nothing changed, just return the current announcement */ 4308 if (a.text != NULL && ts <= a.timestamp) 4309 return &a; 4310 4311 /* read and store new announcement */ 4312 FILE *f; 4313 4314 if ((f = fopen(fn, "r")) != NULL) { 4315 char buffer[MAX_SIZE]; 4316 int r = fread(buffer, 1, MAX_SIZE, f); 4317 4318 if (r == 0) { 4319 /* an empty file means no announcement */ 4320 free(a.text); 4321 a.text = NULL; 4322 a.timestamp = 0.0; 4323 } 4324 else 4325 if (r <= 0 || !xs_is_string(buffer)) { 4326 /* this is probably unintentional */ 4327 srv_log(xs_fmt("announcement.txt reading failed")); 4328 } 4329 else 4330 if (r > MAX_SIZE) { 4331 /* this is probably unintentional */ 4332 srv_log(xs_fmt("announcement.txt too big: max is %ld, ignoring.", MAX_SIZE)); 4333 } 4334 else { 4335 buffer[r] = '\0'; 4336 free(a.text); 4337 a.text = xs_dup(buffer); 4338 a.timestamp = ts; 4339 } 4340 4341 fclose (f); 4342 } 4343 4344 if (a.text != NULL) 4345 return &a; 4346 4347 return NULL; 4348 } 4349 4350 4351 xs_str *make_url(const char *href, const char *proxy, int by_token) 4352 /* makes an URL, possibly including proxying */ 4353 { 4354 xs_str *url = NULL; 4355 4356 if (proxy && !xs_startswith(href, srv_baseurl)) { 4357 xs *p = NULL; 4358 4359 if (by_token) { 4360 const char *tk = xs_md5(srv_proxy_token_seed, ":", proxy); 4361 4362 p = xs_fmt("%s/y/%s/", proxy, tk); 4363 } 4364 else 4365 p = xs_fmt("%s/x/", proxy); 4366 4367 url = xs_replace(href, "https:/" "/", p); 4368 } 4369 else 4370 url = xs_dup(href); 4371 4372 return url; 4373 } 4374 4375 4376 /** bad login throttle **/ 4377 4378 xs_str *_badlogin_fn(const char *addr) 4379 { 4380 const char *md5 = xs_md5(addr); 4381 xs *dir = xs_fmt("%s/badlogin", srv_basedir); 4382 4383 mkdirx(dir); 4384 4385 return xs_fmt("%s/%s", dir, md5); 4386 } 4387 4388 4389 int _badlogin_read(const char *fn, int *failures) 4390 /* reads a badlogin file */ 4391 { 4392 int ok = 0; 4393 FILE *f; 4394 4395 pthread_mutex_lock(&data_mutex); 4396 4397 if ((f = fopen(fn, "r")) != NULL) { 4398 xs *l = xs_readline(f); 4399 fclose(f); 4400 4401 if (sscanf(l, "%d", failures) == 1) 4402 ok = 1; 4403 } 4404 4405 pthread_mutex_unlock(&data_mutex); 4406 4407 return ok; 4408 } 4409 4410 4411 int badlogin_check(const char *user, const char *addr) 4412 /* checks if this address is authorized to try a login */ 4413 { 4414 int valid = 1; 4415 4416 if (xs_type(addr) == XSTYPE_STRING) { 4417 xs *fn = _badlogin_fn(addr); 4418 double mt = mtime(fn); 4419 4420 if (mt > 0) { 4421 int badlogin_expire = xs_number_get(xs_dict_get_def(srv_config, 4422 "badlogin_expire", "300")); 4423 4424 mt += badlogin_expire; 4425 4426 /* if file is expired, delete and give pass */ 4427 if (mt < time(NULL)) { 4428 srv_debug(1, xs_fmt("Login from %s for %s allowed again", addr, user)); 4429 unlink(fn); 4430 } 4431 else { 4432 int failures; 4433 4434 if (_badlogin_read(fn, &failures)) { 4435 int badlogin_max = xs_number_get(xs_dict_get_def(srv_config, 4436 "badlogin_retries", "5")); 4437 4438 if (failures >= badlogin_max) { 4439 valid = 0; 4440 4441 xs *d = xs_str_iso_date((time_t) mt); 4442 4443 srv_debug(1, 4444 xs_fmt("Login from %s for %s forbidden until %s", addr, user, d)); 4445 } 4446 } 4447 } 4448 } 4449 } 4450 4451 return valid; 4452 } 4453 4454 4455 void badlogin_inc(const char *user, const char *addr) 4456 /* increments a bad login from this address */ 4457 { 4458 if (xs_type(addr) == XSTYPE_STRING) { 4459 int failures = 0; 4460 xs *fn = _badlogin_fn(addr); 4461 FILE *f; 4462 4463 _badlogin_read(fn, &failures); 4464 4465 pthread_mutex_lock(&data_mutex); 4466 4467 if ((f = fopen(fn, "w")) != NULL) { 4468 failures++; 4469 4470 fprintf(f, "%d %s %s\n", failures, addr, user); 4471 fclose(f); 4472 4473 srv_log(xs_fmt("Registered %d login failure(s) from %s for %s", failures, addr, user)); 4474 } 4475 4476 pthread_mutex_unlock(&data_mutex); 4477 } 4478 } 4479 4480 4481 /** language strings **/ 4482 4483 const char *lang_str(const char *str, const snac *user) 4484 /* returns a translated string */ 4485 { 4486 const char *n_str = str; 4487 4488 if (user && xs_is_dict(user->lang) && xs_is_string(str)) { 4489 n_str = xs_dict_get(user->lang, str); 4490 4491 if (xs_is_null(n_str) || *n_str == '\0') 4492 n_str = str; 4493 } 4494 4495 return n_str; 4496 }