activitypub.c 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. /* snac - A simple, minimalistic ActivityPub instance */
  2. /* copyright (c) 2022 grunfink - MIT license */
  3. #include "xs.h"
  4. #include "xs_encdec.h"
  5. #include "xs_json.h"
  6. #include "xs_curl.h"
  7. #include "xs_mime.h"
  8. #include "xs_openssl.h"
  9. #include "xs_regex.h"
  10. #include "xs_time.h"
  11. #include "xs_set.h"
  12. #include "snac.h"
  13. const char *public_address = "https:/" "/www.w3.org/ns/activitystreams#Public";
  14. int activitypub_request(snac *snac, char *url, d_char **data)
  15. /* request an object */
  16. {
  17. int status;
  18. xs *response = NULL;
  19. xs *payload = NULL;
  20. int p_size;
  21. char *ctype;
  22. /* check if it's an url for this same site */
  23. /* ... */
  24. /* get from the net */
  25. response = http_signed_request(snac, "GET", url,
  26. NULL, NULL, 0, &status, &payload, &p_size);
  27. if (valid_status(status)) {
  28. /* ensure it's ActivityPub data */
  29. ctype = xs_dict_get(response, "content-type");
  30. if (xs_str_in(ctype, "application/activity+json") != -1 ||
  31. xs_str_in(ctype, "application/ld+json") != -1)
  32. *data = xs_json_loads(payload);
  33. else
  34. status = 500;
  35. }
  36. if (!valid_status(status))
  37. *data = NULL;
  38. return status;
  39. }
  40. int actor_request(snac *snac, char *actor, d_char **data)
  41. /* request an actor */
  42. {
  43. int status, status2;
  44. xs *payload = NULL;
  45. /* get from disk first */
  46. status = actor_get(snac, actor, data);
  47. if (status == 200)
  48. return status;
  49. /* actor data non-existent or stale: get from the net */
  50. status2 = activitypub_request(snac, actor, &payload);
  51. if (valid_status(status2)) {
  52. /* renew data */
  53. status = actor_add(snac, actor, payload);
  54. if (data != NULL) {
  55. *data = payload;
  56. payload = NULL;
  57. }
  58. }
  59. return status;
  60. }
  61. int timeline_request(snac *snac, char **id, char *referrer)
  62. /* ensures that an entry and its ancestors are in the timeline */
  63. {
  64. int status = 0;
  65. if (!xs_is_null(*id)) {
  66. /* is the admired object already there? */
  67. if (!object_here(*id)) {
  68. xs *object = NULL;
  69. /* no; download it */
  70. status = activitypub_request(snac, *id, &object);
  71. if (valid_status(status)) {
  72. char *oid = *id;
  73. char *type = xs_dict_get(object, "type");
  74. /* get the id again from the object, as it may be different */
  75. *id = xs_dict_get(object, "id");
  76. if (strcmp(*id, oid) != 0)
  77. snac_debug(snac, 1,
  78. xs_fmt("timeline_request canonical id for %s is %s", oid, *id));
  79. if (!xs_is_null(type) && strcmp(type, "Note") == 0) {
  80. char *actor = xs_dict_get(object, "attributedTo");
  81. /* request (and drop) the actor for this entry */
  82. if (!xs_is_null(actor))
  83. actor_request(snac, actor, NULL);
  84. /* does it have an ancestor? */
  85. char *in_reply_to = xs_dict_get(object, "inReplyTo");
  86. /* recurse! */
  87. timeline_request(snac, &in_reply_to, referrer);
  88. /* finally store */
  89. timeline_add(snac, *id, object, in_reply_to, referrer);
  90. }
  91. }
  92. }
  93. }
  94. return status;
  95. }
  96. int send_to_inbox(snac *snac, char *inbox, char *msg, d_char **payload, int *p_size)
  97. /* sends a message to an Inbox */
  98. {
  99. int status;
  100. d_char *response;
  101. xs *j_msg = xs_json_dumps_pp(msg, 4);
  102. response = http_signed_request(snac, "POST", inbox,
  103. NULL, j_msg, strlen(j_msg), &status, payload, p_size);
  104. if (status == 400) {
  105. snac_debug(snac, 0, xs_fmt("send_to_inbox error %d (response date: '%s')",
  106. status, xs_dict_get(response, "date")));
  107. }
  108. xs_free(response);
  109. return status;
  110. }
  111. d_char *get_actor_inbox(snac *snac, char *actor)
  112. /* gets an actor's inbox */
  113. {
  114. xs *data = NULL;
  115. char *v = NULL;
  116. if (valid_status(actor_request(snac, actor, &data))) {
  117. /* try first endpoints/sharedInbox */
  118. if ((v = xs_dict_get(data, "endpoints")))
  119. v = xs_dict_get(v, "sharedInbox");
  120. /* try then the regular inbox */
  121. if (xs_is_null(v))
  122. v = xs_dict_get(data, "inbox");
  123. }
  124. return xs_is_null(v) ? NULL : xs_dup(v);
  125. }
  126. int send_to_actor(snac *snac, char *actor, char *msg, d_char **payload, int *p_size)
  127. /* sends a message to an actor */
  128. {
  129. int status = 400;
  130. xs *inbox = get_actor_inbox(snac, actor);
  131. if (!xs_is_null(inbox))
  132. status = send_to_inbox(snac, inbox, msg, payload, p_size);
  133. return status;
  134. }
  135. d_char *recipient_list(snac *snac, char *msg, int expand_public)
  136. /* returns the list of recipients for a message */
  137. {
  138. char *to = xs_dict_get(msg, "to");
  139. char *cc = xs_dict_get(msg, "cc");
  140. xs_set rcpts;
  141. int n;
  142. xs_set_init(&rcpts);
  143. char *lists[] = { to, cc, NULL };
  144. for (n = 0; lists[n]; n++) {
  145. char *l = lists[n];
  146. char *v;
  147. xs *tl = NULL;
  148. /* if it's a string, create a list with only one element */
  149. if (xs_type(l) == XSTYPE_STRING) {
  150. tl = xs_list_new();
  151. tl = xs_list_append(tl, l);
  152. l = tl;
  153. }
  154. while (xs_list_iter(&l, &v)) {
  155. if (expand_public && strcmp(v, public_address) == 0) {
  156. /* iterate the followers and add them */
  157. xs *fwers = follower_list(snac);
  158. char *actor;
  159. char *p = fwers;
  160. while (xs_list_iter(&p, &actor))
  161. xs_set_add(&rcpts, actor);
  162. }
  163. else
  164. xs_set_add(&rcpts, v);
  165. }
  166. }
  167. return xs_set_result(&rcpts);
  168. }
  169. d_char *inbox_list(snac *snac, char *msg)
  170. /* returns the list of inboxes that are recipients of this message */
  171. {
  172. xs *rcpts = recipient_list(snac, msg, 1);
  173. xs_set inboxes;
  174. char *p, *v;
  175. xs_set_init(&inboxes);
  176. p = rcpts;
  177. while (xs_list_iter(&p, &v)) {
  178. xs *inbox;
  179. if ((inbox = get_actor_inbox(snac, v)) != NULL) {
  180. /* add the inbox if it's not already there */
  181. xs_set_add(&inboxes, inbox);
  182. }
  183. }
  184. return xs_set_result(&inboxes);
  185. }
  186. int is_msg_public(snac *snac, char *msg)
  187. /* checks if a message is public */
  188. {
  189. int ret = 0;
  190. xs *rcpts = recipient_list(snac, msg, 0);
  191. char *p, *v;
  192. p = rcpts;
  193. while (!ret && xs_list_iter(&p, &v)) {
  194. if (strcmp(v, public_address) == 0)
  195. ret = 1;
  196. }
  197. return ret;
  198. }
  199. void process_tags(const char *content, d_char **n_content, d_char **tag)
  200. /* parses mentions and tags from content */
  201. {
  202. d_char *nc = xs_str_new(NULL);
  203. d_char *tl = xs_list_new();
  204. xs *split;
  205. char *p, *v;
  206. int n = 0;
  207. split = xs_regex_split(content, "(@[A-Za-z0-9_]+@[A-Za-z0-9\\.-]+|#[^ ,\\.:;]+)");
  208. p = split;
  209. while (xs_list_iter(&p, &v)) {
  210. if ((n & 0x1)) {
  211. if (*v == '@') {
  212. /* query the webfinger about this fellow */
  213. xs *actor = NULL;
  214. xs *uid = NULL;
  215. int status;
  216. status = webfinger_request(v + 1, &actor, &uid);
  217. if (valid_status(status)) {
  218. xs *d = xs_dict_new();
  219. xs *n = xs_fmt("@%s", uid);
  220. xs *l = xs_fmt("<a href=\"%s\" class=\"u-url mention\">%s</a>", actor, n);
  221. d = xs_dict_append(d, "type", "Mention");
  222. d = xs_dict_append(d, "href", actor);
  223. d = xs_dict_append(d, "name", n);
  224. tl = xs_list_append(tl, d);
  225. /* add the code */
  226. nc = xs_str_cat(nc, l);
  227. }
  228. else
  229. /* store as is */
  230. nc = xs_str_cat(nc, v);
  231. }
  232. else
  233. if (*v == '#') {
  234. /* hashtag */
  235. /* store as is by now */
  236. nc = xs_str_cat(nc, v);
  237. }
  238. }
  239. else
  240. nc = xs_str_cat(nc, v);
  241. n++;
  242. }
  243. *n_content = nc;
  244. *tag = tl;
  245. }
  246. /** messages **/
  247. d_char *msg_base(snac *snac, char *type, char *id, char *actor, char *date, char *object)
  248. /* creates a base ActivityPub message */
  249. {
  250. xs *did = NULL;
  251. xs *published = NULL;
  252. /* generated values */
  253. if (date && strcmp(date, "@now") == 0) {
  254. published = xs_str_utctime(0, "%Y-%m-%dT%H:%M:%SZ");
  255. date = published;
  256. }
  257. if (id != NULL) {
  258. if (strcmp(id, "@dummy") == 0) {
  259. xs *ntid = tid(0);
  260. did = xs_fmt("%s/d/%s/%s", snac->actor, ntid, type);
  261. id = did;
  262. }
  263. else
  264. if (strcmp(id, "@object") == 0) {
  265. if (object != NULL) {
  266. did = xs_fmt("%s/%s", xs_dict_get(object, "id"), type);
  267. id = did;
  268. }
  269. else
  270. id = NULL;
  271. }
  272. }
  273. d_char *msg = xs_dict_new();
  274. msg = xs_dict_append(msg, "@context", "https:/" "/www.w3.org/ns/activitystreams");
  275. msg = xs_dict_append(msg, "type", type);
  276. if (id != NULL)
  277. msg = xs_dict_append(msg, "id", id);
  278. if (actor != NULL)
  279. msg = xs_dict_append(msg, "actor", actor);
  280. if (date != NULL)
  281. msg = xs_dict_append(msg, "published", date);
  282. if (object != NULL)
  283. msg = xs_dict_append(msg, "object", object);
  284. return msg;
  285. }
  286. d_char *msg_collection(snac *snac, char *id)
  287. /* creates an empty OrderedCollection message */
  288. {
  289. d_char *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL);
  290. xs *ol = xs_list_new();
  291. xs *nz = xs_number_new(0);
  292. msg = xs_dict_append(msg, "attributedTo", snac->actor);
  293. msg = xs_dict_append(msg, "orderedItems", ol);
  294. msg = xs_dict_append(msg, "totalItems", nz);
  295. return msg;
  296. }
  297. d_char *msg_accept(snac *snac, char *object, char *to)
  298. /* creates an Accept message (as a response to a Follow) */
  299. {
  300. d_char *msg = msg_base(snac, "Accept", "@dummy", snac->actor, NULL, object);
  301. msg = xs_dict_append(msg, "to", to);
  302. return msg;
  303. }
  304. d_char *msg_update(snac *snac, char *object)
  305. /* creates an Update message */
  306. {
  307. d_char *msg = msg_base(snac, "Update", "@object", snac->actor, "@now", object);
  308. msg = xs_dict_append(msg, "to", public_address);
  309. return msg;
  310. }
  311. d_char *msg_admiration(snac *snac, char *object, char *type)
  312. /* creates a Like or Announce message */
  313. {
  314. xs *a_msg = NULL;
  315. d_char *msg = NULL;
  316. /* call the object */
  317. timeline_request(snac, &object, snac->actor);
  318. if (valid_status(object_get(object, &a_msg, NULL))) {
  319. xs *rcpts = xs_list_new();
  320. msg = msg_base(snac, type, "@dummy", snac->actor, "@now", object);
  321. rcpts = xs_list_append(rcpts, public_address);
  322. rcpts = xs_list_append(rcpts, xs_dict_get(a_msg, "attributedTo"));
  323. msg = xs_dict_append(msg, "to", rcpts);
  324. }
  325. else
  326. snac_log(snac, xs_fmt("msg_admiration cannot retrieve object %s", object));
  327. return msg;
  328. }
  329. d_char *msg_actor(snac *snac)
  330. /* create a Person message for this actor */
  331. {
  332. xs *ctxt = xs_list_new();
  333. xs *icon = xs_dict_new();
  334. xs *keys = xs_dict_new();
  335. xs *avtr = NULL;
  336. xs *kid = NULL;
  337. xs *f_bio = NULL;
  338. d_char *msg = msg_base(snac, "Person", snac->actor, NULL, NULL, NULL);
  339. char *p;
  340. int n;
  341. /* change the @context (is this really necessary?) */
  342. ctxt = xs_list_append(ctxt, "https:/" "/www.w3.org/ns/activitystreams");
  343. ctxt = xs_list_append(ctxt, "https:/" "/w3id.org/security/v1");
  344. msg = xs_dict_set(msg, "@context", ctxt);
  345. msg = xs_dict_set(msg, "url", snac->actor);
  346. msg = xs_dict_set(msg, "name", xs_dict_get(snac->config, "name"));
  347. msg = xs_dict_set(msg, "preferredUsername", snac->uid);
  348. msg = xs_dict_set(msg, "published", xs_dict_get(snac->config, "published"));
  349. f_bio = not_really_markdown(xs_dict_get(snac->config, "bio"));
  350. msg = xs_dict_set(msg, "summary", f_bio);
  351. char *folders[] = { "inbox", "outbox", "followers", "following", NULL };
  352. for (n = 0; folders[n]; n++) {
  353. xs *f = xs_fmt("%s/%s", snac->actor, folders[n]);
  354. msg = xs_dict_set(msg, folders[n], f);
  355. }
  356. p = xs_dict_get(snac->config, "avatar");
  357. if (*p == '\0')
  358. avtr = xs_fmt("%s/susie.png", srv_baseurl);
  359. else
  360. avtr = xs_dup(p);
  361. icon = xs_dict_append(icon, "type", "Image");
  362. icon = xs_dict_append(icon, "mediaType", xs_mime_by_ext(avtr));
  363. icon = xs_dict_append(icon, "url", avtr);
  364. msg = xs_dict_set(msg, "icon", icon);
  365. kid = xs_fmt("%s#main-key", snac->actor);
  366. keys = xs_dict_append(keys, "id", kid);
  367. keys = xs_dict_append(keys, "owner", snac->actor);
  368. keys = xs_dict_append(keys, "publicKeyPem", xs_dict_get(snac->key, "public"));
  369. msg = xs_dict_set(msg, "publicKey", keys);
  370. return msg;
  371. }
  372. d_char *msg_create(snac *snac, char *object)
  373. /* creates a 'Create' message */
  374. {
  375. d_char *msg = msg_base(snac, "Create", "@object", snac->actor, "@now", object);
  376. msg = xs_dict_append(msg, "attributedTo", xs_dict_get(object, "attributedTo"));
  377. msg = xs_dict_append(msg, "to", xs_dict_get(object, "to"));
  378. msg = xs_dict_append(msg, "cc", xs_dict_get(object, "cc"));
  379. return msg;
  380. }
  381. d_char *msg_undo(snac *snac, char *object)
  382. /* creates an 'Undo' message */
  383. {
  384. d_char *msg = msg_base(snac, "Undo", "@object", snac->actor, "@now", object);
  385. msg = xs_dict_append(msg, "to", xs_dict_get(object, "object"));
  386. return msg;
  387. }
  388. d_char *msg_delete(snac *snac, char *id)
  389. /* creates a 'Delete' + 'Tombstone' for a local entry */
  390. {
  391. xs *tomb = xs_dict_new();
  392. d_char *msg = NULL;
  393. /* sculpt the tombstone */
  394. tomb = xs_dict_append(tomb, "type", "Tombstone");
  395. tomb = xs_dict_append(tomb, "id", id);
  396. /* now create the Delete */
  397. msg = msg_base(snac, "Delete", "@object", snac->actor, "@now", tomb);
  398. msg = xs_dict_append(msg, "to", public_address);
  399. return msg;
  400. }
  401. d_char *msg_follow(snac *snac, char *url_or_uid)
  402. /* creates a 'Follow' message */
  403. {
  404. xs *actor_o = NULL;
  405. xs *actor = NULL;
  406. d_char *msg = NULL;
  407. int status;
  408. if (xs_startswith(url_or_uid, "https:/"))
  409. actor = xs_dup(url_or_uid);
  410. else
  411. if (!valid_status(webfinger_request(url_or_uid, &actor, NULL))) {
  412. snac_log(snac, xs_fmt("cannot resolve user %s to follow", url_or_uid));
  413. return NULL;
  414. }
  415. /* request the actor */
  416. status = actor_request(snac, actor, &actor_o);
  417. if (valid_status(status)) {
  418. /* check if the actor is an alias */
  419. char *r_actor = xs_dict_get(actor_o, "id");
  420. if (r_actor && strcmp(actor, r_actor) != 0) {
  421. snac_log(snac, xs_fmt("actor to follow is an alias %s -> %s", actor, r_actor));
  422. }
  423. msg = msg_base(snac, "Follow", "@dummy", snac->actor, NULL, r_actor);
  424. }
  425. else
  426. snac_log(snac, xs_fmt("cannot get actor to follow %s %d", actor, status));
  427. return msg;
  428. }
  429. d_char *msg_note(snac *snac, char *content, char *rcpts, char *in_reply_to, char *attach)
  430. /* creates a 'Note' message */
  431. {
  432. xs *ntid = tid(0);
  433. xs *id = xs_fmt("%s/p/%s", snac->actor, ntid);
  434. xs *ctxt = NULL;
  435. xs *fc2 = NULL;
  436. xs *fc1 = NULL;
  437. xs *to = NULL;
  438. xs *cc = xs_list_new();
  439. xs *irt = NULL;
  440. xs *tag = NULL;
  441. xs *atls = NULL;
  442. d_char *msg = msg_base(snac, "Note", id, NULL, "@now", NULL);
  443. char *p, *v;
  444. if (rcpts == NULL)
  445. to = xs_list_new();
  446. else {
  447. if (xs_type(rcpts) == XSTYPE_STRING) {
  448. to = xs_list_new();
  449. to = xs_list_append(to, rcpts);
  450. }
  451. else
  452. to = xs_dup(rcpts);
  453. }
  454. /* format the content */
  455. fc2 = not_really_markdown(content);
  456. /* extract the tags */
  457. process_tags(fc2, &fc1, &tag);
  458. if (tag == NULL)
  459. tag = xs_list_new();
  460. if (in_reply_to != NULL && *in_reply_to) {
  461. xs *p_msg = NULL;
  462. /* demand this thing */
  463. timeline_request(snac, &in_reply_to, NULL);
  464. if (valid_status(object_get(in_reply_to, &p_msg, NULL))) {
  465. /* add this author as recipient */
  466. char *a, *v;
  467. if ((a = xs_dict_get(p_msg, "attributedTo")) && xs_list_in(to, a) == -1)
  468. to = xs_list_append(to, a);
  469. /* add this author to the tag list as a mention */
  470. xs *t_href = NULL;
  471. xs *t_name = NULL;
  472. if (!xs_is_null(a) && valid_status(webfinger_request(a, &t_href, &t_name))) {
  473. xs *t = xs_dict_new();
  474. t = xs_dict_append(t, "type", "Mention");
  475. t = xs_dict_append(t, "href", t_href);
  476. t = xs_dict_append(t, "name", t_name);
  477. tag = xs_list_append(tag, t);
  478. }
  479. /* get the context, if there is one */
  480. if ((v = xs_dict_get(p_msg, "context")))
  481. ctxt = xs_dup(v);
  482. /* if this message is public, ours will also be */
  483. if (is_msg_public(snac, p_msg) &&
  484. xs_list_in(to, public_address) == -1)
  485. to = xs_list_append(to, public_address);
  486. }
  487. irt = xs_dup(in_reply_to);
  488. }
  489. else
  490. irt = xs_val_new(XSTYPE_NULL);
  491. /* create the attachment list, if there are any */
  492. if (!xs_is_null(attach) && *attach != '\0') {
  493. xs *lsof1 = NULL;
  494. if (xs_type(attach) == XSTYPE_STRING) {
  495. lsof1 = xs_list_append(xs_list_new(), attach);
  496. attach = lsof1;
  497. }
  498. atls = xs_list_new();
  499. while (xs_list_iter(&attach, &v)) {
  500. xs *d = xs_dict_new();
  501. char *mime = xs_mime_by_ext(v);
  502. d = xs_dict_append(d, "mediaType", mime);
  503. d = xs_dict_append(d, "url", v);
  504. d = xs_dict_append(d, "name", "");
  505. d = xs_dict_append(d, "type",
  506. xs_startswith(mime, "image/") ? "Image" : "Document");
  507. atls = xs_list_append(atls, d);
  508. }
  509. }
  510. if (ctxt == NULL)
  511. ctxt = xs_fmt("%s#ctxt", id);
  512. /* add all mentions to the cc */
  513. p = tag;
  514. while (xs_list_iter(&p, &v)) {
  515. if (xs_type(v) == XSTYPE_DICT) {
  516. char *t;
  517. if ((t = xs_dict_get(v, "type")) != NULL && strcmp(t, "Mention") == 0) {
  518. if ((t = xs_dict_get(v, "href")) != NULL)
  519. cc = xs_list_append(cc, t);
  520. }
  521. }
  522. }
  523. /* no recipients? must be for everybody */
  524. if (xs_list_len(to) == 0)
  525. to = xs_list_append(to, public_address);
  526. /* delete all cc recipients that also are in the to */
  527. p = to;
  528. while (xs_list_iter(&p, &v)) {
  529. int i;
  530. if ((i = xs_list_in(cc, v)) != -1)
  531. cc = xs_list_del(cc, i);
  532. }
  533. msg = xs_dict_append(msg, "attributedTo", snac->actor);
  534. msg = xs_dict_append(msg, "summary", "");
  535. msg = xs_dict_append(msg, "content", fc1);
  536. msg = xs_dict_append(msg, "context", ctxt);
  537. msg = xs_dict_append(msg, "url", id);
  538. msg = xs_dict_append(msg, "to", to);
  539. msg = xs_dict_append(msg, "cc", cc);
  540. msg = xs_dict_append(msg, "inReplyTo", irt);
  541. msg = xs_dict_append(msg, "tag", tag);
  542. if (atls != NULL)
  543. msg = xs_dict_append(msg, "attachment", atls);
  544. return msg;
  545. }
  546. void notify(snac *snac, char *type, char *utype, char *actor, char *msg)
  547. /* notifies the user of relevant events */
  548. {
  549. char *email = xs_dict_get(snac->config, "email");
  550. char *object = NULL;
  551. /* no email address? done */
  552. if (xs_is_null(email) || *email == '\0')
  553. return;
  554. if (strcmp(type, "Create") == 0) {
  555. /* only notify of notes specifically for us */
  556. xs *rcpts = recipient_list(snac, msg, 0);
  557. if (xs_list_in(rcpts, snac->actor) == -1)
  558. return;
  559. }
  560. if (strcmp(type, "Undo") == 0 && strcmp(utype, "Follow") != 0)
  561. return;
  562. if (strcmp(type, "Like") == 0 || strcmp(type, "Announce") == 0) {
  563. object = xs_dict_get(msg, "object");
  564. if (xs_is_null(object))
  565. return;
  566. else {
  567. if (xs_type(object) == XSTYPE_DICT)
  568. object = xs_dict_get(object, "id");
  569. /* if it's not an admiration about something by us, done */
  570. if (xs_is_null(object) || !xs_startswith(object, snac->actor))
  571. return;
  572. }
  573. }
  574. snac_debug(snac, 1, xs_fmt("notify(%s, %s, %s)", type, utype, actor));
  575. /* prepare message */
  576. xs *subject = xs_fmt("snac notify for @%s@%s",
  577. xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host"));
  578. xs *from = xs_fmt("snac-daemon <snac-daemon@%s>", xs_dict_get(srv_config, "host"));
  579. xs *header = xs_fmt(
  580. "From: %s\n"
  581. "To: %s\n"
  582. "Subject: %s\n"
  583. "\n",
  584. from, email, subject);
  585. xs *body = xs_str_new(header);
  586. if (strcmp(utype, "(null)") != 0) {
  587. xs *s1 = xs_fmt("Type : %s + %s\n", type, utype);
  588. body = xs_str_cat(body, s1);
  589. }
  590. else {
  591. xs *s1 = xs_fmt("Type : %s\n", type);
  592. body = xs_str_cat(body, s1);
  593. }
  594. {
  595. xs *s1 = xs_fmt("Actor : %s\n", actor);
  596. body = xs_str_cat(body, s1);
  597. }
  598. if (object != NULL) {
  599. xs *s1 = xs_fmt("Object: %s\n", object);
  600. body = xs_str_cat(body, s1);
  601. }
  602. enqueue_email(snac, body, 0);
  603. }
  604. /** queues **/
  605. int process_message(snac *snac, char *msg, char *req)
  606. /* processes an ActivityPub message from the input queue */
  607. {
  608. /* actor and type exist, were checked previously */
  609. char *actor = xs_dict_get(msg, "actor");
  610. char *type = xs_dict_get(msg, "type");
  611. xs *actor_o = NULL;
  612. int a_status;
  613. int do_notify = 0;
  614. char *object, *utype;
  615. object = xs_dict_get(msg, "object");
  616. if (object != NULL && xs_type(object) == XSTYPE_DICT)
  617. utype = xs_dict_get(object, "type");
  618. else
  619. utype = "(null)";
  620. /* bring the actor */
  621. a_status = actor_request(snac, actor, &actor_o);
  622. /* if the actor does not explicitly exist, discard */
  623. if (a_status == 404 || a_status == 410) {
  624. snac_debug(snac, 1,
  625. xs_fmt("dropping message due to actor error %s %d", actor, a_status));
  626. return 1;
  627. }
  628. if (!valid_status(a_status)) {
  629. /* other actor download errors may need a retry */
  630. snac_debug(snac, 1,
  631. xs_fmt("error requesting actor %s %d -- retry later", actor, a_status));
  632. return 0;
  633. }
  634. /* check the signature */
  635. if (!check_signature(snac, req)) {
  636. snac_log(snac, xs_fmt("bad signature"));
  637. return 1;
  638. }
  639. if (strcmp(type, "Follow") == 0) {
  640. if (!follower_check(snac, actor)) {
  641. xs *f_msg = xs_dup(msg);
  642. xs *reply = msg_accept(snac, f_msg, actor);
  643. enqueue_message(snac, reply);
  644. if (xs_is_null(xs_dict_get(f_msg, "published"))) {
  645. /* add a date if it doesn't include one (Mastodon) */
  646. xs *date = xs_str_utctime(0, "%Y-%m-%dT%H:%M:%SZ");
  647. f_msg = xs_dict_set(f_msg, "published", date);
  648. }
  649. timeline_add(snac, xs_dict_get(f_msg, "id"), f_msg, NULL, NULL);
  650. follower_add(snac, actor);
  651. snac_log(snac, xs_fmt("new follower %s", actor));
  652. do_notify = 1;
  653. }
  654. else
  655. snac_log(snac, xs_fmt("repeated 'Follow' from %s", actor));
  656. }
  657. else
  658. if (strcmp(type, "Undo") == 0) {
  659. if (strcmp(utype, "Follow") == 0) {
  660. if (valid_status(follower_del(snac, actor))) {
  661. snac_log(snac, xs_fmt("no longer following us %s", actor));
  662. do_notify = 1;
  663. }
  664. else
  665. snac_log(snac, xs_fmt("error deleting follower %s", actor));
  666. }
  667. else
  668. snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype));
  669. }
  670. else
  671. if (strcmp(type, "Create") == 0) {
  672. if (strcmp(utype, "Note") == 0) {
  673. if (is_muted(snac, actor))
  674. snac_log(snac, xs_fmt("ignored 'Note' from muted actor %s", actor));
  675. else {
  676. char *id = xs_dict_get(object, "id");
  677. char *in_reply_to = xs_dict_get(object, "inReplyTo");
  678. timeline_request(snac, &in_reply_to, NULL);
  679. if (timeline_add(snac, id, object, in_reply_to, NULL)) {
  680. snac_log(snac, xs_fmt("new 'Note' %s %s", actor, id));
  681. do_notify = 1;
  682. }
  683. }
  684. }
  685. else
  686. snac_debug(snac, 1, xs_fmt("ignored 'Create' for object type '%s'", utype));
  687. }
  688. else
  689. if (strcmp(type, "Accept") == 0) {
  690. if (strcmp(utype, "Follow") == 0) {
  691. if (following_check(snac, actor)) {
  692. following_add(snac, actor, msg);
  693. snac_log(snac, xs_fmt("confirmed follow from %s", actor));
  694. }
  695. else
  696. snac_log(snac, xs_fmt("spurious follow accept from %s", actor));
  697. }
  698. else
  699. snac_debug(snac, 1, xs_fmt("ignored 'Accept' for object type '%s'", utype));
  700. }
  701. else
  702. if (strcmp(type, "Like") == 0) {
  703. if (xs_type(object) == XSTYPE_DICT)
  704. object = xs_dict_get(object, "id");
  705. timeline_admire(snac, msg, object, actor, 1);
  706. snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object));
  707. do_notify = 1;
  708. }
  709. else
  710. if (strcmp(type, "Announce") == 0) {
  711. xs *a_msg = NULL;
  712. if (xs_type(object) == XSTYPE_DICT)
  713. object = xs_dict_get(object, "id");
  714. timeline_request(snac, &object, actor);
  715. if (valid_status(object_get(object, &a_msg, NULL))) {
  716. char *who = xs_dict_get(a_msg, "attributedTo");
  717. if (who && !is_muted(snac, who)) {
  718. /* bring the actor */
  719. xs *who_o = NULL;
  720. if (valid_status(actor_request(snac, who, &who_o))) {
  721. timeline_admire(snac, msg, object, actor, 0);
  722. snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object));
  723. do_notify = 1;
  724. }
  725. else
  726. snac_log(snac, xs_fmt("dropped 'Announce' on actor request error %s", who));
  727. }
  728. else
  729. snac_log(snac, xs_fmt("ignored 'Announce' about muted actor %s", who));
  730. }
  731. else
  732. snac_log(snac, xs_fmt("error requesting 'Announce' object %s", object));
  733. }
  734. else
  735. if (strcmp(type, "Update") == 0) {
  736. if (strcmp(utype, "Person") == 0) {
  737. actor_add(snac, actor, xs_dict_get(msg, "object"));
  738. snac_log(snac, xs_fmt("updated actor %s", actor));
  739. }
  740. else
  741. if (strcmp(utype, "Note") == 0) {
  742. char *id = xs_dict_get(object, "id");
  743. object_add_ow(id, object);
  744. snac_log(snac, xs_fmt("updated post %s", id));
  745. }
  746. else
  747. snac_log(snac, xs_fmt("ignored 'Update' for object type '%s'", utype));
  748. }
  749. else
  750. if (strcmp(type, "Delete") == 0) {
  751. if (xs_type(object) == XSTYPE_DICT)
  752. object = xs_dict_get(object, "id");
  753. if (valid_status(timeline_del(snac, object)))
  754. snac_debug(snac, 1, xs_fmt("new 'Delete' %s %s", actor, object));
  755. else
  756. snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object));
  757. }
  758. else
  759. snac_debug(snac, 1, xs_fmt("process_message type '%s' ignored", type));
  760. if (do_notify)
  761. notify(snac, type, utype, actor, msg);
  762. return 1;
  763. }
  764. void process_queue(snac *snac)
  765. /* processes the queue */
  766. {
  767. xs *list;
  768. char *p, *fn;
  769. int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max"));
  770. list = queue(snac);
  771. p = list;
  772. while (xs_list_iter(&p, &fn)) {
  773. xs *q_item = dequeue(snac, fn);
  774. char *type;
  775. if (q_item == NULL) {
  776. snac_log(snac, xs_fmt("process_queue q_item error"));
  777. continue;
  778. }
  779. if ((type = xs_dict_get(q_item, "type")) == NULL)
  780. type = "output";
  781. if (strcmp(type, "message") == 0) {
  782. char *msg = xs_dict_get(q_item, "message");
  783. xs *inboxes = inbox_list(snac, msg);
  784. char *p, *v;
  785. p = inboxes;
  786. while (xs_list_iter(&p, &v)) {
  787. enqueue_output(snac, msg, v, 0);
  788. }
  789. }
  790. else
  791. if (strcmp(type, "output") == 0) {
  792. int status;
  793. char *inbox = xs_dict_get(q_item, "inbox");
  794. char *msg = xs_dict_get(q_item, "object");
  795. int retries = xs_number_get(xs_dict_get(q_item, "retries"));
  796. xs *payload = NULL;
  797. int p_size = 0;
  798. if (xs_is_null(inbox) || xs_is_null(msg))
  799. continue;
  800. /* deliver */
  801. status = send_to_inbox(snac, inbox, msg, &payload, &p_size);
  802. snac_log(snac, xs_fmt("process_queue sent to inbox %s %d", inbox, status));
  803. if (!valid_status(status)) {
  804. /* error sending; requeue? */
  805. if (status == 404 || status == 410)
  806. /* explicit error: discard */
  807. snac_log(snac, xs_fmt("process_queue error %s %d", inbox, status));
  808. else
  809. if (retries > queue_retry_max)
  810. snac_log(snac, xs_fmt("process_queue giving up %s %d", inbox, status));
  811. else {
  812. /* requeue */
  813. enqueue_output(snac, msg, inbox, retries + 1);
  814. snac_log(snac, xs_fmt("process_queue requeue %s #%d", inbox, retries + 1));
  815. }
  816. }
  817. }
  818. else
  819. if (strcmp(type, "input") == 0) {
  820. /* process the message */
  821. char *msg = xs_dict_get(q_item, "object");
  822. char *req = xs_dict_get(q_item, "req");
  823. int retries = xs_number_get(xs_dict_get(q_item, "retries"));
  824. if (!process_message(snac, msg, req)) {
  825. if (retries > queue_retry_max)
  826. snac_log(snac, xs_fmt("process_queue input giving up"));
  827. else {
  828. /* reenqueue */
  829. enqueue_input(snac, msg, req, retries + 1);
  830. snac_log(snac, xs_fmt("process_queue input requeue #%d", retries + 1));
  831. }
  832. }
  833. }
  834. else
  835. if (strcmp(type, "email") == 0) {
  836. /* send this email */
  837. char *msg = xs_dict_get(q_item, "message");
  838. int retries = xs_number_get(xs_dict_get(q_item, "retries"));
  839. FILE *f;
  840. int ok = 0;
  841. if ((f = popen("/usr/sbin/sendmail -t", "w")) != NULL) {
  842. fprintf(f, "%s\n", msg);
  843. if (pclose(f) != -1)
  844. ok = 1;
  845. }
  846. if (ok)
  847. snac_debug(snac, 1, xs_fmt("email message sent"));
  848. else {
  849. if (retries > queue_retry_max)
  850. snac_log(snac, xs_fmt("process_queue email giving up (errno: %d)", errno));
  851. else {
  852. /* requeue */
  853. snac_log(snac, xs_fmt(
  854. "process_queue email requeue #%d (errno: %d)", retries + 1, errno));
  855. enqueue_email(snac, msg, retries + 1);
  856. }
  857. }
  858. }
  859. }
  860. }
  861. void post(snac *snac, char *msg)
  862. /* enqueues a message to all its recipients */
  863. {
  864. xs *inboxes = inbox_list(snac, msg);
  865. char *p, *v;
  866. p = inboxes;
  867. while (xs_list_iter(&p, &v)) {
  868. enqueue_output(snac, msg, v, 0);
  869. }
  870. }
  871. /** HTTP handlers */
  872. int activitypub_get_handler(d_char *req, char *q_path,
  873. char **body, int *b_size, char **ctype)
  874. {
  875. int status = 200;
  876. char *accept = xs_dict_get(req, "accept");
  877. snac snac;
  878. xs *msg = NULL;
  879. if (accept == NULL)
  880. return 0;
  881. if (xs_str_in(accept, "application/activity+json") == -1 &&
  882. xs_str_in(accept, "application/ld+json") == -1)
  883. return 0;
  884. xs *l = xs_split_n(q_path, "/", 2);
  885. char *uid, *p_path;
  886. uid = xs_list_get(l, 1);
  887. if (!user_open(&snac, uid)) {
  888. /* invalid user */
  889. srv_log(xs_fmt("activitypub_get_handler bad user %s", uid));
  890. return 404;
  891. }
  892. p_path = xs_list_get(l, 2);
  893. *ctype = "application/activity+json";
  894. if (p_path == NULL) {
  895. /* if there was no component after the user, it's an actor request */
  896. msg = msg_actor(&snac);
  897. *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
  898. snac_debug(&snac, 0, xs_fmt("actor requested"));
  899. }
  900. else
  901. if (strcmp(p_path, "outbox") == 0) {
  902. xs *id = xs_fmt("%s/outbox", snac.actor);
  903. xs *elems = timeline_simple_list(&snac, "public", 0, 20);
  904. xs *list = xs_list_new();
  905. msg = msg_collection(&snac, id);
  906. char *p, *v;
  907. p = elems;
  908. while (xs_list_iter(&p, &v)) {
  909. xs *i = NULL;
  910. if (valid_status(object_get_by_md5(v, &i, NULL))) {
  911. char *type = xs_dict_get(i, "type");
  912. char *id = xs_dict_get(i, "id");
  913. if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) {
  914. i = xs_dict_del(i, "_snac");
  915. list = xs_list_append(list, i);
  916. }
  917. }
  918. }
  919. /* replace the 'orderedItems' with the latest posts */
  920. xs *items = xs_number_new(xs_list_len(list));
  921. msg = xs_dict_set(msg, "orderedItems", list);
  922. msg = xs_dict_set(msg, "totalItems", items);
  923. }
  924. else
  925. if (strcmp(p_path, "followers") == 0 || strcmp(p_path, "following") == 0) {
  926. xs *id = xs_fmt("%s/%s", snac.actor, p_path);
  927. msg = msg_collection(&snac, id);
  928. }
  929. else
  930. if (xs_startswith(p_path, "p/")) {
  931. xs *id = xs_fmt("%s/%s", snac.actor, p_path);
  932. status = object_get(id, &msg, NULL);
  933. }
  934. else
  935. status = 404;
  936. if (status == 200 && msg != NULL) {
  937. *body = xs_json_dumps_pp(msg, 4);
  938. *b_size = strlen(*body);
  939. }
  940. snac_debug(&snac, 1, xs_fmt("activitypub_get_handler serving %s %d", q_path, status));
  941. user_free(&snac);
  942. return status;
  943. }
  944. int activitypub_post_handler(d_char *req, char *q_path,
  945. d_char *payload, int p_size,
  946. char **body, int *b_size, char **ctype)
  947. /* processes an input message */
  948. {
  949. int status = 202; /* accepted */
  950. char *i_ctype = xs_dict_get(req, "content-type");
  951. snac snac;
  952. char *v;
  953. if (i_ctype == NULL)
  954. return 400;
  955. if (xs_str_in(i_ctype, "application/activity+json") == -1 &&
  956. xs_str_in(i_ctype, "application/ld+json") == -1)
  957. return 0;
  958. /* decode the message */
  959. xs *msg = xs_json_loads(payload);
  960. if (msg == NULL) {
  961. srv_log(xs_fmt("activitypub_post_handler JSON error %s", q_path));
  962. status = 400;
  963. }
  964. /* get the user and path */
  965. xs *l = xs_split_n(q_path, "/", 2);
  966. char *uid;
  967. if (xs_list_len(l) != 3 || strcmp(xs_list_get(l, 2), "inbox") != 0) {
  968. /* strange q_path */
  969. srv_debug(1, xs_fmt("activitypub_post_handler unsupported path %s", q_path));
  970. return 404;
  971. }
  972. uid = xs_list_get(l, 1);
  973. if (!user_open(&snac, uid)) {
  974. /* invalid user */
  975. srv_debug(1, xs_fmt("activitypub_post_handler bad user %s", uid));
  976. return 404;
  977. }
  978. /* if it has a digest, check it now, because
  979. later the payload won't be exactly the same */
  980. if ((v = xs_dict_get(req, "digest")) != NULL) {
  981. xs *s1 = xs_sha256_base64(payload, p_size);
  982. xs *s2 = xs_fmt("SHA-256=%s", s1);
  983. if (strcmp(s2, v) != 0) {
  984. srv_log(xs_fmt("digest check FAILED"));
  985. status = 400;
  986. }
  987. }
  988. if (valid_status(status)) {
  989. enqueue_input(&snac, msg, req, 0);
  990. *ctype = "application/activity+json";
  991. }
  992. user_free(&snac);
  993. return status;
  994. }