2.52
main.c
1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2024 grunfink et al. / MIT license */
3
4#include "xs.h"
5#include "xs_io.h"
6#include "xs_json.h"
7#include "xs_time.h"
8#include "xs_openssl.h"
9
10#include "snac.h"
11
12#include <sys/stat.h>
13
14int usage(void)
15{
16 printf("snac " VERSION " - A simple, minimalistic ActivityPub instance\n");
17 printf("Copyright (c) 2022 - 2024 grunfink et al. / MIT license\n");
18 printf("\n");
19 printf("Commands:\n");
20 printf("\n");
21 printf("init [{basedir}] Initializes the data storage\n");
22 printf("upgrade {basedir} Upgrade to a new version\n");
23 printf("adduser {basedir} [{uid}] Adds a new user\n");
24 printf("deluser {basedir} {uid} Deletes a user\n");
25 printf("httpd {basedir} Starts the HTTPD daemon\n");
26 printf("purge {basedir} Purges old data\n");
27 printf("state {basedir} Prints server state\n");
28 printf("webfinger {basedir} {actor} Queries about an actor (@user@host or actor url)\n");
29 printf("queue {basedir} {uid} Processes a user queue\n");
30 printf("follow {basedir} {uid} {actor} Follows an actor\n");
31 printf("unfollow {basedir} {uid} {actor} Unfollows an actor\n");
32 printf("request {basedir} {uid} {url} Requests an object\n");
33 printf("actor {basedir} [{uid}] {url} Requests an actor\n");
34 printf("note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n");
35 printf("boost|announce {basedir} {uid} {url} Boosts (announces) a post\n");
36 printf("unboost {basedir} {uid} {url} Unboosts a post\n");
37 printf("resetpwd {basedir} {uid} Resets the password of a user\n");
38 printf("ping {basedir} {uid} {actor} Pings an actor\n");
39 printf("webfinger_s {basedir} {uid} {actor} Queries about an actor (@user@host or actor url)\n");
40 printf("pin {basedir} {uid} {msg_url} Pins a message\n");
41 printf("unpin {basedir} {uid} {msg_url} Unpins a message\n");
42 printf("block {basedir} {instance_url} Blocks a full instance\n");
43 printf("unblock {basedir} {instance_url} Unblocks a full instance\n");
44 printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n");
45 printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n");
46 printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n");
47
48 return 1;
49}
50
51
52char *get_argv(int *argi, int argc, char *argv[])
53{
54 if (*argi < argc)
55 return argv[(*argi)++];
56 else
57 return NULL;
58}
59
60
61#define GET_ARGV() get_argv(&argi, argc, argv)
62
63int main(int argc, char *argv[])
64{
65 char *cmd;
66 char *basedir;
67 char *user;
68 char *url;
69 int argi = 1;
70 snac snac;
71
72 /* ensure group has write access */
73 umask(0007);
74
75 if ((cmd = GET_ARGV()) == NULL)
76 return usage();
77
78 if (strcmp(cmd, "init") == 0) { /** **/
79 /* initialize the data storage */
80 /* ... */
81 basedir = GET_ARGV();
82
83 return snac_init(basedir);
84 }
85
86 if (strcmp(cmd, "upgrade") == 0) { /** **/
87 int ret;
88
89 /* upgrade */
90 if ((basedir = GET_ARGV()) == NULL)
91 return usage();
92
93 if ((ret = srv_open(basedir, 1)) == 1)
94 srv_log(xs_dup("OK"));
95
96 return ret;
97 }
98
99 if (strcmp(cmd, "markdown") == 0) { /** **/
100 /* undocumented, for testing only */
101 xs *c = xs_readall(stdin);
102 xs *fc = not_really_markdown(c, NULL, NULL);
103
104 printf("<html>\n%s\n</html>\n", fc);
105 return 0;
106 }
107
108 if ((basedir = GET_ARGV()) == NULL)
109 return usage();
110
111 if (!srv_open(basedir, 0)) {
112 srv_log(xs_fmt("error opening data storage at %s", basedir));
113 return 1;
114 }
115
116 if (strcmp(cmd, "adduser") == 0) { /** **/
117 user = GET_ARGV();
118
119 return adduser(user);
120
121 return 0;
122 }
123
124 if (strcmp(cmd, "httpd") == 0) { /** **/
125 httpd();
126 srv_free();
127 return 0;
128 }
129
130 if (strcmp(cmd, "purge") == 0) { /** **/
131 purge_all();
132 return 0;
133 }
134
135 if (strcmp(cmd, "state") == 0) { /** **/
136 xs *shm_name = NULL;
137 srv_state *p_state = srv_state_op(&shm_name, 1);
138
139 if (p_state == NULL)
140 return 1;
141
142 srv_state ss = *p_state;
143 int n;
144
145 printf("server: %s (%s)\n", xs_dict_get(srv_config, "host"), USER_AGENT);
146 xs *uptime = xs_str_time_diff(time(NULL) - ss.srv_start_time);
147 printf("uptime: %s\n", uptime);
148 printf("job fifo size (cur): %d\n", ss.job_fifo_size);
149 printf("job fifo size (peak): %d\n", ss.peak_job_fifo_size);
150 char *th_states[] = { "stopped", "waiting", "input", "output" };
151
152 for (n = 0; n < ss.n_threads; n++)
153 printf("thread #%d state: %s\n", n, th_states[ss.th_state[n]]);
154
155 return 0;
156 }
157
158 if ((user = GET_ARGV()) == NULL)
159 return usage();
160
161 if (strcmp(cmd, "block") == 0) { /** **/
162 int ret = instance_block(user);
163
164 if (ret < 0) {
165 fprintf(stderr, "Error blocking instance %s: %d\n", user, ret);
166 return 1;
167 }
168
169 return 0;
170 }
171
172 if (strcmp(cmd, "unblock") == 0) { /** **/
173 int ret = instance_unblock(user);
174
175 if (ret < 0) {
176 fprintf(stderr, "Error unblocking instance %s: %d\n", user, ret);
177 return 1;
178 }
179
180 return 0;
181 }
182
183 if (strcmp(cmd, "webfinger") == 0) { /** **/
184 xs *actor = NULL;
185 xs *uid = NULL;
186 int status;
187
188 status = webfinger_request(user, &actor, &uid);
189
190 printf("status: %d\n", status);
191 if (actor != NULL)
192 printf("actor: %s\n", actor);
193 if (uid != NULL)
194 printf("uid: %s\n", uid);
195
196 return 0;
197 }
198
199 if (argi == argc && strcmp(cmd, "actor") == 0) { /** **/
200 /* query an actor without user (non-signed) */
201 xs *actor = NULL;
202 int status;
203
204 status = actor_request(NULL, user, &actor);
205
206 printf("status: %d\n", status);
207 if (valid_status(status)) {
208 xs_json_dump(actor, 4, stdout);
209 printf("\n");
210 }
211
212 return 0;
213 }
214
215 if (!user_open(&snac, user)) {
216 printf("invalid user '%s'\n", user);
217 return 1;
218 }
219
220 lastlog_write(&snac, "cmdline");
221
222 if (strcmp(cmd, "resetpwd") == 0) { /** **/
223 return resetpwd(&snac);
224 }
225
226 if (strcmp(cmd, "deluser") == 0) { /** **/
227 return deluser(&snac);
228 }
229
230 if (strcmp(cmd, "update") == 0) { /** **/
231 xs *a_msg = msg_actor(&snac);
232 xs *u_msg = msg_update(&snac, a_msg);
233 enqueue_message(&snac, u_msg);
234 return 0;
235 }
236
237 if (strcmp(cmd, "queue") == 0) { /** **/
238 process_user_queue(&snac);
239 return 0;
240 }
241
242 if (strcmp(cmd, "verify_links") == 0) { /** **/
243 verify_links(&snac);
244 return 0;
245 }
246
247 if (strcmp(cmd, "timeline") == 0) { /** **/
248#if 0
249 xs *list = local_list(&snac, XS_ALL);
250 xs *body = html_timeline(&snac, list, 1);
251
252 printf("%s\n", body);
253 user_free(&snac);
254 srv_free();
255#endif
256
257 xs *idx = xs_fmt("%s/private.idx", snac.basedir);
258 xs *list = index_list_desc(idx, 0, 256);
259 xs *tl = timeline_top_level(&snac, list);
260
261 xs_json_dump(tl, 4, stdout);
262
263 return 0;
264 }
265
266 if ((url = GET_ARGV()) == NULL)
267 return usage();
268
269 if (strcmp(cmd, "webfinger_s") == 0) { /** **/
270 xs *actor = NULL;
271 xs *uid = NULL;
272 int status;
273
274 status = webfinger_request_signed(&snac, url, &actor, &uid);
275
276 printf("status: %d\n", status);
277 if (actor != NULL)
278 printf("actor: %s\n", actor);
279 if (uid != NULL)
280 printf("uid: %s\n", uid);
281
282 return 0;
283 }
284
285 if (strcmp(cmd, "boost") == 0 || strcmp(cmd, "announce") == 0) { /** **/
286 xs *msg = msg_admiration(&snac, url, "Announce");
287
288 if (msg != NULL) {
289 enqueue_message(&snac, msg);
290
291 if (dbglevel) {
292 xs_json_dump(msg, 4, stdout);
293 }
294 }
295
296 return 0;
297 }
298
299 if (strcmp(cmd, "unboost") == 0) { /** **/
300 xs *msg = msg_repulsion(&snac, url, "Announce");
301
302 if (msg != NULL) {
303 enqueue_message(&snac, msg);
304
305 if (dbglevel) {
306 xs_json_dump(msg, 4, stdout);
307 }
308 }
309
310 return 0;
311 }
312
313 if (strcmp(cmd, "follow") == 0) { /** **/
314 xs *msg = msg_follow(&snac, url);
315
316 if (msg != NULL) {
317 char *actor = xs_dict_get(msg, "object");
318
319 following_add(&snac, actor, msg);
320
321 enqueue_output_by_actor(&snac, msg, actor, 0);
322
323 if (dbglevel) {
324 xs_json_dump(msg, 4, stdout);
325 }
326 }
327
328 return 0;
329 }
330
331 if (strcmp(cmd, "unfollow") == 0) { /** **/
332 xs *object = NULL;
333
334 if (valid_status(following_get(&snac, url, &object))) {
335 xs *msg = msg_undo(&snac, xs_dict_get(object, "object"));
336
337 following_del(&snac, url);
338
339 enqueue_output_by_actor(&snac, msg, url, 0);
340
341 snac_log(&snac, xs_fmt("unfollowed actor %s", url));
342 }
343 else
344 snac_log(&snac, xs_fmt("actor is not being followed %s", url));
345
346 return 0;
347 }
348
349 if (strcmp(cmd, "limit") == 0) { /** **/
350 int ret;
351
352 if (!following_check(&snac, url))
353 snac_log(&snac, xs_fmt("actor %s is not being followed", url));
354 else
355 if ((ret = limit(&snac, url)) == 0)
356 snac_log(&snac, xs_fmt("actor %s is now limited", url));
357 else
358 snac_log(&snac, xs_fmt("error limiting actor %s (%d)", url, ret));
359
360 return 0;
361 }
362
363 if (strcmp(cmd, "unlimit") == 0) { /** **/
364 int ret;
365
366 if (!following_check(&snac, url))
367 snac_log(&snac, xs_fmt("actor %s is not being followed", url));
368 else
369 if ((ret = unlimit(&snac, url)) == 0)
370 snac_log(&snac, xs_fmt("actor %s is no longer limited", url));
371 else
372 snac_log(&snac, xs_fmt("error unlimiting actor %s (%d)", url, ret));
373
374 return 0;
375 }
376
377 if (strcmp(cmd, "ping") == 0) { /** **/
378 xs *actor_o = NULL;
379
380 if (!xs_startswith(url, "https:/")) {
381 /* try to resolve via webfinger */
382 if (!valid_status(webfinger_request(url, &url, NULL))) {
383 srv_log(xs_fmt("cannot resolve %s via webfinger", url));
384 return 1;
385 }
386 }
387
388 if (valid_status(actor_request(&snac, url, &actor_o))) {
389 xs *msg = msg_ping(&snac, url);
390
391 enqueue_output_by_actor(&snac, msg, url, 0);
392
393 if (dbglevel) {
394 xs_json_dump(msg, 4, stdout);
395 }
396
397 srv_log(xs_fmt("Ping sent to %s -- see log for Pong reply", url));
398 }
399 else {
400 srv_log(xs_fmt("Error getting actor %s", url));
401 return 1;
402 }
403
404 return 0;
405 }
406
407 if (strcmp(cmd, "pin") == 0) { /** **/
408 int ret = pin(&snac, url);
409 if (ret < 0) {
410 fprintf(stderr, "error pinning %s %d\n", url, ret);
411 return 1;
412 }
413
414 return 0;
415 }
416
417 if (strcmp(cmd, "unpin") == 0) { /** **/
418 int ret = unpin(&snac, url);
419 if (ret < 0) {
420 fprintf(stderr, "error unpinning %s %d\n", url, ret);
421 return 1;
422 }
423
424 return 0;
425 }
426
427 if (strcmp(cmd, "question") == 0) { /** **/
428 int end_secs = 5 * 60;
429 xs *opts = xs_split(url, ";");
430
431 xs *msg = msg_question(&snac, "Poll", NULL, opts, 0, end_secs);
432 xs *c_msg = msg_create(&snac, msg);
433
434 if (dbglevel) {
435 xs_json_dump(c_msg, 4, stdout);
436 }
437
438 enqueue_message(&snac, c_msg);
439 enqueue_close_question(&snac, xs_dict_get(msg, "id"), end_secs);
440
441 timeline_add(&snac, xs_dict_get(msg, "id"), msg);
442
443 return 0;
444 }
445
446 if (strcmp(cmd, "request") == 0) { /** **/
447 int status;
448 xs *data = NULL;
449
450 status = activitypub_request(&snac, url, &data);
451
452 printf("status: %d\n", status);
453
454 if (data != NULL) {
455 xs_json_dump(data, 4, stdout);
456 }
457
458 return 0;
459 }
460
461 if (strcmp(cmd, "request2") == 0) { /** **/
462 enqueue_object_request(&snac, url, 2);
463
464 return 0;
465 }
466
467 if (strcmp(cmd, "actor") == 0) { /** **/
468 int status;
469 xs *data = NULL;
470
471 status = actor_request(&snac, url, &data);
472
473 printf("status: %d\n", status);
474
475 if (valid_status(status)) {
476 xs_json_dump(data, 4, stdout);
477 }
478
479 return 0;
480 }
481
482 if (strcmp(cmd, "note") == 0) { /** **/
483 xs *content = NULL;
484 xs *msg = NULL;
485 xs *c_msg = NULL;
486 xs *attl = xs_list_new();
487 char *fn = NULL;
488
489 /* iterate possible attachments */
490 while ((fn = GET_ARGV())) {
491 FILE *f;
492
493 if ((f = fopen(fn, "rb")) != NULL) {
494 /* get the file size and content */
495 fseek(f, 0, SEEK_END);
496 int sz = ftell(f);
497 fseek(f, 0, SEEK_SET);
498 xs *atc = xs_readall(f);
499 fclose(f);
500
501 char *ext = strrchr(fn, '.');
502 xs *hash = xs_md5_hex(fn, strlen(fn));
503 xs *id = xs_fmt("%s%s", hash, ext);
504 xs *url = xs_fmt("%s/s/%s", snac.actor, id);
505
506 /* store */
507 static_put(&snac, id, atc, sz);
508
509 xs *l = xs_list_new();
510
511 l = xs_list_append(l, url);
512 l = xs_list_append(l, ""); /* alt text */
513
514 attl = xs_list_append(attl, l);
515 }
516 else
517 fprintf(stderr, "Error opening '%s' as attachment\n", fn);
518 }
519
520 if (strcmp(url, "-e") == 0) {
521 /* get the content from an editor */
522 FILE *f;
523
524 unlink("/tmp/snac-edit.txt");
525 system("$EDITOR /tmp/snac-edit.txt");
526
527 if ((f = fopen("/tmp/snac-edit.txt", "r")) != NULL) {
528 content = xs_readall(f);
529 fclose(f);
530
531 unlink("/tmp/snac-edit.txt");
532 }
533 else {
534 printf("Nothing to send\n");
535 return 1;
536 }
537 }
538 else
539 if (strcmp(url, "-") == 0) {
540 /* get the content from stdin */
541 content = xs_readall(stdin);
542 }
543 else
544 content = xs_dup(url);
545
546 msg = msg_note(&snac, content, NULL, NULL, attl, 0);
547
548 c_msg = msg_create(&snac, msg);
549
550 if (dbglevel) {
551 xs_json_dump(c_msg, 4, stdout);
552 }
553
554 enqueue_message(&snac, c_msg);
555
556 timeline_add(&snac, xs_dict_get(msg, "id"), msg);
557
558 return 0;
559 }
560
561 fprintf(stderr, "ERROR: bad command '%s'\n", cmd);
562
563 return 1;
564}