main.c (10628B)
1 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <netdb.h> 7 #include <sys/types.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <arpa/inet.h> 11 #include <ctype.h> 12 #include <assert.h> 13 #include <base64.h> 14 15 #include "http.h" 16 #include "ap_json.h" 17 #include "inbox.h" 18 #include "outbox.h" 19 #include "json.h" 20 #include "sigcheck.h" 21 #include "env.h" 22 #include "post.h" 23 24 #define BUF_SIZE 1048576 25 #define ARENA_SIZE 134217728 /* 128 MB virtual mem arena */ 26 #define streq(a,b) (!strcmp(a,b)) 27 #define patheq(req, x) (streq(x, req->path) || streq(x "/", req->path)) 28 29 struct webfinger { 30 const char *acct; 31 const char *alias; 32 const char *profile_page; 33 const char *self; 34 }; 35 36 static void error(char *msg) 37 { 38 perror(msg); 39 exit(1); 40 } 41 42 43 #define SCHEMA "https://" 44 45 static void init_test_webfinger(struct webfinger *finger) 46 { 47 static unsigned char buf[512]; 48 struct cursor c; 49 make_cursor(buf, buf+sizeof(buf), &c); 50 51 const char *host = get_hostname(); 52 const char *id = get_id(); 53 assert(host); 54 55 finger->acct = (char*)c.p; 56 57 assert( 58 push_str(&c, "jb55@") && push_c_str(&c, id) 59 ); 60 61 finger->alias = (char*)c.p; 62 63 assert( 64 push_str(&c, SCHEMA) && push_str(&c, id) && 65 push_c_str(&c, "/") 66 ); 67 68 finger->profile_page = (char*)c.p; 69 70 assert( 71 push_str(&c, SCHEMA) && push_str(&c, host) && 72 push_c_str(&c, "/profile") 73 ); 74 75 finger->self = (char*)c.p; 76 77 assert( 78 push_str(&c, SCHEMA) && push_str(&c, id) && 79 push_c_str(&c, "/") 80 ); 81 } 82 83 static int handle_webfinger(struct http_req *req) 84 { 85 struct webfinger finger; 86 87 init_test_webfinger(&finger); 88 89 http_write_header(req, "200 OK"); 90 91 fprintf(req->client.socket_file, 92 "{\"subject\": \"acct:%s\"," 93 " \"aliases\": [\"%s\"]," 94 " \"links\": [{" 95 " \"rel\": \"http://webfinger.net/rel/profile-page\"," 96 " \"type\": \"text/html\"," 97 " \"href\": \"%s\"" 98 " },{" 99 " \"rel\":\"self\"," 100 " \"type\": \"application/activity+json\"," 101 " \"href\": \"%s\"" 102 " }]}\r\n", 103 finger.acct, 104 finger.alias, 105 finger.profile_page, 106 finger.self); 107 108 return 1; 109 } 110 111 112 static inline int starts_with(const char *str, const char *prefix) 113 { 114 unsigned int prefix_size; 115 prefix_size = strlen(prefix); 116 117 if (prefix_size > strlen(str)) { 118 return 0; 119 } 120 121 return !memcmp(str, prefix, prefix_size); 122 } 123 124 static void http_log(struct http_req *req) 125 { 126 printf("%s - \"%s %s %s\"\n", 127 req->client.addr_str, 128 req->method, 129 req->path, 130 req->ver); 131 } 132 133 static int handle_inbox_request(struct http_req *req, struct cursor *arena) 134 { 135 const char *signature; 136 unsigned char *start; 137 FILE *out; 138 int len; 139 struct sig_header sig; 140 struct json_parser pull; 141 struct json_pusher push; 142 struct ap_json apjson; 143 144 struct json_handlers compact; 145 struct json_handlers ap_handler; 146 147 make_compact_handlers(&compact, &push); 148 make_ap_json_pusher(&ap_handler, &apjson); 149 init_ap_json(&apjson, &compact); 150 151 memset(&sig, 0, sizeof(sig)); 152 153 if (!get_header(req->headers, "signature", &signature)) { 154 note_error(&req->errs, "signature"); 155 return 0; 156 } 157 158 printf("signature: %s\n", signature); 159 160 if (!parse_signature_header(&req->errs, arena, signature, strlen(signature), &sig)) { 161 note_error(&req->errs, "parse signature header"); 162 return 0; 163 } 164 165 apjson.sig = &sig; 166 apjson.req = req; 167 168 start = arena->p; 169 init_json_pusher_with(&push, arena); 170 171 init_json_parser(&pull, req->body, req->body_len, &ap_handler); 172 if (!parse_json(&pull)) { 173 note_error(&req->errs, "json parse failed"); 174 return 0; 175 } 176 177 if (!(out = fopen("activities.json", "a"))) { 178 note_error(&req->errs, "could not open activities.json"); 179 return 0; 180 } 181 182 len = push.cur.p - start; 183 184 // 128 KB 185 if (len >= 131072) { 186 note_error(&req->errs, "ActivityPub message too big"); 187 return 0; 188 } 189 190 fwrite(start, len, 1, out); 191 fwrite("\n", 1, 1, out); 192 fclose(out); 193 194 http_write_header(req, "200 OK"); 195 return 1; 196 } 197 198 static inline int is_get(struct http_req *req) 199 { 200 return streq(req->method, "GET"); 201 } 202 203 static inline int is_post(struct http_req *req) 204 { 205 return streq(req->method, "POST"); 206 } 207 208 209 static inline int is_accept_activity(struct http_req *req) 210 { 211 const char *accept; 212 213 if (!get_header(req->headers, "accept", &accept)) { 214 note_error(&req->errs, "accept"); 215 return 0; 216 } 217 218 if (!(strstr(accept, "application/activity+json") || 219 strstr(accept, "application/ld+json"))) { 220 note_error(&req->errs, "not accept: '%s'", accept); 221 return 0; 222 } 223 224 return 1; 225 } 226 227 struct profile 228 { 229 const char *id; 230 const char *type; 231 const char *username; 232 const char *name; 233 const char *summary; 234 const char *url; 235 int manually_approves_followers; 236 const char *image_mime_type; 237 const char *image_url; 238 const char *key_id; 239 const char *pubkey_pem; 240 }; 241 242 static void test_profile(struct profile *p) 243 { 244 static unsigned char buf[128]; 245 struct cursor c; 246 make_cursor(buf, buf + sizeof(buf), &c); 247 248 const char *host = get_hostname(); 249 const char *id = get_id(); 250 251 assert(host); 252 253 p->id = (char*)c.p; 254 assert( 255 push_str(&c, SCHEMA) && push_str(&c, id) && push_c_str(&c, "/") 256 ); 257 258 p->url = (char*)c.p; 259 assert( 260 push_str(&c, SCHEMA) && push_str(&c, host) && push_c_str(&c, "/") 261 ); 262 263 p->type = "Person"; 264 p->username = "jb55"; 265 p->name = "William Casarin"; 266 p->summary = "chibipub prototype"; 267 p->manually_approves_followers = 0; 268 p->image_mime_type = "image/jpeg"; 269 p->image_url = "https://jb55.com/s/blue-me.jpg"; 270 p->key_id = "main-key"; 271 p->pubkey_pem = get_pubkey(); 272 } 273 274 static int handle_self(struct http_req *req) 275 { 276 struct profile profile; 277 test_profile(&profile); 278 279 http_write_header(req, "200 OK"); 280 281 fprintf(req->client.socket_file, 282 "{" 283 "\"@context\": [" 284 "\"https://www.w3.org/ns/activitystreams\"" 285 "]," 286 "\"inbox\": \"%sinbox\"," 287 "\"id\": \"%s\"," 288 "\"type\": \"%s\"," 289 "\"preferredUsername\": \"%s\"," 290 "\"name\": \"%s\"," 291 "\"summary\": \"%s\"," 292 "\"url\": \"%s\"," 293 "\"manuallyApprovesFollowers\": %s," 294 "\"icon\": {" 295 "\"type\": \"Image\"," 296 "\"mediaType\": \"%s\"," 297 "\"url\": \"%s\"" 298 "}," 299 "\"publicKey\": {" 300 "\"id\": \"%s#%s\"," 301 "\"owner\": \"%s\"," 302 "\"publicKeyPem\": \"%s\"" 303 "}" 304 "}\n", 305 profile.url, 306 profile.id, 307 profile.type, 308 profile.username, 309 profile.name, 310 profile.summary, 311 profile.url, 312 profile.manually_approves_followers ? "true" : "false", 313 profile.image_mime_type, 314 profile.image_url, 315 profile.id, profile.key_id, 316 profile.id, 317 profile.pubkey_pem); 318 319 return 1; 320 } 321 322 static int handle_request(struct http_req *req, struct cursor *arena) 323 { 324 http_log(req); 325 326 if (starts_with(req->path, "/.well-known/webfinger?resource=acct:") || 327 starts_with(req->path, "/?resource=acct:")) { 328 return handle_webfinger(req); 329 } else if (is_post(req) && patheq(req, "/inbox")) { 330 return handle_inbox_request(req, arena); 331 } else if (is_get(req) && patheq(req, "/outbox")) { 332 return handle_outbox_request(req); 333 } else if (is_get(req) && patheq(req, "/outbox?page=true")) { 334 return handle_outbox_page_request(req, "outbox.json"); 335 } else if (is_get(req) && 336 is_accept_activity(req) && 337 streq(req->path, "/")) { 338 return handle_self(req); 339 } 340 341 return 0; 342 } 343 344 static int http_accept_client(struct http_req *req, int parent) 345 { 346 struct sockaddr_in *addr; 347 socklen_t client_len; 348 struct hostent *client_host; 349 350 client_len = sizeof(addr); 351 352 addr = &req->client.sockaddr; 353 354 req->client.socket = 355 accept(parent, (struct sockaddr *) &req->client.sockaddr, 356 &client_len); 357 358 if (req->client.socket < 0) { 359 note_error(&req->errs, "bad socket"); 360 return 0; 361 } 362 363 req->client.socket_file = fdopen(req->client.socket, "wb"); 364 365 client_host = gethostbyaddr((const char *)&addr->sin_addr.s_addr, 366 sizeof(addr->sin_addr.s_addr), AF_INET); 367 368 if (!client_host) { 369 note_error(&req->errs, "gethostbyaddr"); 370 return 0; 371 } 372 373 req->client.addr_str = inet_ntoa(addr->sin_addr); 374 375 if (!req->client.addr_str) { 376 note_error(&req->errs, "inet_htoa"); 377 return 0; 378 } 379 380 return 1; 381 } 382 383 void run_http_server() 384 { 385 static unsigned char buffer[BUF_SIZE]; 386 unsigned char *arena; 387 388 ssize_t len, size; 389 390 int parent; 391 struct sockaddr_in server_addr; 392 int optval; 393 394 struct http_req req; 395 396 struct parser parser; 397 398 const int port = 5188; 399 arena = malloc(ARENA_SIZE); 400 assert(arena); 401 402 if ((parent = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 403 error("socket"); 404 } 405 406 optval = 1; 407 setsockopt(parent, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, 408 sizeof(optval)); 409 410 memset(&server_addr, 0, sizeof(server_addr)); 411 server_addr.sin_family = AF_INET; 412 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 413 server_addr.sin_port = htons(port); 414 415 if (bind(parent, (struct sockaddr *)&server_addr, 416 sizeof(server_addr)) < 0) { 417 error("bind"); 418 } 419 420 printf("listening for activities on 0.0.0.0:%d\n", port); 421 422 if (listen(parent, 5) < 0) { 423 error("listen"); 424 } 425 426 while (1) { 427 init_http_req(&req); 428 make_cursor(buffer, buffer + BUF_SIZE, &parser.cur); 429 make_cursor(arena, arena + ARENA_SIZE, &parser.arena); 430 431 http_accept_client(&req, parent); 432 433 for(size = 0;1;) { 434 len = read(req.client.socket, (void*)(buffer+size), BUF_SIZE-size); 435 size += len; 436 if (len == 0) { 437 break; 438 } 439 440 if (len != (BUF_SIZE-size)) { 441 break; 442 } 443 } 444 445 if (parse_http_request(&req, &parser, size)) { 446 handle_request(&req, &parser.arena); 447 } 448 449 fclose(req.client.socket_file); 450 close(req.client.socket); 451 } 452 453 free(arena); 454 } 455 456 static int load_config() 457 { 458 if (!get_hostname()) { 459 printf("CHIBIPUB_HOST env not set\n"); 460 return 0; 461 } 462 return 1; 463 } 464 465 static int checksigs() 466 { 467 struct sigcheck check; 468 check.activity_file = "activities.json"; 469 if (!sigcheck(&check)) { 470 printf("sigcheck failed\n"); 471 return 0; 472 } 473 printf("ok\n"); 474 return 1; 475 } 476 477 int usage() 478 { 479 printf( 480 "usage: chibipub [OPTION]... <command>\n" 481 "\n" 482 "commands\n" 483 "\n" 484 "help show this help\n" 485 "serve run the server\n" 486 "post post a new message\n" 487 "checksigs check inbox signatures\n" 488 ); 489 490 return 0; 491 } 492 493 static int serve() 494 { 495 if (!load_config()) 496 return usage(); 497 run_http_server(); 498 return 0; 499 } 500 501 static int mkpost(struct chibipub *pub, int argc, char **argv) 502 { 503 struct post post; 504 505 if (argc != 1) 506 return 1; 507 508 post.content = argv[0]; 509 510 return !create_post(pub, &post); 511 } 512 513 int main(int argc, char *argv[]) 514 { 515 struct chibipub pub; 516 517 if (argc < 2) { 518 return usage(); 519 } 520 521 // early cmds 522 if (!strcmp(argv[1], "help") || !strcmp(argv[1], "--help")) { 523 return usage(); 524 } 525 526 if (!init_chibipub(&pub)) 527 return 1; 528 529 if (!strcmp(argv[1], "serve")) { 530 return serve(); 531 } else if (!strcmp(argv[1], "post")) { 532 if (argc - 2 < 1) return usage(); 533 return mkpost(&pub, argc - 2, &argv[2]); 534 } else if (!strcmp(argv[1], "checksigs")) { 535 return checksigs(); 536 } else { 537 return usage(); 538 } 539 }