snac2

Fork of https://codeberg.org/grunfink/snac2
git clone https://git.inz.fi/snac2
Log | Files | Refs | README | LICENSE

commit 85be7f36e12507cff7607df22ca14f8bfc00f6e2
parent 13e4a7cda59db29063efc5dad0823fe99ec7b764
Author: shtrophic <christoph@liebender.dev>
Date:   Fri, 24 Jan 2025 20:38:26 +0100

Merge remote-tracking branch 'upstream/master' into curl-smtp

Diffstat:
MRELEASE_NOTES.md | 6++++--
Mactivitypub.c | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mdoc/snac.5 | 7++++++-
Mdoc/snac.8 | 9++++++---
Mformat.c | 47++++++++++++++++++++++++++++++++++++++++++++++-
Mhtml.c | 28++++++++++++++++++++--------
Mhttpd.c | 2+-
Mmastoapi.c | 5++++-
Msandbox.c | 9++++++++-
Msnac.h | 2+-
Mutils.c | 4++++
11 files changed, 197 insertions(+), 53 deletions(-)

diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ # Release Notes -## UNRELEASED +## 2.69 "Yin/Yang of Love" Added support for subscribing to LitePub (Pleroma-style) Fediverse Relays like e.g. https://fedi-relay.gyptazy.com to improve federation. See `snac(8)` (the Administrator Manual) for more information on how to use this feature. @@ -18,7 +18,7 @@ Fixed broken NetBSD build (missing dependency in Makefile.NetBSD). The user profile can now include longitude and latitude data for your current location. -Mastodon API: implemented limit= on notification fetches (contributed by nowster), implemented faster min_id handling (contributed by nowster), obey the quiet public visibility set for posts. +Mastodon API: implemented limit= on notification fetches (contributed by nowster), implemented faster min_id handling (contributed by nowster), obey the quiet public visibility set for posts, other timeline improvements (contributed by nowster). Reduced RSA key size for new users from 4096 to 2048. This will be friendlier to smaller machines, and everybody else out there is using 2048. @@ -26,6 +26,8 @@ If the `SNAC_BASEDIR` environment variable is defined and set to the base direct Fixed a bug in the generation of the top page (contributed by an-im-dugud). +Added support for Markdown headers and underlining (contributed by an-im-dugud). + ## 2.68 Fixed regression in link verification code (contributed by nowster). diff --git a/activitypub.c b/activitypub.c @@ -587,6 +587,70 @@ int is_msg_from_private_user(const xs_dict *msg) } +int followed_hashtag_check(snac *user, const xs_dict *msg) +/* returns true if this message contains a hashtag followed by me */ +{ + const xs_list *fw_tags = xs_dict_get(user->config, "followed_hashtags"); + + if (xs_is_list(fw_tags)) { + const xs_list *tags_in_msg = xs_dict_get(msg, "tag"); + + if (xs_is_list(tags_in_msg)) { + const xs_dict *te; + + /* iterate the tags in the message */ + xs_list_foreach(tags_in_msg, te) { + if (xs_is_dict(te)) { + const char *type = xs_dict_get(te, "type"); + const char *name = xs_dict_get(te, "name"); + + if (xs_is_string(type) && xs_is_string(name)) { + if (strcmp(type, "Hashtag") == 0) { + xs *lc_name = xs_utf8_to_lower(name); + + if (xs_list_in(fw_tags, lc_name) != -1) + return 1; + } + } + } + } + } + } + + return 0; +} + + +void followed_hashtag_distribute(const xs_dict *msg) +/* distribute this post to all users following the included hashtags */ +{ + const char *id = xs_dict_get(msg, "id"); + const xs_list *tags_in_msg = xs_dict_get(msg, "tag"); + + if (!xs_is_string(id) || !xs_is_list(tags_in_msg) || xs_list_len(tags_in_msg) == 0) + return; + + srv_debug(1, xs_fmt("followed_hashtag_distribute check for %s", id)); + + xs *users = user_list(); + const char *uid; + + xs_list_foreach(users, uid) { + snac user; + + if (user_open(&user, uid)) { + if (followed_hashtag_check(&user, msg)) { + timeline_add(&user, id, msg); + + snac_log(&user, xs_fmt("followed hashtag in %s", id)); + } + + user_free(&user); + } + } +} + + int is_msg_for_me(snac *snac, const xs_dict *c_msg) /* checks if this message is for me */ { @@ -602,19 +666,32 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) if (xs_match(type, "Like|Announce|EmojiReact")) { const char *object = xs_dict_get(c_msg, "object"); - if (xs_type(object) == XSTYPE_DICT) + if (xs_is_dict(object)) object = xs_dict_get(object, "id"); /* bad object id? reject */ - if (xs_type(object) != XSTYPE_STRING) + if (!xs_is_string(object)) return 0; /* if it's about one of our posts, accept it */ if (xs_startswith(object, snac->actor)) return 2; - /* if it's by someone we don't follow, reject */ - return following_check(snac, actor); + /* if it's by someone we follow, accept it */ + if (following_check(snac, actor)) + return 1; + + /* do we follow any hashtag? */ + if (xs_is_list(xs_dict_get(snac->config, "followed_hashtags"))) { + xs *obj = NULL; + + /* if the admired object contains any followed hashtag, accept it */ + if (valid_status(object_get(object, &obj)) && + followed_hashtag_check(snac, obj)) + return 7; + } + + return 0; } /* if it's an Undo, it must be from someone related to us */ @@ -708,30 +785,8 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) } /* does this message contain a tag we are following? */ - const xs_list *fw_tags = xs_dict_get(snac->config, "followed_hashtags"); - if (pub_msg && xs_type(fw_tags) == XSTYPE_LIST) { - const xs_list *tags_in_msg = xs_dict_get(msg, "tag"); - if (xs_type(tags_in_msg) == XSTYPE_LIST) { - const xs_dict *te; - - /* iterate the tags in the message */ - xs_list_foreach(tags_in_msg, te) { - if (xs_type(te) == XSTYPE_DICT) { - const char *type = xs_dict_get(te, "type"); - const char *name = xs_dict_get(te, "name"); - - if (xs_type(type) == XSTYPE_STRING && xs_type(name) == XSTYPE_STRING) { - if (strcmp(type, "Hashtag") == 0) { - xs *lc_name = xs_utf8_to_lower(name); - - if (xs_list_in(fw_tags, lc_name) != -1) - return 7; - } - } - } - } - } - } + if (pub_msg && followed_hashtag_check(snac, msg)) + return 7; return 0; } @@ -2277,15 +2332,23 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) xs *who_o = NULL; if (valid_status(actor_request(snac, who, &who_o))) { - if (timeline_admire(snac, object, actor, 0) == HTTP_STATUS_CREATED) - snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); - else - snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", - actor, object)); + /* don't account as such announces by our own relay */ + xs *this_relay = xs_fmt("%s/relay", srv_baseurl); + + if (strcmp(actor, this_relay) != 0) { + if (timeline_admire(snac, object, actor, 0) == HTTP_STATUS_CREATED) + snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); + else + snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", + actor, object)); + } /* distribute the post with the actor as 'proxy' */ list_distribute(snac, actor, a_msg); + /* distribute the post to users following these hashtags */ + followed_hashtag_distribute(a_msg); + do_notify = 1; } else @@ -2300,7 +2363,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) } else if (strcmp(type, "Update") == 0) { /** **/ - if (xs_match(utype, "Person|Service")) { /** **/ + if (xs_match(utype, "Person|Service|Application")) { /** **/ actor_add(actor, xs_dict_get(msg, "object")); timeline_touch(snac); diff --git a/doc/snac.5 b/doc/snac.5 @@ -24,9 +24,11 @@ A special subset of Markdown is allowed, including: .It bold **text between two pairs of asterisks** .It italic -*text between a pair of asterisks* +*text between a pair of asterisks* or _between a pair of underscores_ .It strikethrough text ~~text between a pair of tildes~~ +.It underlined text +__text between two pairs of underscores__ .It code Text `between backticks` is formatted as code. .Bd -literal @@ -53,6 +55,9 @@ Horizonal rules can be inserted by typing three minus symbols alone in a line. .It quoted text Lines starting with >. +.It headers +One, two or three # at the beginning of a line plus a space plus +some text are converted to HTML headers. .It user mentions Strings in the format @user@host are requested using the Webfinger protocol and converted to links and mentions if something reasonable diff --git a/doc/snac.8 b/doc/snac.8 @@ -591,17 +591,20 @@ instance can subscribe to LitePub (Pleroma-style) Fediverse Relays. Doing this i visibility and allows following hashtags. To do this, you must create a special user named relay and, from it, follow the relay actor(s) like you do with regular actor URLs. This special user will start receiving boosts from the relay server of posts from other instances -also following it. It any other user of the same +also following it. If any other user of the same .Nm instance follows any of the hashtags included in these boosted posts coming from the relay, -they will received as if they were for them. +they will be received as if they were for them. .Pp Example: .Bd -literal -offset indent snac adduser $SNAC_BASEDIR relay # only needed once -snac follow $SNAC_BASEDIR relay https://fedi-relay.gyptazy.com/actor +snac follow $SNAC_BASEDIR relay https://relay.example.com/actor .Ed .Pp +Users on your instance do NOT need to follow the local relay user to benefit from following +hashtags. +.Pp Please take note that subscribing to relays can increase the traffic towards your instance significantly. In any case, lowering the "Maximum days to keep posts" value for the relay special user is recommended (e.g. setting to just 1 day). diff --git a/format.c b/format.c @@ -92,6 +92,8 @@ static xs_str *format_line(const char *line, xs_list **attach) "`[^`]+`" "|" "~~[^~]+~~" "|" "\\*\\*?\\*?[^\\*]+\\*?\\*?\\*" "|" + "_[^_]+_" "|" //anzu + "__[^_]+__" "|" //anzu "!\\[[^]]+\\]\\([^\\)]+\\)" "|" "\\[[^]]+\\]\\([^\\)]+\\)" "|" "[a-z]+:/" "/[^[:space:]]+" "|" @@ -127,6 +129,20 @@ static xs_str *format_line(const char *line, xs_list **attach) xs *s2 = xs_fmt("<i>%s</i>", s1); s = xs_str_cat(s, s2); } + //anzu - begin + else + if (xs_startswith(v, "__")) { + xs *s1 = xs_strip_chars_i(xs_dup(v), "_"); + xs *s2 = xs_fmt("<u>%s</u>", s1); + s = xs_str_cat(s, s2); + } + else + if (xs_startswith(v, "_")) { + xs *s1 = xs_strip_chars_i(xs_dup(v), "_"); + xs *s2 = xs_fmt("<i>%s</i>", s1); + s = xs_str_cat(s, s2); + } + //anzu - end else if (xs_startswith(v, "~~")) { xs *s1 = xs_strip_chars_i(xs_dup(v), "~"); @@ -303,6 +319,31 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag continue; } + //anzu - begin + // h1 reserved for snac? + if (xs_startswith(ss, "# ")) { + ss = xs_strip_i(xs_crop_i(ss, 2, 0)); + s = xs_str_cat(s, "<h2>"); + s = xs_str_cat(s, ss); + s = xs_str_cat(s, "</h2>"); + continue; + } + if (xs_startswith(ss, "## ")) { + ss = xs_strip_i(xs_crop_i(ss, 3, 0)); + s = xs_str_cat(s, "<h2>"); + s = xs_str_cat(s, ss); + s = xs_str_cat(s, "</h2>"); + continue; + } + if (xs_startswith(ss, "### ")) { + ss = xs_strip_i(xs_crop_i(ss, 4, 0)); + s = xs_str_cat(s, "<h3>"); + s = xs_str_cat(s, ss); + s = xs_str_cat(s, "</h3>"); + continue; + } + //anzu - end + if (xs_startswith(ss, ">")) { /* delete the > and subsequent spaces */ ss = xs_strip_i(xs_crop_i(ss, 1, 0)); @@ -336,6 +377,8 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag s = xs_replace_i(s, "<br><br><blockquote>", "<br><blockquote>"); s = xs_replace_i(s, "</blockquote><br>", "</blockquote>"); s = xs_replace_i(s, "</pre><br>", "</pre>"); + s = xs_replace_i(s, "</h2><br>", "</h2>"); //anzu ??? + s = xs_replace_i(s, "</h3><br>", "</h3>"); //anzu ??? { /* traditional emoticons */ @@ -378,7 +421,9 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag const char *valid_tags[] = { "a", "p", "br", "br/", "blockquote", "ul", "ol", "li", "cite", "small", - "span", "i", "b", "u", "s", "pre", "code", "em", "strong", "hr", "img", "del", "bdi", NULL + "span", "i", "b", "u", "s", "pre", "code", "em", "strong", "hr", "img", "del", "bdi", + "h2","h3", //anzu + NULL }; xs_str *sanitize(const char *content) diff --git a/html.c b/html.c @@ -1855,13 +1855,15 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, } } } - else - if (strcmp(type, "Note") == 0) { - if (level == 0) { - /* is the parent not here? */ - const char *parent = get_in_reply_to(msg); - if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { + if (user && strcmp(type, "Note") == 0) { + /* is the parent not here? */ + const char *parent = get_in_reply_to(msg); + + if (!xs_is_null(parent) && *parent) { + xs *md5 = xs_md5_hex(parent, strlen(parent)); + + if (!timeline_here(user, md5)) { xs_html_add(post_header, xs_html_tag("div", xs_html_attr("class", "snac-origin"), @@ -2965,9 +2967,12 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_attr("class", "snac-posts")); xs_html_add(body, posts); - xs_list *p = n_list; + xs_set rep; + xs_set_init(&rep); + const xs_str *v; - while (xs_list_iter(&p, &v)) { + + xs_list_foreach(n_list, v) { xs *noti = notify_get(user, v); if (noti == NULL) @@ -2988,6 +2993,11 @@ xs_str *html_notifications(snac *user, int skip, int show) object_get(id, &obj); + const char *msg_id = NULL; + + if (xs_is_dict(obj) && (msg_id = xs_dict_get(obj, "id")) && xs_set_add(&rep, msg_id) != 1) + continue; + const char *actor_id = xs_dict_get(noti, "actor"); xs *actor = NULL; @@ -3101,6 +3111,8 @@ xs_str *html_notifications(snac *user, int skip, int show) } } + xs_set_free(&rep); + if (noti_new == NULL && noti_seen == NULL) xs_html_add(body, xs_html_tag("h2", diff --git a/httpd.c b/httpd.c @@ -138,7 +138,7 @@ static xs_str *greeting_html(void) while (xs_list_iter(&p, &uid)) { snac user; - if (user_open(&user, uid)) { + if (strcmp(uid, "relay") && user_open(&user, uid)) { xs_html_add(ul, xs_html_tag("li", xs_html_tag("a", diff --git a/mastoapi.c b/mastoapi.c @@ -1453,7 +1453,10 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn xs *st = mastoapi_status(user, msg); if (st != NULL) { - out = xs_list_append(out, st); + if (ascending) + out = xs_list_insert(out, 0, st); + else + out = xs_list_append(out, st); cnt++; } diff --git a/sandbox.c b/sandbox.c @@ -63,15 +63,22 @@ LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smtp_p LANDLOCK_ACCESS_FS_REFER_COMPAT, s = LANDLOCK_ACCESS_FS_MAKE_SOCK, x = LANDLOCK_ACCESS_FS_EXECUTE; + char *resolved_path = NULL; LL_PATH(basedir, rf|rd|w|c); LL_PATH("/tmp", rf|rd|w|c); #ifndef WITHOUT_SHM LL_PATH("/dev/shm", rf|w|c ); #endif + LL_PATH("/dev/urandom", rf ); LL_PATH("/etc/resolv.conf", rf ); LL_PATH("/etc/hosts", rf ); - LL_PATH("/etc/ssl", rf ); + LL_PATH("/etc/ssl", rf|rd ); + if ((resolved_path = realpath("/etc/ssl/cert.pem", NULL))) { + /* some distros like cert.pem to be a symlink */ + LL_PATH(resolved_path, rf ); + free(resolved_path); + } LL_PATH("/usr/share/zoneinfo", rf ); if (mtime("/etc/pki") > 0) diff --git a/snac.h b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.69-dev" +#define VERSION "2.69" #define USER_AGENT "snac/" VERSION diff --git a/utils.c b/utils.c @@ -318,6 +318,10 @@ int adduser(const char *uid) mkdirx(d); } + /* add a specially short data retention time for the relay */ + if (strcmp(uid, "relay") == 0) + config = xs_dict_set(config, "purge_days", xs_stock(1)); + xs *cfn = xs_fmt("%s/user.json", basedir); if ((f = fopen(cfn, "w")) == NULL) {