diff options
| author | 2024-12-23 13:42:45 +0100 | |
|---|---|---|
| committer | 2024-12-23 13:42:45 +0100 | |
| commit | a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1 (patch) | |
| tree | 3128196bd7eb298be5a37edac5922009ec5fcac1 | |
| parent | Merge tag '2.66' (diff) | |
| parent | Version 2.67 RELEASED. (diff) | |
| download | penes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.tar.gz penes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.tar.xz penes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.zip | |
Merge tag '2.67'
Version 2.67 RELEASED.
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | RELEASE_NOTES.md | 14 | ||||
| -rw-r--r-- | activitypub.c | 33 | ||||
| -rw-r--r-- | data.c | 190 | ||||
| -rw-r--r-- | doc/snac.8 | 29 | ||||
| -rw-r--r-- | examples/static-linking-with-musl.md | 77 | ||||
| -rw-r--r-- | format.c | 52 | ||||
| -rw-r--r-- | html.c | 90 | ||||
| -rw-r--r-- | httpd.c | 36 | ||||
| -rw-r--r-- | mastoapi.c | 259 | ||||
| -rw-r--r-- | snac.h | 10 | ||||
| -rw-r--r-- | webfinger.c | 6 | ||||
| -rw-r--r-- | xs_fcgi.h | 2 |
13 files changed, 692 insertions, 114 deletions
| @@ -1,4 +1,4 @@ | |||
| 1 | PREFIX=/usr/local | 1 | PREFIX?=/usr/local |
| 2 | PREFIX_MAN=$(PREFIX)/man | 2 | PREFIX_MAN=$(PREFIX)/man |
| 3 | CFLAGS?=-g -Wall -Wextra -pedantic | 3 | CFLAGS?=-g -Wall -Wextra -pedantic |
| 4 | 4 | ||
| @@ -6,16 +6,16 @@ all: snac | |||
| 6 | 6 | ||
| 7 | snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \ | 7 | snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \ |
| 8 | activitypub.o html.o utils.o format.o upgrade.o mastoapi.o | 8 | activitypub.o html.o utils.o format.o upgrade.o mastoapi.o |
| 9 | $(CC) $(CFLAGS) -L/usr/local/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ | 9 | $(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ |
| 10 | 10 | ||
| 11 | .c.o: | 11 | .c.o: |
| 12 | $(CC) $(CFLAGS) $(CPPFLAGS) -I/usr/local/include -c $< | 12 | $(CC) $(CFLAGS) $(CPPFLAGS) -I$(PREFIX)/include -c $< |
| 13 | 13 | ||
| 14 | clean: | 14 | clean: |
| 15 | rm -rf *.o *.core snac makefile.depend | 15 | rm -rf *.o *.core snac makefile.depend |
| 16 | 16 | ||
| 17 | dep: | 17 | dep: |
| 18 | $(CC) -I/usr/local/include -MM *.c > makefile.depend | 18 | $(CC) -I$(PREFIX)/include -MM *.c > makefile.depend |
| 19 | 19 | ||
| 20 | install: | 20 | install: |
| 21 | mkdir -p -m 755 $(PREFIX)/bin | 21 | mkdir -p -m 755 $(PREFIX)/bin |
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4630352..073985b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,5 +1,19 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## 2.67 | ||
| 4 | |||
| 5 | The search box also accepts post URLs; the post is requested and, if it's found, can be interacted with (liked, boosted, replied to, etc.). | ||
| 6 | |||
| 7 | IP addresses for failed logins are tracked and throttled to mitigate brute force attacks (see `snac(8)` for more information). | ||
| 8 | |||
| 9 | Fixed a bug regarding repeated attachments when editing a post. | ||
| 10 | |||
| 11 | Mastodon API: Improved timeline pagination in some clients (details: an HTTP `Link` header has been added), fixed missing audio attachments, fixed an incorrect value for the `bot` field in newly created accounts, fixed a crash, implemented markers (contributed by nowster). | ||
| 12 | |||
| 13 | When running in server mode, the pidfile is locked to avoid concurrent running of the same server instance. | ||
| 14 | |||
| 15 | Added documentation and some tweaks to enable static compilation with musl (contributed by Shamar). | ||
| 16 | |||
| 3 | ## 2.66 | 17 | ## 2.66 |
| 4 | 18 | ||
| 5 | As many users have asked for it, there is now an option to make the number of followed and following accounts public (still disabled by default). These are only the numbers; the lists themselves are never published. | 19 | As many users have asked for it, there is now an option to make the number of followed and following accounts public (still disabled by default). These are only the numbers; the lists themselves are never published. |
diff --git a/activitypub.c b/activitypub.c index 773df78..34cc32f 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -258,6 +258,10 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 258 | d = xs_dict_append(d, "href", href); | 258 | d = xs_dict_append(d, "href", href); |
| 259 | d = xs_dict_append(d, "name", name); | 259 | d = xs_dict_append(d, "name", name); |
| 260 | 260 | ||
| 261 | const xs_dict *icon = xs_dict_get(v, "icon"); | ||
| 262 | if (xs_type(icon) == XSTYPE_DICT) | ||
| 263 | d = xs_dict_append(d, "icon", icon); | ||
| 264 | |||
| 261 | l = xs_list_append(l, d); | 265 | l = xs_list_append(l, d); |
| 262 | } | 266 | } |
| 263 | } | 267 | } |
| @@ -1476,20 +1480,31 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1476 | 1480 | ||
| 1477 | /* create the attachment list, if there are any */ | 1481 | /* create the attachment list, if there are any */ |
| 1478 | if (!xs_is_null(attach)) { | 1482 | if (!xs_is_null(attach)) { |
| 1479 | int c = 0; | 1483 | xs_list_foreach(attach, v) { |
| 1480 | while (xs_list_next(attach, &v, &c)) { | ||
| 1481 | xs *d = xs_dict_new(); | ||
| 1482 | const char *url = xs_list_get(v, 0); | 1484 | const char *url = xs_list_get(v, 0); |
| 1483 | const char *alt = xs_list_get(v, 1); | 1485 | const char *alt = xs_list_get(v, 1); |
| 1484 | const char *mime = xs_mime_by_ext(url); | 1486 | const char *mime = xs_mime_by_ext(url); |
| 1487 | int add = 1; | ||
| 1488 | |||
| 1489 | /* check if it's already here */ | ||
| 1490 | const xs_dict *ad; | ||
| 1491 | xs_list_foreach(atls, ad) { | ||
| 1492 | if (strcmp(xs_dict_get_def(ad, "url", ""), url) == 0) { | ||
| 1493 | add = 0; | ||
| 1494 | break; | ||
| 1495 | } | ||
| 1496 | } | ||
| 1485 | 1497 | ||
| 1486 | d = xs_dict_append(d, "mediaType", mime); | 1498 | if (add) { |
| 1487 | d = xs_dict_append(d, "url", url); | 1499 | xs *d = xs_dict_new(); |
| 1488 | d = xs_dict_append(d, "name", alt); | 1500 | d = xs_dict_append(d, "mediaType", mime); |
| 1489 | d = xs_dict_append(d, "type", | 1501 | d = xs_dict_append(d, "url", url); |
| 1490 | xs_startswith(mime, "image/") ? "Image" : "Document"); | 1502 | d = xs_dict_append(d, "name", alt); |
| 1503 | d = xs_dict_append(d, "type", | ||
| 1504 | xs_startswith(mime, "image/") ? "Image" : "Document"); | ||
| 1491 | 1505 | ||
| 1492 | atls = xs_list_append(atls, d); | 1506 | atls = xs_list_append(atls, d); |
| 1507 | } | ||
| 1493 | } | 1508 | } |
| 1494 | } | 1509 | } |
| 1495 | 1510 | ||
| @@ -2705,6 +2705,23 @@ xs_list *content_search(snac *user, const char *regex, | |||
| 2705 | if (id == NULL || is_hidden(user, id)) | 2705 | if (id == NULL || is_hidden(user, id)) |
| 2706 | continue; | 2706 | continue; |
| 2707 | 2707 | ||
| 2708 | /* test for the post URL */ | ||
| 2709 | if (strcmp(id, regex) == 0) { | ||
| 2710 | if (xs_set_add(&seen, md5) == 1) | ||
| 2711 | show--; | ||
| 2712 | |||
| 2713 | continue; | ||
| 2714 | } | ||
| 2715 | |||
| 2716 | /* test for the alternate post id */ | ||
| 2717 | const char *url = xs_dict_get(post, "url"); | ||
| 2718 | if (xs_type(url) == XSTYPE_STRING && strcmp(url, regex) == 0) { | ||
| 2719 | if (xs_set_add(&seen, md5) == 1) | ||
| 2720 | show--; | ||
| 2721 | |||
| 2722 | continue; | ||
| 2723 | } | ||
| 2724 | |||
| 2708 | xs *c = xs_str_new(NULL); | 2725 | xs *c = xs_str_new(NULL); |
| 2709 | const char *content = xs_dict_get(post, "content"); | 2726 | const char *content = xs_dict_get(post, "content"); |
| 2710 | const char *name = xs_dict_get(post, "name"); | 2727 | const char *name = xs_dict_get(post, "name"); |
| @@ -2786,6 +2803,74 @@ xs_str *notify_check_time(snac *snac, int reset) | |||
| 2786 | return t; | 2803 | return t; |
| 2787 | } | 2804 | } |
| 2788 | 2805 | ||
| 2806 | xs_dict *markers_get(snac *snac, const xs_list *markers) | ||
| 2807 | { | ||
| 2808 | xs *data = NULL; | ||
| 2809 | xs_dict *returns = xs_dict_new(); | ||
| 2810 | xs *fn = xs_fmt("%s/markers.json", snac->basedir); | ||
| 2811 | const xs_str *v = NULL; | ||
| 2812 | FILE *f; | ||
| 2813 | |||
| 2814 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 2815 | data = xs_json_load(f); | ||
| 2816 | fclose(f); | ||
| 2817 | } | ||
| 2818 | |||
| 2819 | if (xs_is_null(data)) | ||
| 2820 | data = xs_dict_new(); | ||
| 2821 | |||
| 2822 | xs_list_foreach(markers, v) { | ||
| 2823 | const xs_dict *mark = xs_dict_get(data, v); | ||
| 2824 | if (!xs_is_null(mark)) { | ||
| 2825 | returns = xs_dict_append(returns, v, mark); | ||
| 2826 | } | ||
| 2827 | } | ||
| 2828 | return returns; | ||
| 2829 | } | ||
| 2830 | |||
| 2831 | xs_dict *markers_set(snac *snac, const char *home_marker, const char *notify_marker) | ||
| 2832 | /* gets or sets notification marker */ | ||
| 2833 | { | ||
| 2834 | xs *data = NULL; | ||
| 2835 | xs_dict *written = xs_dict_new(); | ||
| 2836 | xs *fn = xs_fmt("%s/markers.json", snac->basedir); | ||
| 2837 | FILE *f; | ||
| 2838 | |||
| 2839 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 2840 | data = xs_json_load(f); | ||
| 2841 | fclose(f); | ||
| 2842 | } | ||
| 2843 | |||
| 2844 | if (xs_is_null(data)) | ||
| 2845 | data = xs_dict_new(); | ||
| 2846 | |||
| 2847 | if (!xs_is_null(home_marker)) { | ||
| 2848 | xs *home = xs_dict_new(); | ||
| 2849 | xs *s_tid = tid(0); | ||
| 2850 | home = xs_dict_append(home, "last_read_id", home_marker); | ||
| 2851 | home = xs_dict_append(home, "version", xs_stock(0)); | ||
| 2852 | home = xs_dict_append(home, "updated_at", s_tid); | ||
| 2853 | data = xs_dict_set(data, "home", home); | ||
| 2854 | written = xs_dict_append(written, "home", home); | ||
| 2855 | } | ||
| 2856 | |||
| 2857 | if (!xs_is_null(notify_marker)) { | ||
| 2858 | xs *notify = xs_dict_new(); | ||
| 2859 | xs *s_tid = tid(0); | ||
| 2860 | notify = xs_dict_append(notify, "last_read_id", notify_marker); | ||
| 2861 | notify = xs_dict_append(notify, "version", xs_stock(0)); | ||
| 2862 | notify = xs_dict_append(notify, "updated_at", s_tid); | ||
| 2863 | data = xs_dict_set(data, "notifications", notify); | ||
| 2864 | written = xs_dict_append(written, "notifications", notify); | ||
| 2865 | } | ||
| 2866 | |||
| 2867 | if ((f = fopen(fn, "w")) != NULL) { | ||
| 2868 | xs_json_dump(data, 4, f); | ||
| 2869 | fclose(f); | ||
| 2870 | } | ||
| 2871 | |||
| 2872 | return written; | ||
| 2873 | } | ||
| 2789 | 2874 | ||
| 2790 | void notify_add(snac *snac, const char *type, const char *utype, | 2875 | void notify_add(snac *snac, const char *type, const char *utype, |
| 2791 | const char *actor, const char *objid, const xs_dict *msg) | 2876 | const char *actor, const char *objid, const xs_dict *msg) |
| @@ -3767,3 +3852,108 @@ xs_str *make_url(const char *href, const char *proxy, int by_token) | |||
| 3767 | 3852 | ||
| 3768 | return url; | 3853 | return url; |
| 3769 | } | 3854 | } |
| 3855 | |||
| 3856 | |||
| 3857 | /** bad login throttle **/ | ||
| 3858 | |||
| 3859 | xs_str *_badlogin_fn(const char *addr) | ||
| 3860 | { | ||
| 3861 | xs *md5 = xs_md5_hex(addr, strlen(addr)); | ||
| 3862 | xs *dir = xs_fmt("%s/badlogin", srv_basedir); | ||
| 3863 | |||
| 3864 | mkdirx(dir); | ||
| 3865 | |||
| 3866 | return xs_fmt("%s/%s", dir, md5); | ||
| 3867 | } | ||
| 3868 | |||
| 3869 | |||
| 3870 | int _badlogin_read(const char *fn, int *failures) | ||
| 3871 | /* reads a badlogin file */ | ||
| 3872 | { | ||
| 3873 | int ok = 0; | ||
| 3874 | FILE *f; | ||
| 3875 | |||
| 3876 | pthread_mutex_lock(&data_mutex); | ||
| 3877 | |||
| 3878 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 3879 | xs *l = xs_readline(f); | ||
| 3880 | fclose(f); | ||
| 3881 | |||
| 3882 | if (sscanf(l, "%d", failures) == 1) | ||
| 3883 | ok = 1; | ||
| 3884 | } | ||
| 3885 | |||
| 3886 | pthread_mutex_unlock(&data_mutex); | ||
| 3887 | |||
| 3888 | return ok; | ||
| 3889 | } | ||
| 3890 | |||
| 3891 | |||
| 3892 | int badlogin_check(const char *user, const char *addr) | ||
| 3893 | /* checks if this address is authorized to try a login */ | ||
| 3894 | { | ||
| 3895 | int valid = 1; | ||
| 3896 | |||
| 3897 | if (xs_type(addr) == XSTYPE_STRING) { | ||
| 3898 | xs *fn = _badlogin_fn(addr); | ||
| 3899 | double mt = mtime(fn); | ||
| 3900 | |||
| 3901 | if (mt > 0) { | ||
| 3902 | int badlogin_expire = xs_number_get(xs_dict_get_def(srv_config, | ||
| 3903 | "badlogin_expire", "300")); | ||
| 3904 | |||
| 3905 | mt += badlogin_expire; | ||
| 3906 | |||
| 3907 | /* if file is expired, delete and give pass */ | ||
| 3908 | if (mt < time(NULL)) { | ||
| 3909 | srv_debug(1, xs_fmt("Login from %s for %s allowed again", addr, user)); | ||
| 3910 | unlink(fn); | ||
| 3911 | } | ||
| 3912 | else { | ||
| 3913 | int failures; | ||
| 3914 | |||
| 3915 | if (_badlogin_read(fn, &failures)) { | ||
| 3916 | int badlogin_max = xs_number_get(xs_dict_get_def(srv_config, | ||
| 3917 | "badlogin_retries", "5")); | ||
| 3918 | |||
| 3919 | if (failures >= badlogin_max) { | ||
| 3920 | valid = 0; | ||
| 3921 | |||
| 3922 | xs *d = xs_str_iso_date((time_t) mt); | ||
| 3923 | |||
| 3924 | srv_debug(1, | ||
| 3925 | xs_fmt("Login from %s for %s forbidden until %s", addr, user, d)); | ||
| 3926 | } | ||
| 3927 | } | ||
| 3928 | } | ||
| 3929 | } | ||
| 3930 | } | ||
| 3931 | |||
| 3932 | return valid; | ||
| 3933 | } | ||
| 3934 | |||
| 3935 | |||
| 3936 | void badlogin_inc(const char *user, const char *addr) | ||
| 3937 | /* increments a bad login from this address */ | ||
| 3938 | { | ||
| 3939 | if (xs_type(addr) == XSTYPE_STRING) { | ||
| 3940 | int failures = 0; | ||
| 3941 | xs *fn = _badlogin_fn(addr); | ||
| 3942 | FILE *f; | ||
| 3943 | |||
| 3944 | _badlogin_read(fn, &failures); | ||
| 3945 | |||
| 3946 | pthread_mutex_lock(&data_mutex); | ||
| 3947 | |||
| 3948 | if ((f = fopen(fn, "w")) != NULL) { | ||
| 3949 | failures++; | ||
| 3950 | |||
| 3951 | fprintf(f, "%d %s %s\n", failures, addr, user); | ||
| 3952 | fclose(f); | ||
| 3953 | |||
| 3954 | srv_log(xs_fmt("Registered %d login failure(s) from %s for %s", failures, addr, user)); | ||
| 3955 | } | ||
| 3956 | |||
| 3957 | pthread_mutex_unlock(&data_mutex); | ||
| 3958 | } | ||
| 3959 | } | ||
| @@ -242,6 +242,12 @@ posts will not be direct ones, but proxied by | |||
| 242 | This way, remote media servers will not see the user's IP, but the server one, | 242 | This way, remote media servers will not see the user's IP, but the server one, |
| 243 | improving privacy. Please take note that this will increase the server's incoming | 243 | improving privacy. Please take note that this will increase the server's incoming |
| 244 | and outgoing traffic. | 244 | and outgoing traffic. |
| 245 | .It Ic badlogin_retries | ||
| 246 | If incorrect logins from a given IP address reach this count, subsequent attempts | ||
| 247 | from it are rejected until the lock expires (default: 5 retries). | ||
| 248 | .It Ic badlogin_expire | ||
| 249 | The number of seconds a blocked IP address is ignored in login attempts | ||
| 250 | (default: 300 seconds). | ||
| 245 | .El | 251 | .El |
| 246 | .Pp | 252 | .Pp |
| 247 | You must restart the server to make effective these changes. | 253 | You must restart the server to make effective these changes. |
| @@ -546,6 +552,22 @@ heavily on how all the servers involved behave. Just cross your fingers and hope | |||
| 546 | Full instances can be blocked. This operation must be done from | 552 | Full instances can be blocked. This operation must be done from |
| 547 | the command-line tool. See | 553 | the command-line tool. See |
| 548 | .Xr snac 1 . | 554 | .Xr snac 1 . |
| 555 | .Pp | ||
| 556 | .Ss Bad login throttling | ||
| 557 | Since version 2.67, a simple logic to avoid brute force attacks against user passwords | ||
| 558 | has been implemented: if, from a given IP address, the number of failed logins reaches | ||
| 559 | a given threshold, further tries from that IP address are never successful until a timer | ||
| 560 | expires. The maximum number of retries can be configured in the | ||
| 561 | .Pa server.json | ||
| 562 | file by setting the | ||
| 563 | .Ic badlogin_retries | ||
| 564 | variable, and the number of seconds the IP address unlock timer expires, in | ||
| 565 | .Ic badlogin_expire . | ||
| 566 | Please take note that, for this system to work, you must setup your web server proxy | ||
| 567 | to pass the remote connection address in the | ||
| 568 | .Ic X-Forwarded-For | ||
| 569 | HTTP header (unless you use the FastCGI interface; if that's the case, you don't have | ||
| 570 | to do anything). | ||
| 549 | .Sh ENVIRONMENT | 571 | .Sh ENVIRONMENT |
| 550 | .Bl -tag -width Ds | 572 | .Bl -tag -width Ds |
| 551 | .It Ev DEBUG | 573 | .It Ev DEBUG |
| @@ -603,35 +625,42 @@ example.com server section: | |||
| 603 | location /fedi { | 625 | location /fedi { |
| 604 | proxy_pass http://localhost:8001; | 626 | proxy_pass http://localhost:8001; |
| 605 | proxy_set_header Host $http_host; | 627 | proxy_set_header Host $http_host; |
| 628 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 606 | } | 629 | } |
| 607 | # webfinger | 630 | # webfinger |
| 608 | location /.well-known/webfinger { | 631 | location /.well-known/webfinger { |
| 609 | proxy_pass http://localhost:8001; | 632 | proxy_pass http://localhost:8001; |
| 610 | proxy_set_header Host $http_host; | 633 | proxy_set_header Host $http_host; |
| 634 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 611 | } | 635 | } |
| 612 | # Mastodon API (entry points) | 636 | # Mastodon API (entry points) |
| 613 | location /api/v1/ { | 637 | location /api/v1/ { |
| 614 | proxy_pass http://localhost:8001; | 638 | proxy_pass http://localhost:8001; |
| 615 | proxy_set_header Host $http_host; | 639 | proxy_set_header Host $http_host; |
| 640 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 616 | } | 641 | } |
| 617 | location /api/v2/ { | 642 | location /api/v2/ { |
| 618 | proxy_pass http://localhost:8001; | 643 | proxy_pass http://localhost:8001; |
| 619 | proxy_set_header Host $http_host; | 644 | proxy_set_header Host $http_host; |
| 645 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 620 | } | 646 | } |
| 621 | # Mastodon API (OAuth support) | 647 | # Mastodon API (OAuth support) |
| 622 | location /oauth { | 648 | location /oauth { |
| 623 | proxy_pass http://localhost:8001; | 649 | proxy_pass http://localhost:8001; |
| 624 | proxy_set_header Host $http_host; | 650 | proxy_set_header Host $http_host; |
| 651 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 625 | } | 652 | } |
| 626 | # optional | 653 | # optional |
| 627 | location /.well-known/nodeinfo { | 654 | location /.well-known/nodeinfo { |
| 628 | proxy_pass http://localhost:8001; | 655 | proxy_pass http://localhost:8001; |
| 629 | proxy_set_header Host $http_host; | 656 | proxy_set_header Host $http_host; |
| 657 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 630 | } | 658 | } |
| 631 | # optional (needed by some Mastodon API clients) | 659 | # optional (needed by some Mastodon API clients) |
| 632 | location /.well-known/host-meta { | 660 | location /.well-known/host-meta { |
| 633 | proxy_pass http://localhost:8001; | 661 | proxy_pass http://localhost:8001; |
| 634 | proxy_set_header Host $http_host; | 662 | proxy_set_header Host $http_host; |
| 663 | proxy_set_header X-Forwarded-For $remote_addr; | ||
| 635 | } | 664 | } |
| 636 | .Ed | 665 | .Ed |
| 637 | .Pp | 666 | .Pp |
diff --git a/examples/static-linking-with-musl.md b/examples/static-linking-with-musl.md new file mode 100644 index 0000000..b14132c --- /dev/null +++ b/examples/static-linking-with-musl.md | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | # How to build a statically linked Snac with musl | ||
| 2 | |||
| 3 | Prepare the environment | ||
| 4 | ``` | ||
| 5 | mkdir build | ||
| 6 | cd build | ||
| 7 | export BUILD_TARGET=$PWD | ||
| 8 | export CC="musl-gcc" | ||
| 9 | ``` | ||
| 10 | |||
| 11 | Download and build latest zlib | ||
| 12 | ``` | ||
| 13 | wget http://zlib.net/current/zlib.tar.gz | ||
| 14 | tar xvf zlib.tar.gz | ||
| 15 | cd zlib-1.3.1/ | ||
| 16 | ./configure --prefix=$BUILD_TARGET --static | ||
| 17 | make | ||
| 18 | make install | ||
| 19 | cd .. | ||
| 20 | ``` | ||
| 21 | |||
| 22 | Download and build latest openssl | ||
| 23 | ``` | ||
| 24 | wget https://github.com/openssl/openssl/releases/download/openssl-3.4.0/openssl-3.4.0.tar.gz | ||
| 25 | tar xvf openssl-3.4.0.tar.gz | ||
| 26 | cd openssl-3.4.0 | ||
| 27 | CC="musl-gcc -fPIE -pie -static -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/" \ | ||
| 28 | ./Configure no-shared no-async --prefix=$BUILD_TARGET --openssldir=$BUILD_TARGET/ssl linux-x86_64 | ||
| 29 | make depend | ||
| 30 | make | ||
| 31 | make install | ||
| 32 | cd .. | ||
| 33 | ``` | ||
| 34 | |||
| 35 | Download and build latest curl | ||
| 36 | ``` | ||
| 37 | wget https://curl.se/download/curl-7.88.1.tar.gz | ||
| 38 | tar xvf curl-7.88.1.tar.gz | ||
| 39 | cd curl-7.88.1 | ||
| 40 | ./configure --disable-shared --enable-static --disable-silent-rules \ | ||
| 41 | --disable-debug --disable-warnings --disable-werror \ | ||
| 42 | --disable-curldebug --disable-symbol-hiding --disable-ares \ | ||
| 43 | --disable-rt --disable-ech --disable-dependency-tracking \ | ||
| 44 | --disable-libtool-lock --enable-http --disable-ftp \ | ||
| 45 | --disable-file --disable-ldap --disable-ldaps \ | ||
| 46 | --disable-rtsp --disable-proxy --disable-dict \ | ||
| 47 | --disable-telnet --disable-tftp --disable-pop3 \ | ||
| 48 | --disable-imap --disable-smb --disable-smtp --disable-gopher \ | ||
| 49 | --disable-mqtt --disable-manual --disable-libcurl-option --disable-ipv6 \ | ||
| 50 | --disable-openssl-auto-load-config --disable-versioned-symbols | ||
| 51 | --disable-verbose --disable-sspi --disable-crypto-auth \ | ||
| 52 | --disable-ntlm --disable-ntlm-wb --disable-tls-srp \ | ||
| 53 | --disable-unix-sockets --disable-cookies --disable-socketpair \ | ||
| 54 | --disable-http-auth --disable-doh --disable-mime --disable-dateparse \ | ||
| 55 | --disable-netrc --disable-progress-meter --disable-dnsshuffle \ | ||
| 56 | --disable-get-easy-options --disable-alt-svc --disable-websockets \ | ||
| 57 | --without-brotli --without-zstd --without-libpsl --without-libgsasl \ | ||
| 58 | --without-librtmp --without-winidn --disable-threaded-resolver \ | ||
| 59 | --with-openssl=$BUILD_TARGET/ --with-zlib=$BUILD_TARGET/ \ | ||
| 60 | --prefix=$BUILD_TARGET/ | ||
| 61 | make | ||
| 62 | make install | ||
| 63 | cd .. | ||
| 64 | ``` | ||
| 65 | |||
| 66 | Download and build latest snac2 | ||
| 67 | ``` | ||
| 68 | git clone https://codeberg.org/grunfink/snac2.git # or cd to your existing repo | ||
| 69 | cd snac2 | ||
| 70 | make CFLAGS="-g -Wall -Wextra -pedantic -static -DWITHOUT_SHM" \ | ||
| 71 | LDFLAGS="-L$BUILD_TARGET/lib64 -lcurl -lssl -lcrypto -lz" \ | ||
| 72 | PREFIX=$BUILD_TARGET | ||
| 73 | make install PREFIX=$BUILD_TARGET | ||
| 74 | cd .. | ||
| 75 | ``` | ||
| 76 | |||
| 77 | Finally a statically linked snac is ready at $BUILD_TARGET/bin | ||
| @@ -163,14 +163,26 @@ static xs_str *format_line(const char *line, xs_list **attach) | |||
| 163 | const char *mime = xs_mime_by_ext(img_url); | 163 | const char *mime = xs_mime_by_ext(img_url); |
| 164 | 164 | ||
| 165 | if (attach != NULL && xs_startswith(mime, "image/")) { | 165 | if (attach != NULL && xs_startswith(mime, "image/")) { |
| 166 | xs *d = xs_dict_new(); | 166 | const xs_dict *ad; |
| 167 | 167 | int add = 1; | |
| 168 | d = xs_dict_append(d, "mediaType", mime); | 168 | |
| 169 | d = xs_dict_append(d, "url", img_url); | 169 | xs_list_foreach(*attach, ad) { |
| 170 | d = xs_dict_append(d, "name", alt_text); | 170 | if (strcmp(xs_dict_get_def(ad, "url", ""), img_url) == 0) { |
| 171 | d = xs_dict_append(d, "type", "Image"); | 171 | add = 0; |
| 172 | 172 | break; | |
| 173 | *attach = xs_list_append(*attach, d); | 173 | } |
| 174 | } | ||
| 175 | |||
| 176 | if (add) { | ||
| 177 | xs *d = xs_dict_new(); | ||
| 178 | |||
| 179 | d = xs_dict_append(d, "mediaType", mime); | ||
| 180 | d = xs_dict_append(d, "url", img_url); | ||
| 181 | d = xs_dict_append(d, "name", alt_text); | ||
| 182 | d = xs_dict_append(d, "type", "Image"); | ||
| 183 | |||
| 184 | *attach = xs_list_append(*attach, d); | ||
| 185 | } | ||
| 174 | } | 186 | } |
| 175 | else { | 187 | else { |
| 176 | xs *link = xs_fmt("<a href=\"%s\">%s</a>", img_url, alt_text); | 188 | xs *link = xs_fmt("<a href=\"%s\">%s</a>", img_url, alt_text); |
| @@ -191,14 +203,26 @@ static xs_str *format_line(const char *line, xs_list **attach) | |||
| 191 | 203 | ||
| 192 | if (attach != NULL && xs_startswith(mime, "image/")) { | 204 | if (attach != NULL && xs_startswith(mime, "image/")) { |
| 193 | /* if it's a link to an image, insert it as an attachment */ | 205 | /* if it's a link to an image, insert it as an attachment */ |
| 194 | xs *d = xs_dict_new(); | 206 | const xs_dict *ad; |
| 207 | int add = 1; | ||
| 208 | |||
| 209 | xs_list_foreach(*attach, ad) { | ||
| 210 | if (strcmp(xs_dict_get_def(ad, "url", ""), v2) == 0) { | ||
| 211 | add = 0; | ||
| 212 | break; | ||
| 213 | } | ||
| 214 | } | ||
| 195 | 215 | ||
| 196 | d = xs_dict_append(d, "mediaType", mime); | 216 | if (add) { |
| 197 | d = xs_dict_append(d, "url", v2); | 217 | xs *d = xs_dict_new(); |
| 198 | d = xs_dict_append(d, "name", ""); | 218 | |
| 199 | d = xs_dict_append(d, "type", "Image"); | 219 | d = xs_dict_append(d, "mediaType", mime); |
| 220 | d = xs_dict_append(d, "url", v2); | ||
| 221 | d = xs_dict_append(d, "name", ""); | ||
| 222 | d = xs_dict_append(d, "type", "Image"); | ||
| 200 | 223 | ||
| 201 | *attach = xs_list_append(*attach, d); | 224 | *attach = xs_list_append(*attach, d); |
| 225 | } | ||
| 202 | } | 226 | } |
| 203 | else { | 227 | else { |
| 204 | xs *s1 = xs_fmt("<a href=\"%s\" target=\"_blank\">%s</a>", v2, u); | 228 | xs *s1 = xs_fmt("<a href=\"%s\" target=\"_blank\">%s</a>", v2, u); |
| @@ -29,9 +29,18 @@ int login(snac *snac, const xs_dict *headers) | |||
| 29 | xs *l1 = xs_split_n(s2, ":", 1); | 29 | xs *l1 = xs_split_n(s2, ":", 1); |
| 30 | 30 | ||
| 31 | if (xs_list_len(l1) == 2) { | 31 | if (xs_list_len(l1) == 2) { |
| 32 | logged_in = check_password( | 32 | const char *user = xs_list_get(l1, 0); |
| 33 | xs_list_get(l1, 0), xs_list_get(l1, 1), | 33 | const char *pwd = xs_list_get(l1, 1); |
| 34 | xs_dict_get(snac->config, "passwd")); | 34 | const char *addr = xs_or(xs_dict_get(headers, "remote-addr"), |
| 35 | xs_dict_get(headers, "x-forwarded-for")); | ||
| 36 | |||
| 37 | if (badlogin_check(user, addr)) { | ||
| 38 | logged_in = check_password(user, pwd, | ||
| 39 | xs_dict_get(snac->config, "passwd")); | ||
| 40 | |||
| 41 | if (!logged_in) | ||
| 42 | badlogin_inc(user, addr); | ||
| 43 | } | ||
| 35 | } | 44 | } |
| 36 | } | 45 | } |
| 37 | 46 | ||
| @@ -633,6 +642,17 @@ xs_html *html_user_head(snac *user, const char *desc, const char *url) | |||
| 633 | else | 642 | else |
| 634 | s_desc = xs_dup(desc); | 643 | s_desc = xs_dup(desc); |
| 635 | 644 | ||
| 645 | /* show metrics in og:description? */ | ||
| 646 | if (xs_is_true(xs_dict_get(user->config, "show_contact_metrics"))) { | ||
| 647 | xs *fwers = follower_list(user); | ||
| 648 | xs *fwing = following_list(user); | ||
| 649 | |||
| 650 | xs *s1 = xs_fmt(L("%d following, %d followers · "), | ||
| 651 | xs_list_len(fwing), xs_list_len(fwers)); | ||
| 652 | |||
| 653 | s_desc = xs_str_prepend_i(s_desc, s1); | ||
| 654 | } | ||
| 655 | |||
| 636 | /* shorten desc to a reasonable size */ | 656 | /* shorten desc to a reasonable size */ |
| 637 | for (n = 0; s_desc[n]; n++) { | 657 | for (n = 0; s_desc[n]; n++) { |
| 638 | if (n > 512 && (s_desc[n] == ' ' || s_desc[n] == '\n')) | 658 | if (n > 512 && (s_desc[n] == ' ' || s_desc[n] == '\n')) |
| @@ -2041,6 +2061,23 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2041 | if (content && xs_str_in(content, o_href) != -1) | 2061 | if (content && xs_str_in(content, o_href) != -1) |
| 2042 | continue; | 2062 | continue; |
| 2043 | 2063 | ||
| 2064 | /* do this attachment include an icon? */ | ||
| 2065 | const xs_dict *icon = xs_dict_get(a, "icon"); | ||
| 2066 | if (xs_type(icon) == XSTYPE_DICT) { | ||
| 2067 | const char *icon_mtype = xs_dict_get(icon, "mediaType"); | ||
| 2068 | const char *icon_url = xs_dict_get(icon, "url"); | ||
| 2069 | |||
| 2070 | if (icon_mtype && icon_url && xs_startswith(icon_mtype, "image/")) { | ||
| 2071 | xs_html_add(content_attachments, | ||
| 2072 | xs_html_tag("a", | ||
| 2073 | xs_html_attr("href", icon_url), | ||
| 2074 | xs_html_attr("target", "_blank"), | ||
| 2075 | xs_html_sctag("img", | ||
| 2076 | xs_html_attr("loading", "lazy"), | ||
| 2077 | xs_html_attr("src", icon_url)))); | ||
| 2078 | } | ||
| 2079 | } | ||
| 2080 | |||
| 2044 | xs *href = make_url(o_href, proxy, 0); | 2081 | xs *href = make_url(o_href, proxy, 0); |
| 2045 | 2082 | ||
| 2046 | if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { | 2083 | if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { |
| @@ -2996,9 +3033,54 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2996 | } | 3033 | } |
| 2997 | else { | 3034 | else { |
| 2998 | const char *q = xs_dict_get(q_vars, "q"); | 3035 | const char *q = xs_dict_get(q_vars, "q"); |
| 3036 | xs *url_acct = NULL; | ||
| 3037 | |||
| 3038 | /* searching for an URL? */ | ||
| 3039 | if (q && xs_match(q, "https://*|http://*")) { | ||
| 3040 | /* may by an actor; try a webfinger */ | ||
| 3041 | xs *actor_obj = NULL; | ||
| 3042 | |||
| 3043 | if (valid_status(webfinger_request(q, &actor_obj, &url_acct))) { | ||
| 3044 | /* it's an actor; do the dirty trick of changing q to the account name */ | ||
| 3045 | q = url_acct; | ||
| 3046 | } | ||
| 3047 | else { | ||
| 3048 | /* if it's not already here, try to bring it to the user's timeline */ | ||
| 3049 | xs *md5 = xs_md5_hex(q, strlen(q)); | ||
| 3050 | |||
| 3051 | if (!timeline_here(&snac, md5)) { | ||
| 3052 | xs *object = NULL; | ||
| 3053 | int status; | ||
| 3054 | |||
| 3055 | status = activitypub_request(&snac, q, &object); | ||
| 3056 | snac_debug(&snac, 1, xs_fmt("Request searched URL %s %d", q, status)); | ||
| 3057 | |||
| 3058 | if (valid_status(status)) { | ||
| 3059 | /* got it; also request the actor */ | ||
| 3060 | const char *attr_to = get_atto(object); | ||
| 3061 | |||
| 3062 | if (!xs_is_null(attr_to)) { | ||
| 3063 | status = actor_request(&snac, attr_to, &actor_obj); | ||
| 3064 | |||
| 3065 | snac_debug(&snac, 1, xs_fmt("Request author %s of %s %d", attr_to, q, status)); | ||
| 3066 | |||
| 3067 | if (valid_status(status)) { | ||
| 3068 | /* add the actor */ | ||
| 3069 | actor_add(attr_to, actor_obj); | ||
| 3070 | |||
| 3071 | /* add the post to the timeline */ | ||
| 3072 | timeline_add(&snac, q, object); | ||
| 3073 | } | ||
| 3074 | } | ||
| 3075 | } | ||
| 3076 | } | ||
| 3077 | } | ||
| 3078 | |||
| 3079 | /* fall through */ | ||
| 3080 | } | ||
| 2999 | 3081 | ||
| 3000 | if (q && *q) { | 3082 | if (q && *q) { |
| 3001 | if (xs_regex_match(q, "^@?[a-zA-Z0-9_]+@[a-zA-Z0-9-]+\\.")) { | 3083 | if (xs_regex_match(q, "^@?[a-zA-Z0-9._]+@[a-zA-Z0-9-]+\\.")) { |
| 3002 | /** search account **/ | 3084 | /** search account **/ |
| 3003 | xs *actor = NULL; | 3085 | xs *actor = NULL; |
| 3004 | xs *acct = NULL; | 3086 | xs *acct = NULL; |
| @@ -279,6 +279,7 @@ void httpd_connection(FILE *f) | |||
| 279 | xs *payload = NULL; | 279 | xs *payload = NULL; |
| 280 | xs *etag = NULL; | 280 | xs *etag = NULL; |
| 281 | xs *last_modified = NULL; | 281 | xs *last_modified = NULL; |
| 282 | xs *link = NULL; | ||
| 282 | int p_size = 0; | 283 | int p_size = 0; |
| 283 | const char *p; | 284 | const char *p; |
| 284 | int fcgi_id; | 285 | int fcgi_id; |
| @@ -326,7 +327,7 @@ void httpd_connection(FILE *f) | |||
| 326 | status = oauth_get_handler(req, q_path, &body, &b_size, &ctype); | 327 | status = oauth_get_handler(req, q_path, &body, &b_size, &ctype); |
| 327 | 328 | ||
| 328 | if (status == 0) | 329 | if (status == 0) |
| 329 | status = mastoapi_get_handler(req, q_path, &body, &b_size, &ctype); | 330 | status = mastoapi_get_handler(req, q_path, &body, &b_size, &ctype, &link); |
| 330 | #endif /* NO_MASTODON_API */ | 331 | #endif /* NO_MASTODON_API */ |
| 331 | 332 | ||
| 332 | if (status == 0) | 333 | if (status == 0) |
| @@ -426,6 +427,8 @@ void httpd_connection(FILE *f) | |||
| 426 | headers = xs_dict_append(headers, "etag", etag); | 427 | headers = xs_dict_append(headers, "etag", etag); |
| 427 | if (!xs_is_null(last_modified)) | 428 | if (!xs_is_null(last_modified)) |
| 428 | headers = xs_dict_append(headers, "last-modified", last_modified); | 429 | headers = xs_dict_append(headers, "last-modified", last_modified); |
| 430 | if (!xs_is_null(link)) | ||
| 431 | headers = xs_dict_append(headers, "Link", link); | ||
| 429 | 432 | ||
| 430 | /* if there are any additional headers, add them */ | 433 | /* if there are any additional headers, add them */ |
| 431 | const xs_dict *more_headers = xs_dict_get(srv_config, "http_headers"); | 434 | const xs_dict *more_headers = xs_dict_get(srv_config, "http_headers"); |
| @@ -775,6 +778,26 @@ void httpd(void) | |||
| 775 | xs *shm_name = NULL; | 778 | xs *shm_name = NULL; |
| 776 | sem_t anon_job_sem; | 779 | sem_t anon_job_sem; |
| 777 | xs *pidfile = xs_fmt("%s/server.pid", srv_basedir); | 780 | xs *pidfile = xs_fmt("%s/server.pid", srv_basedir); |
| 781 | int pidfd; | ||
| 782 | |||
| 783 | { | ||
| 784 | /* do some pidfile locking acrobatics */ | ||
| 785 | if ((pidfd = open(pidfile, O_RDWR | O_CREAT, 0660)) == -1) { | ||
| 786 | srv_log(xs_fmt("Cannot create pidfile %s -- cannot continue", pidfile)); | ||
| 787 | return; | ||
| 788 | } | ||
| 789 | |||
| 790 | if (lockf(pidfd, F_TLOCK, 1) == -1) { | ||
| 791 | srv_log(xs_fmt("Cannot lock pidfile %s -- server already running?", pidfile)); | ||
| 792 | close(pidfd); | ||
| 793 | return; | ||
| 794 | } | ||
| 795 | |||
| 796 | ftruncate(pidfd, 0); | ||
| 797 | |||
| 798 | xs *s = xs_fmt("%d\n", (int)getpid()); | ||
| 799 | write(pidfd, s, strlen(s)); | ||
| 800 | } | ||
| 778 | 801 | ||
| 779 | address = xs_dict_get(srv_config, "address"); | 802 | address = xs_dict_get(srv_config, "address"); |
| 780 | 803 | ||
| @@ -810,17 +833,6 @@ void httpd(void) | |||
| 810 | srv_log(xs_fmt("httpd%s start %s %s", p_state->use_fcgi ? " (FastCGI)" : "", | 833 | srv_log(xs_fmt("httpd%s start %s %s", p_state->use_fcgi ? " (FastCGI)" : "", |
| 811 | full_address, USER_AGENT)); | 834 | full_address, USER_AGENT)); |
| 812 | 835 | ||
| 813 | { | ||
| 814 | FILE *f; | ||
| 815 | |||
| 816 | if ((f = fopen(pidfile, "w")) != NULL) { | ||
| 817 | fprintf(f, "%d\n", getpid()); | ||
| 818 | fclose(f); | ||
| 819 | } | ||
| 820 | else | ||
| 821 | srv_log(xs_fmt("Cannot create %s: %s", pidfile, strerror(errno))); | ||
| 822 | } | ||
| 823 | |||
| 824 | /* show the number of usable file descriptors */ | 836 | /* show the number of usable file descriptors */ |
| 825 | struct rlimit r; | 837 | struct rlimit r; |
| 826 | getrlimit(RLIMIT_NOFILE, &r); | 838 | getrlimit(RLIMIT_NOFILE, &r); |
| @@ -293,47 +293,54 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 293 | snac snac; | 293 | snac snac; |
| 294 | 294 | ||
| 295 | if (user_open(&snac, login)) { | 295 | if (user_open(&snac, login)) { |
| 296 | /* check the login + password */ | 296 | const char *addr = xs_or(xs_dict_get(req, "remote-addr"), |
| 297 | if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) { | 297 | xs_dict_get(req, "x-forwarded-for")); |
| 298 | /* success! redirect to the desired uri */ | ||
| 299 | xs *code = random_str(); | ||
| 300 | 298 | ||
| 301 | xs_free(*body); | 299 | if (badlogin_check(login, addr)) { |
| 300 | /* check the login + password */ | ||
| 301 | if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) { | ||
| 302 | /* success! redirect to the desired uri */ | ||
| 303 | xs *code = random_str(); | ||
| 302 | 304 | ||
| 303 | if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) { | 305 | xs_free(*body); |
| 304 | *body = xs_dup(code); | ||
| 305 | } | ||
| 306 | else { | ||
| 307 | if (xs_str_in(redir, "?") != -1) | ||
| 308 | *body = xs_fmt("%s&code=%s", redir, code); | ||
| 309 | else | ||
| 310 | *body = xs_fmt("%s?code=%s", redir, code); | ||
| 311 | 306 | ||
| 312 | status = HTTP_STATUS_SEE_OTHER; | 307 | if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) { |
| 313 | } | 308 | *body = xs_dup(code); |
| 309 | } | ||
| 310 | else { | ||
| 311 | if (xs_str_in(redir, "?") != -1) | ||
| 312 | *body = xs_fmt("%s&code=%s", redir, code); | ||
| 313 | else | ||
| 314 | *body = xs_fmt("%s?code=%s", redir, code); | ||
| 314 | 315 | ||
| 315 | /* if there is a state, add it */ | 316 | status = HTTP_STATUS_SEE_OTHER; |
| 316 | if (!xs_is_null(state) && *state) { | 317 | } |
| 317 | *body = xs_str_cat(*body, "&state="); | ||
| 318 | *body = xs_str_cat(*body, state); | ||
| 319 | } | ||
| 320 | 318 | ||
| 321 | srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s", | 319 | /* if there is a state, add it */ |
| 320 | if (!xs_is_null(state) && *state) { | ||
| 321 | *body = xs_str_cat(*body, "&state="); | ||
| 322 | *body = xs_str_cat(*body, state); | ||
| 323 | } | ||
| 324 | |||
| 325 | srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s", | ||
| 322 | login, *body)); | 326 | login, *body)); |
| 323 | 327 | ||
| 324 | /* assign the login to the app */ | 328 | /* assign the login to the app */ |
| 325 | xs *app = app_get(cid); | 329 | xs *app = app_get(cid); |
| 326 | 330 | ||
| 327 | if (app != NULL) { | 331 | if (app != NULL) { |
| 328 | app = xs_dict_set(app, "uid", login); | 332 | app = xs_dict_set(app, "uid", login); |
| 329 | app = xs_dict_set(app, "code", code); | 333 | app = xs_dict_set(app, "code", code); |
| 330 | app_add(cid, app); | 334 | app_add(cid, app); |
| 335 | } | ||
| 336 | else | ||
| 337 | srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid)); | ||
| 338 | } | ||
| 339 | else { | ||
| 340 | srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login)); | ||
| 341 | badlogin_inc(login, addr); | ||
| 331 | } | 342 | } |
| 332 | else | ||
| 333 | srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid)); | ||
| 334 | } | 343 | } |
| 335 | else | ||
| 336 | srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login)); | ||
| 337 | 344 | ||
| 338 | user_free(&snac); | 345 | user_free(&snac); |
| 339 | } | 346 | } |
| @@ -474,29 +481,36 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 474 | snac user; | 481 | snac user; |
| 475 | 482 | ||
| 476 | if (user_open(&user, login)) { | 483 | if (user_open(&user, login)) { |
| 477 | /* check the login + password */ | 484 | const char *addr = xs_or(xs_dict_get(req, "remote-addr"), |
| 478 | if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) { | 485 | xs_dict_get(req, "x-forwarded-for")); |
| 479 | /* success! create a new token */ | ||
| 480 | xs *tokid = random_str(); | ||
| 481 | 486 | ||
| 482 | srv_debug(1, xs_fmt("x-snac-new-token: " | 487 | if (badlogin_check(login, addr)) { |
| 488 | /* check the login + password */ | ||
| 489 | if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) { | ||
| 490 | /* success! create a new token */ | ||
| 491 | xs *tokid = random_str(); | ||
| 492 | |||
| 493 | srv_debug(1, xs_fmt("x-snac-new-token: " | ||
| 483 | "successful login for %s, new token %s", login, tokid)); | 494 | "successful login for %s, new token %s", login, tokid)); |
| 484 | 495 | ||
| 485 | xs *token = xs_dict_new(); | 496 | xs *token = xs_dict_new(); |
| 486 | token = xs_dict_append(token, "token", tokid); | 497 | token = xs_dict_append(token, "token", tokid); |
| 487 | token = xs_dict_append(token, "client_id", "snac-client"); | 498 | token = xs_dict_append(token, "client_id", "snac-client"); |
| 488 | token = xs_dict_append(token, "client_secret", ""); | 499 | token = xs_dict_append(token, "client_secret", ""); |
| 489 | token = xs_dict_append(token, "uid", login); | 500 | token = xs_dict_append(token, "uid", login); |
| 490 | token = xs_dict_append(token, "code", ""); | 501 | token = xs_dict_append(token, "code", ""); |
| 491 | 502 | ||
| 492 | token_add(tokid, token); | 503 | token_add(tokid, token); |
| 493 | 504 | ||
| 494 | *ctype = "text/plain"; | 505 | *ctype = "text/plain"; |
| 495 | xs_free(*body); | 506 | xs_free(*body); |
| 496 | *body = xs_dup(tokid); | 507 | *body = xs_dup(tokid); |
| 497 | } | 508 | } |
| 509 | else | ||
| 510 | badlogin_inc(login, addr); | ||
| 498 | 511 | ||
| 499 | user_free(&user); | 512 | user_free(&user); |
| 513 | } | ||
| 500 | } | 514 | } |
| 501 | } | 515 | } |
| 502 | } | 516 | } |
| @@ -898,7 +912,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 898 | const char *o_href = xs_dict_get(v, "href"); | 912 | const char *o_href = xs_dict_get(v, "href"); |
| 899 | const char *name = xs_dict_get(v, "name"); | 913 | const char *name = xs_dict_get(v, "name"); |
| 900 | 914 | ||
| 901 | if (xs_match(type, "image/*|video/*|Image|Video")) { /* */ | 915 | if (xs_match(type, "image/*|video/*|audio/*|Image|Video")) { /* */ |
| 902 | xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); | 916 | xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); |
| 903 | xs *href = make_url(o_href, proxy, 1); | 917 | xs *href = make_url(o_href, proxy, 1); |
| 904 | 918 | ||
| @@ -910,7 +924,8 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 910 | d = xs_dict_append(d, "remote_url", href); | 924 | d = xs_dict_append(d, "remote_url", href); |
| 911 | d = xs_dict_append(d, "description", name); | 925 | d = xs_dict_append(d, "description", name); |
| 912 | 926 | ||
| 913 | d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" : "image"); | 927 | d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" : |
| 928 | (*type == 'a' || *type == 'A') ? "audio" : "image"); | ||
| 914 | 929 | ||
| 915 | matt = xs_list_append(matt, d); | 930 | matt = xs_list_append(matt, d); |
| 916 | } | 931 | } |
| @@ -990,7 +1005,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 990 | const char *o_url = xs_dict_get(icon, "url"); | 1005 | const char *o_url = xs_dict_get(icon, "url"); |
| 991 | 1006 | ||
| 992 | if (!xs_is_null(o_url)) { | 1007 | if (!xs_is_null(o_url)) { |
| 993 | xs *url = make_url(o_url, snac->actor, 1); | 1008 | xs *url = make_url(o_url, snac ? snac->actor : NULL, 1); |
| 994 | xs *nm = xs_strip_chars_i(xs_dup(name), ":"); | 1009 | xs *nm = xs_strip_chars_i(xs_dup(name), ":"); |
| 995 | 1010 | ||
| 996 | d1 = xs_dict_append(d1, "shortcode", nm); | 1011 | d1 = xs_dict_append(d1, "shortcode", nm); |
| @@ -1193,10 +1208,13 @@ int process_auth_token(snac *snac, const xs_dict *req) | |||
| 1193 | return logged_in; | 1208 | return logged_in; |
| 1194 | } | 1209 | } |
| 1195 | 1210 | ||
| 1211 | |||
| 1196 | void credentials_get(char **body, char **ctype, int *status, snac snac) | 1212 | void credentials_get(char **body, char **ctype, int *status, snac snac) |
| 1197 | { | 1213 | { |
| 1198 | xs *acct = xs_dict_new(); | 1214 | xs *acct = xs_dict_new(); |
| 1199 | 1215 | ||
| 1216 | const xs_val *bot = xs_dict_get(snac.config, "bot"); | ||
| 1217 | |||
| 1200 | acct = xs_dict_append(acct, "id", snac.md5); | 1218 | acct = xs_dict_append(acct, "id", snac.md5); |
| 1201 | acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid")); | 1219 | acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid")); |
| 1202 | acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid")); | 1220 | acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid")); |
| @@ -1206,7 +1224,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac) | |||
| 1206 | acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio")); | 1224 | acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio")); |
| 1207 | acct = xs_dict_append(acct, "url", snac.actor); | 1225 | acct = xs_dict_append(acct, "url", snac.actor); |
| 1208 | acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); | 1226 | acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); |
| 1209 | acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot")); | 1227 | acct = xs_dict_append(acct, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE)); |
| 1210 | acct = xs_dict_append(acct, "emojis", xs_stock(XSTYPE_LIST)); | 1228 | acct = xs_dict_append(acct, "emojis", xs_stock(XSTYPE_LIST)); |
| 1211 | 1229 | ||
| 1212 | xs *src = xs_json_loads("{\"privacy\":\"public\", \"language\":\"en\"," | 1230 | xs *src = xs_json_loads("{\"privacy\":\"public\", \"language\":\"en\"," |
| @@ -1220,7 +1238,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac) | |||
| 1220 | src = xs_dict_set(src, "sensitive", | 1238 | src = xs_dict_set(src, "sensitive", |
| 1221 | strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); | 1239 | strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 1222 | 1240 | ||
| 1223 | src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot")); | 1241 | src = xs_dict_set(src, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE)); |
| 1224 | 1242 | ||
| 1225 | xs *avatar = NULL; | 1243 | xs *avatar = NULL; |
| 1226 | const char *av = xs_dict_get(snac.config, "avatar"); | 1244 | const char *av = xs_dict_get(snac.config, "avatar"); |
| @@ -1319,7 +1337,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn | |||
| 1319 | 1337 | ||
| 1320 | const char *max_id = xs_dict_get(args, "max_id"); | 1338 | const char *max_id = xs_dict_get(args, "max_id"); |
| 1321 | const char *since_id = xs_dict_get(args, "since_id"); | 1339 | const char *since_id = xs_dict_get(args, "since_id"); |
| 1322 | const char *min_id = xs_dict_get(args, "min_id"); | 1340 | const char *min_id = xs_dict_get(args, "min_id"); /* unsupported old-to-new navigation */ |
| 1323 | const char *limit_s = xs_dict_get(args, "limit"); | 1341 | const char *limit_s = xs_dict_get(args, "limit"); |
| 1324 | int limit = 0; | 1342 | int limit = 0; |
| 1325 | int cnt = 0; | 1343 | int cnt = 0; |
| @@ -1330,7 +1348,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn | |||
| 1330 | if (limit == 0) | 1348 | if (limit == 0) |
| 1331 | limit = 20; | 1349 | limit = 20; |
| 1332 | 1350 | ||
| 1333 | if (index_desc_first(f, md5, 0)) { | 1351 | if (min_id == NULL && index_desc_first(f, md5, 0)) { |
| 1334 | do { | 1352 | do { |
| 1335 | xs *msg = NULL; | 1353 | xs *msg = NULL; |
| 1336 | 1354 | ||
| @@ -1348,13 +1366,6 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn | |||
| 1348 | break; | 1366 | break; |
| 1349 | } | 1367 | } |
| 1350 | 1368 | ||
| 1351 | /* only returns entries newer than min_id */ | ||
| 1352 | /* what does really "Return results immediately newer than ID" mean? */ | ||
| 1353 | if (min_id) { | ||
| 1354 | if (strcmp(md5, MID_TO_MD5(min_id)) == 0) | ||
| 1355 | break; | ||
| 1356 | } | ||
| 1357 | |||
| 1358 | /* get the entry */ | 1369 | /* get the entry */ |
| 1359 | if (user) { | 1370 | if (user) { |
| 1360 | if (!valid_status(timeline_get_by_md5(user, md5, &msg))) | 1371 | if (!valid_status(timeline_get_by_md5(user, md5, &msg))) |
| @@ -1438,8 +1449,35 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn | |||
| 1438 | } | 1449 | } |
| 1439 | 1450 | ||
| 1440 | 1451 | ||
| 1452 | xs_str *timeline_link_header(const char *endpoint, xs_list *timeline) | ||
| 1453 | /* returns a Link header with paging information */ | ||
| 1454 | { | ||
| 1455 | xs_str *s = NULL; | ||
| 1456 | |||
| 1457 | if (xs_list_len(timeline) == 0) | ||
| 1458 | return NULL; | ||
| 1459 | |||
| 1460 | const xs_dict *first_st = xs_list_get(timeline, 0); | ||
| 1461 | const xs_dict *last_st = xs_list_get(timeline, -1); | ||
| 1462 | const char *first_id = xs_dict_get(first_st, "id"); | ||
| 1463 | const char *last_id = xs_dict_get(last_st, "id"); | ||
| 1464 | const char *host = xs_dict_get(srv_config, "host"); | ||
| 1465 | const char *protocol = xs_dict_get_def(srv_config, "protocol", "https"); | ||
| 1466 | |||
| 1467 | s = xs_fmt( | ||
| 1468 | "<%s:/" "/%s%s?max_id=%s>; rel=\"next\", " | ||
| 1469 | "<%s:/" "/%s%s?since_id=%s>; rel=\"prev\"", | ||
| 1470 | protocol, host, endpoint, last_id, | ||
| 1471 | protocol, host, endpoint, first_id); | ||
| 1472 | |||
| 1473 | srv_debug(1, xs_fmt("timeline_link_header %s", s)); | ||
| 1474 | |||
| 1475 | return s; | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | |||
| 1441 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, | 1479 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, |
| 1442 | char **body, int *b_size, char **ctype) | 1480 | char **body, int *b_size, char **ctype, xs_str **link) |
| 1443 | { | 1481 | { |
| 1444 | (void)b_size; | 1482 | (void)b_size; |
| 1445 | 1483 | ||
| @@ -1699,6 +1737,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1699 | xs *ifn = user_index_fn(&snac1, "private"); | 1737 | xs *ifn = user_index_fn(&snac1, "private"); |
| 1700 | xs *out = mastoapi_timeline(&snac1, args, ifn); | 1738 | xs *out = mastoapi_timeline(&snac1, args, ifn); |
| 1701 | 1739 | ||
| 1740 | *link = timeline_link_header("/api/v1/timelines/home", out); | ||
| 1741 | |||
| 1702 | *body = xs_json_dumps(out, 4); | 1742 | *body = xs_json_dumps(out, 4); |
| 1703 | *ctype = "application/json"; | 1743 | *ctype = "application/json"; |
| 1704 | status = HTTP_STATUS_OK; | 1744 | status = HTTP_STATUS_OK; |
| @@ -1763,8 +1803,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1763 | xs *out = xs_list_new(); | 1803 | xs *out = xs_list_new(); |
| 1764 | const xs_dict *v; | 1804 | const xs_dict *v; |
| 1765 | const xs_list *excl = xs_dict_get(args, "exclude_types[]"); | 1805 | const xs_list *excl = xs_dict_get(args, "exclude_types[]"); |
| 1806 | const char *min_id = xs_dict_get(args, "min_id"); | ||
| 1766 | const char *max_id = xs_dict_get(args, "max_id"); | 1807 | const char *max_id = xs_dict_get(args, "max_id"); |
| 1767 | 1808 | ||
| 1809 | if (dbglevel) { | ||
| 1810 | xs *js = xs_json_dumps(args, 0); | ||
| 1811 | srv_debug(1, xs_fmt("mastoapi_notifications args %s", js)); | ||
| 1812 | } | ||
| 1813 | |||
| 1768 | xs_list_foreach(l, v) { | 1814 | xs_list_foreach(l, v) { |
| 1769 | xs *noti = notify_get(&snac1, v); | 1815 | xs *noti = notify_get(&snac1, v); |
| 1770 | 1816 | ||
| @@ -1795,6 +1841,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1795 | continue; | 1841 | continue; |
| 1796 | } | 1842 | } |
| 1797 | 1843 | ||
| 1844 | if (min_id) { | ||
| 1845 | if (strcmp(fid, min_id) <= 0) { | ||
| 1846 | continue; | ||
| 1847 | } | ||
| 1848 | } | ||
| 1849 | |||
| 1798 | /* convert the type */ | 1850 | /* convert the type */ |
| 1799 | if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) | 1851 | if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) |
| 1800 | type = "favourite"; | 1852 | type = "favourite"; |
| @@ -1842,6 +1894,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1842 | out = xs_list_append(out, mn); | 1894 | out = xs_list_append(out, mn); |
| 1843 | } | 1895 | } |
| 1844 | 1896 | ||
| 1897 | srv_debug(1, xs_fmt("mastoapi_notifications count %d", xs_list_len(out))); | ||
| 1898 | |||
| 1845 | *body = xs_json_dumps(out, 4); | 1899 | *body = xs_json_dumps(out, 4); |
| 1846 | *ctype = "application/json"; | 1900 | *ctype = "application/json"; |
| 1847 | status = HTTP_STATUS_OK; | 1901 | status = HTTP_STATUS_OK; |
| @@ -2273,9 +2327,22 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2273 | } | 2327 | } |
| 2274 | else | 2328 | else |
| 2275 | if (strcmp(cmd, "/v1/markers") == 0) { /** **/ | 2329 | if (strcmp(cmd, "/v1/markers") == 0) { /** **/ |
| 2276 | *body = xs_dup("{}"); | 2330 | if (logged_in) { |
| 2277 | *ctype = "application/json"; | 2331 | const xs_list *timeline = xs_dict_get(args, "timeline[]"); |
| 2278 | status = HTTP_STATUS_OK; | 2332 | xs_str *json = NULL; |
| 2333 | if (!xs_is_null(timeline)) | ||
| 2334 | json = xs_json_dumps(markers_get(&snac1, timeline), 4); | ||
| 2335 | |||
| 2336 | if (!xs_is_null(json)) | ||
| 2337 | *body = json; | ||
| 2338 | else | ||
| 2339 | *body = xs_dup("{}"); | ||
| 2340 | |||
| 2341 | *ctype = "application/json"; | ||
| 2342 | status = HTTP_STATUS_OK; | ||
| 2343 | } | ||
| 2344 | else | ||
| 2345 | status = HTTP_STATUS_UNAUTHORIZED; | ||
| 2279 | } | 2346 | } |
| 2280 | else | 2347 | else |
| 2281 | if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/ | 2348 | if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/ |
| @@ -2310,6 +2377,37 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2310 | if (xs_is_null(offset) || strcmp(offset, "0") == 0) { | 2377 | if (xs_is_null(offset) || strcmp(offset, "0") == 0) { |
| 2311 | /* reply something only for offset 0; otherwise, | 2378 | /* reply something only for offset 0; otherwise, |
| 2312 | apps like Tusky keep asking again and again */ | 2379 | apps like Tusky keep asking again and again */ |
| 2380 | if (xs_startswith(q, "https://")) { | ||
| 2381 | xs *md5 = xs_md5_hex(q, strlen(q)); | ||
| 2382 | |||
| 2383 | if (!timeline_here(&snac1, md5)) { | ||
| 2384 | xs *object = NULL; | ||
| 2385 | int status; | ||
| 2386 | |||
| 2387 | status = activitypub_request(&snac1, q, &object); | ||
| 2388 | snac_debug(&snac1, 1, xs_fmt("Request searched URL %s %d", q, status)); | ||
| 2389 | |||
| 2390 | if (valid_status(status)) { | ||
| 2391 | /* got it; also request the actor */ | ||
| 2392 | const char *attr_to = get_atto(object); | ||
| 2393 | xs *actor_obj = NULL; | ||
| 2394 | |||
| 2395 | if (!xs_is_null(attr_to)) { | ||
| 2396 | status = actor_request(&snac1, attr_to, &actor_obj); | ||
| 2397 | |||
| 2398 | snac_debug(&snac1, 1, xs_fmt("Request author %s of %s %d", attr_to, q, status)); | ||
| 2399 | |||
| 2400 | if (valid_status(status)) { | ||
| 2401 | /* add the actor */ | ||
| 2402 | actor_add(attr_to, actor_obj); | ||
| 2403 | |||
| 2404 | /* add the post to the timeline */ | ||
| 2405 | timeline_add(&snac1, q, object); | ||
| 2406 | } | ||
| 2407 | } | ||
| 2408 | } | ||
| 2409 | } | ||
| 2410 | } | ||
| 2313 | 2411 | ||
| 2314 | if (!xs_is_null(q)) { | 2412 | if (!xs_is_null(q)) { |
| 2315 | if (xs_is_null(type) || strcmp(type, "accounts") == 0) { | 2413 | if (xs_is_null(type) || strcmp(type, "accounts") == 0) { |
| @@ -2945,6 +3043,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2945 | status = HTTP_STATUS_UNPROCESSABLE_CONTENT; | 3043 | status = HTTP_STATUS_UNPROCESSABLE_CONTENT; |
| 2946 | } | 3044 | } |
| 2947 | } | 3045 | } |
| 3046 | else | ||
| 2948 | if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ | 3047 | if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ |
| 2949 | if (logged_in) { | 3048 | if (logged_in) { |
| 2950 | xs *l = xs_split(cmd, "/"); | 3049 | xs *l = xs_split(cmd, "/"); |
| @@ -2972,9 +3071,35 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2972 | } | 3071 | } |
| 2973 | } | 3072 | } |
| 2974 | } | 3073 | } |
| 3074 | } | ||
| 3075 | else if (strcmp(cmd, "/v1/markers") == 0) { /** **/ | ||
| 3076 | xs_str *json = NULL; | ||
| 3077 | if (logged_in) { | ||
| 3078 | const xs_str *home_marker = xs_dict_get(args, "home[last_read_id]"); | ||
| 3079 | if (xs_is_null(home_marker)) { | ||
| 3080 | const xs_dict *home = xs_dict_get(args, "home"); | ||
| 3081 | if (!xs_is_null(home)) | ||
| 3082 | home_marker = xs_dict_get(home, "last_read_id"); | ||
| 3083 | } | ||
| 3084 | |||
| 3085 | const xs_str *notify_marker = xs_dict_get(args, "notifications[last_read_id]"); | ||
| 3086 | if (xs_is_null(notify_marker)) { | ||
| 3087 | const xs_dict *notify = xs_dict_get(args, "notifications"); | ||
| 3088 | if (!xs_is_null(notify)) | ||
| 3089 | notify_marker = xs_dict_get(notify, "last_read_id"); | ||
| 3090 | } | ||
| 3091 | json = xs_json_dumps(markers_set(&snac, home_marker, notify_marker), 4); | ||
| 3092 | } | ||
| 3093 | if (!xs_is_null(json)) | ||
| 3094 | *body = json; | ||
| 2975 | else | 3095 | else |
| 2976 | status = HTTP_STATUS_UNPROCESSABLE_CONTENT; | 3096 | *body = xs_dup("{}"); |
| 3097 | |||
| 3098 | *ctype = "application/json"; | ||
| 3099 | status = HTTP_STATUS_OK; | ||
| 2977 | } | 3100 | } |
| 3101 | else | ||
| 3102 | status = HTTP_STATUS_UNPROCESSABLE_CONTENT; | ||
| 2978 | 3103 | ||
| 2979 | /* user cleanup */ | 3104 | /* user cleanup */ |
| 2980 | if (logged_in) | 3105 | if (logged_in) |
| @@ -1,7 +1,7 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #define VERSION "2.66" | 4 | #define VERSION "2.67" |
| 5 | 5 | ||
| 6 | #define USER_AGENT "snac/" VERSION | 6 | #define USER_AGENT "snac/" VERSION |
| 7 | 7 | ||
| @@ -238,6 +238,9 @@ int notify_new_num(snac *snac); | |||
| 238 | xs_list *notify_list(snac *snac, int skip, int show); | 238 | xs_list *notify_list(snac *snac, int skip, int show); |
| 239 | void notify_clear(snac *snac); | 239 | void notify_clear(snac *snac); |
| 240 | 240 | ||
| 241 | xs_dict *markers_get(snac *snac, const xs_list *markers); | ||
| 242 | xs_dict *markers_set(snac *snac, const char *home_marker, const char *notify_marker); | ||
| 243 | |||
| 241 | void inbox_add(const char *inbox); | 244 | void inbox_add(const char *inbox); |
| 242 | void inbox_add_by_actor(const xs_dict *actor); | 245 | void inbox_add_by_actor(const xs_dict *actor); |
| 243 | xs_list *inbox_list(void); | 246 | xs_list *inbox_list(void); |
| @@ -386,7 +389,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 386 | const char *payload, int p_size, | 389 | const char *payload, int p_size, |
| 387 | char **body, int *b_size, char **ctype); | 390 | char **body, int *b_size, char **ctype); |
| 388 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, | 391 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, |
| 389 | char **body, int *b_size, char **ctype); | 392 | char **body, int *b_size, char **ctype, xs_str **link); |
| 390 | int mastoapi_post_handler(const xs_dict *req, const char *q_path, | 393 | int mastoapi_post_handler(const xs_dict *req, const char *q_path, |
| 391 | const char *payload, int p_size, | 394 | const char *payload, int p_size, |
| 392 | char **body, int *b_size, char **ctype); | 395 | char **body, int *b_size, char **ctype); |
| @@ -427,3 +430,6 @@ typedef struct { | |||
| 427 | t_announcement *announcement(double after); | 430 | t_announcement *announcement(double after); |
| 428 | 431 | ||
| 429 | xs_str *make_url(const char *href, const char *proxy, int by_token); | 432 | xs_str *make_url(const char *href, const char *proxy, int by_token); |
| 433 | |||
| 434 | int badlogin_check(const char *user, const char *addr); | ||
| 435 | void badlogin_inc(const char *user, const char *addr); | ||
diff --git a/webfinger.c b/webfinger.c index 893d777..85123bc 100644 --- a/webfinger.c +++ b/webfinger.c | |||
| @@ -82,7 +82,11 @@ int webfinger_request_signed(snac *snac, const char *qs, xs_str **actor, xs_str | |||
| 82 | 82 | ||
| 83 | if (obj == NULL && valid_status(status) && payload) { | 83 | if (obj == NULL && valid_status(status) && payload) { |
| 84 | obj = xs_json_loads(payload); | 84 | obj = xs_json_loads(payload); |
| 85 | object_add(cached_qs, obj); | 85 | |
| 86 | if (obj) | ||
| 87 | object_add(cached_qs, obj); | ||
| 88 | else | ||
| 89 | status = HTTP_STATUS_BAD_REQUEST; | ||
| 86 | } | 90 | } |
| 87 | 91 | ||
| 88 | if (obj) { | 92 | if (obj) { |
| @@ -190,7 +190,7 @@ xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *fcgi_id) | |||
| 190 | q_vars = xs_url_vars(xs_list_get(pnv, 1)); | 190 | q_vars = xs_url_vars(xs_list_get(pnv, 1)); |
| 191 | } | 191 | } |
| 192 | else | 192 | else |
| 193 | if (xs_match(k, "CONTENT_TYPE|CONTENT_LENGTH|HTTP_*")) { | 193 | if (xs_match(k, "CONTENT_TYPE|CONTENT_LENGTH|REMOTE_ADDR|HTTP_*")) { |
| 194 | if (xs_startswith(k, "HTTP_")) | 194 | if (xs_startswith(k, "HTTP_")) |
| 195 | k = xs_crop_i(k, 5, 0); | 195 | k = xs_crop_i(k, 5, 0); |
| 196 | 196 | ||