chibipub

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit a9cb8c1f980c97ba0d1a3021b6772d57dbce0315
parent dc801743c990bae3bbba4618ca32df329f7bfed0
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 18 Dec 2020 22:22:29 -0800

initial requests from masto working

Diffstat:
MMakefile | 6+++++-
Mdefault.nix | 6+++---
Msrc/http.h | 10++++++++++
Msrc/inbox.c | 4++--
Msrc/json.c | 13++++++-------
Msrc/wolfsocks.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
6 files changed, 280 insertions(+), 56 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,5 +1,5 @@ -CFLAGS = -Wall -Werror -std=c99 -Isrc $(shell pkg-config --cflags openssl) +CFLAGS = -Wall -Werror -std=gnu99 -Isrc $(shell pkg-config --cflags openssl) LDFLAGS = $(shell pkg-config --libs openssl) OBJS = src/http.o src/base64.o src/inbox.o src/json.o @@ -8,6 +8,10 @@ HEADERS = $(wildcard src/*.h) all: wolfsocks +%.o: %.c %.h + @echo "cc $<" + @$(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $< + corpus/math.json: curl -sL 'https://data.cityofnewyork.us/api/views/x4ai-kstz/rows.json?accessType=DOWNLOAD' > $@ diff --git a/default.nix b/default.nix @@ -1,7 +1,7 @@ { pkgs ? import <nixpkgs> {} }: with pkgs; stdenv.mkDerivation { - name = "project"; - nativeBuildInputs = [ gdb ]; - buildInputs = [ ]; + name = "wolfsocks"; + nativeBuildInputs = [ gdb pkg-config ngrok ]; + buildInputs = [ openssl ]; } diff --git a/src/http.h b/src/http.h @@ -4,6 +4,8 @@ #include "cursor.h" #include "errors.h" +#include <netinet/in.h> + struct parser { struct cursor cur; struct cursor arena; @@ -15,10 +17,18 @@ struct http_header { struct http_header *next; }; +struct client { + int socket; + FILE *socket_file; + struct sockaddr_in sockaddr; + const char *addr_str; +}; + struct http_req { char *method; char *path; char *ver; + struct client client; struct http_header *headers; struct errors errs; }; diff --git a/src/inbox.c b/src/inbox.c @@ -86,8 +86,8 @@ int parse_signature_header(struct errors *errs, struct cursor *arena, } out->signature_len = out_len; - arena->p += out_len; + if (!push_byte(arena, 0)) { note_error(errs, "oom"); return 0; @@ -114,5 +114,5 @@ int verify_signature_header(struct sig_header *sig) } printf("\n"); - return 1; + return 0; } diff --git a/src/json.c b/src/json.c @@ -170,11 +170,10 @@ static int push_ubjson_str(struct cursor *ubjson, unsigned char *text, return 1; } -static int parse_escape(struct json_parser *p) +static inline int parse_escape(struct json_parser *p) { - (void)p; - note_error(&p->errs, "implement parse_escape"); - return 0; + p->cur.p++; + return 1; } static int parse_string(struct json_parser *p) @@ -474,8 +473,9 @@ static int parse_array(struct json_parser *p) note_error(&p->errs, "expected ',', got '%c'", c); return 0; } - } + p->cur.p++; + } return 0; } @@ -528,6 +528,7 @@ static int parse_value(struct json_parser *p) p->errs.record = 1; if (!res) { + print_around(&p->cur, 20); note_error(&p->errs, "couldn't parse json value"); return 0; } @@ -615,7 +616,6 @@ static int parse_ubjson_string(struct ubjson *ubjson, struct json *val) byte = 0; if (!parse_char(&ubjson->cur, &byte, 'S')) { - print_around(&ubjson->cur, 10); note_error(&ubjson->errs, "expected S tag, got '%c'", byte); return 0; } @@ -811,7 +811,6 @@ int ubjson_lookup(struct ubjson *ubjson, const char **path, int path_len, struct seg = path[i]; if (!ubjson_obj_lookup(&next, seg, val)) { - print_around(&ubjson->cur, 10); note_error(&ubjson->errs, "lookup path segment: '%s'", seg); return 0; } diff --git a/src/wolfsocks.c b/src/wolfsocks.c @@ -17,6 +17,15 @@ #define BUF_SIZE 4096 #define ARENA_SIZE 134217728 /* 128 MB virtual mem arena */ +#define streq(a,b) (!strcmp(a,b)) +#define patheq(req, x) (streq(x, req->path) || streq(x "/", req->path)) + +struct webfinger { + const char *acct; + const char *alias; + const char *profile_page; + const char *self; +}; static void error(char *msg) { @@ -24,22 +33,86 @@ static void error(char *msg) exit(1); } -static int handle_request(struct http_req *req, struct cursor *arena) +#define SCHEMA "https://" +#define HOST "fe6d51cd2814.ngrok.io" + +static void init_test_webfinger(struct webfinger *finger) { - struct sig_header sig; - int inbox_req; - const char *signature; - memset(&sig, 0, sizeof(sig)); + finger->acct = "jb55@" HOST; + finger->alias = SCHEMA HOST "/alias"; + finger->profile_page = SCHEMA HOST "/profile"; + finger->self = SCHEMA HOST "/"; +} + +static inline void write_status(struct http_req *req, const char *status) +{ + fprintf(req->client.socket_file, "HTTP/1.1 %s\r\n", status); +} + +static inline void write_resp_header(struct http_req *req, const char *status) +{ + write_status(req, status); + fprintf(req->client.socket_file, "Connection: close\r\n"); + fprintf(req->client.socket_file, "\r\n"); +} - inbox_req = !strcmp(req->method, "POST") && - (!strcmp(req->path, "/inbox") || - !strcmp(req->path, "/inbox/")); +static int handle_webfinger(struct http_req *req) +{ + struct webfinger finger; + + init_test_webfinger(&finger); + + write_resp_header(req, "200 OK"); + + fprintf(req->client.socket_file, + "{\"subject\": \"acct:%s\"," + " \"aliases\": [\"%s\"]," + " \"links\": [{" + " \"rel\": \"http://webfinger.net/rel/profile-page\"," + " \"type\": \"text/html\"," + " \"href\": \"%s\"" + " },{" + " \"rel\":\"self\"," + " \"type\": \"application/activity+json\"," + " \"href\": \"%s\"" + " }]}\r\n", + finger.acct, + finger.alias, + finger.profile_page, + finger.self); + + return 1; +} + + +static inline int starts_with(const char *str, const char *prefix) +{ + unsigned int prefix_size; + prefix_size = strlen(prefix); - if (!inbox_req) { - note_error(&req->errs, "404"); + if (prefix_size > strlen(str)) { return 0; } + return !memcmp(str, prefix, prefix_size); +} + +static void http_log(struct http_req *req) +{ + printf("%s - \"%s %s %s\"\n", + req->client.addr_str, + req->method, + req->path, + req->ver); +} + +static int handle_inbox_request(struct http_req *req, struct cursor *arena) +{ + const char *signature; + struct sig_header sig; + + memset(&sig, 0, sizeof(sig)); + if (!get_header(req->headers, "signature", &signature)) { note_error(&req->errs, "signature"); return 0; @@ -60,18 +133,177 @@ static int handle_request(struct http_req *req, struct cursor *arena) return 1; } +static inline int is_get(struct http_req *req) +{ + return streq(req->method, "GET"); +} + +static inline int is_post(struct http_req *req) +{ + return streq(req->method, "POST"); +} + + +static inline int is_accept_activity(struct http_req *req) +{ + const char *accept; + + if (!get_header(req->headers, "accept", &accept)) { + note_error(&req->errs, "accept"); + return 0; + } + + if (!strstr(accept, "application/activity+json")) { + note_error(&req->errs, "not accept: '%s'", accept); + return 0; + } + + return 1; +} + +struct profile +{ + const char *id; + const char *type; + const char *username; + const char *name; + const char *summary; + const char *url; + int manually_approves_followers; + const char *image_mime_type; + const char *image_url; + const char *key_id; + const char *pubkey_pem; +}; + +static void test_profile(struct profile *p) +{ + p->id = SCHEMA HOST "/"; + p->type = "Person"; + p->username = "jb55"; + p->name = "William Casarin"; + p->summary = "wolfsocks prototype"; + p->url = p->id; + p->manually_approves_followers = 0; + p->image_mime_type = "image/jpeg"; + p->image_url = "https://jb55.com/s/blue-me.jpg"; + p->key_id = "main-key"; + p->pubkey_pem = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnJOPxwmRGBBQYm7YgHRubTaYaKbMoEQiui+37nizXA73CRNeKblSXIaJnfOKfz/ttRG0GH43GzHTpghUDuZX+QBpyOk8UMmCW5gM0Y5c3IOv0zLezqLXrVEM8UXMUHE3hxf61r1NKl1+IG9MwhtHayx0Kaz6vT/V8nkotCSlb91lMT8X28bButwN86RCclZncecQXuVvgXnFeZCeBLM+qV2tBPnn14Ws+AqVvVnBW8xXwVfSPFHQchSLAusdWI7Kw/oWN/on2CqfRASoaVASqKG+uPuJ+1f92iH0ZY1wLB2/ITl7HKTiIMKNikXTWcUudkMlKxc5Iqb7HMHuaPZ9IQIDAQAB-----END PUBLIC KEY-----"; +} + +static int handle_self(struct http_req *req) +{ + struct profile profile; + test_profile(&profile); + + write_resp_header(req, "200 OK"); + + fprintf(req->client.socket_file, + "{" + "\"@context\": [" + "\"https://www.w3.org/ns/activitystreams\"" + "]," + "\"inbox\": \"%sinbox\"," + "\"id\": \"%s\"," + "\"type\": \"%s\"," + "\"preferredUsername\": \"%s\"," + "\"name\": \"%s\"," + "\"summary\": \"%s\"," + "\"url\": \"%s\"," + "\"manuallyApprovesFollowers\": %s," + "\"icon\": {" + "\"type\": \"Image\"," + "\"mediaType\": \"%s\"," + "\"url\": \"%s\"" + "}," + "\"publicKey\": {" + "\"id\": \"%s#%s\"," + "\"owner\": \"%s\"," + "\"publicKeyPem\": \"%s\"" + "}" + "}\n", + profile.id, + profile.id, + profile.type, + profile.username, + profile.name, + profile.summary, + profile.url, + profile.manually_approves_followers ? "true" : "false", + profile.image_mime_type, + profile.image_url, + profile.id, profile.key_id, + profile.id, + profile.pubkey_pem); + + return 1; +} + +static int handle_request(struct http_req *req, struct cursor *arena) +{ + http_log(req); + + if (starts_with(req->path, "/.well-known/webfinger?resource=acct:")) { + return handle_webfinger(req); + } else if (is_post(req) && patheq(req, "/inbox")) { + return handle_inbox_request(req, arena); + } else if (is_get(req) && + is_accept_activity(req) && + streq(req->path, "/")) { + return handle_self(req); + } + + return 0; +} + +static int http_accept_client(struct http_req *req, int parent) +{ + struct sockaddr_in *addr; + socklen_t client_len; + struct hostent *client_host; + + client_len = sizeof(addr); + + addr = &req->client.sockaddr; + + req->client.socket = + accept(parent, (struct sockaddr *) &req->client.sockaddr, + &client_len); + + if (req->client.socket < 0) { + note_error(&req->errs, "bad socket"); + return 0; + } + + req->client.socket_file = fdopen(req->client.socket, "wb"); + + client_host = gethostbyaddr((const char *)&addr->sin_addr.s_addr, + sizeof(addr->sin_addr.s_addr), AF_INET); + + if (!client_host) { + note_error(&req->errs, "gethostbyaddr"); + return 0; + } + + req->client.addr_str = inet_ntoa(addr->sin_addr); + + if (!req->client.addr_str) { + note_error(&req->errs, "inet_htoa"); + return 0; + } + + return 1; +} + void run_http_server() { static unsigned char buffer[BUF_SIZE]; unsigned char *arena; ssize_t len; - socklen_t client_len; - int parent, client; - struct sockaddr_in server_addr, client_addr; - struct hostent *client_host; - char *client_addr_str; + int parent; + struct sockaddr_in server_addr; int optval; struct http_req req; @@ -106,51 +338,30 @@ void run_http_server() error("listen"); } - client_len = sizeof(client_addr); while (1) { init_http_req(&req); make_cursor(buffer, buffer + BUF_SIZE, &parser.cur); make_cursor(arena, arena + ARENA_SIZE, &parser.arena); - client = accept(parent, (struct sockaddr *) &client_addr, - &client_len); - if (client < 0) { - error("accept"); - } - - client_host = gethostbyaddr( - (const char *)&client_addr.sin_addr.s_addr, - sizeof(client_addr.sin_addr.s_addr), AF_INET); - - if (!client_host) { - error("gethostbyaddr"); - } - - client_addr_str = inet_ntoa(client_addr.sin_addr); - - if (!client_addr_str) { - error("inet_htoa"); - } - + http_accept_client(&req, parent); while(1) { - len = read(client, (void*)buffer, BUF_SIZE); + len = read(req.client.socket, (void*)buffer, BUF_SIZE); if (len == 0) { break; } - if (parse_http_request(&req, &parser)) { - handle_request(&req, &parser.arena); - } - if (len != BUF_SIZE) { break; } } - printf("\n"); + if (parse_http_request(&req, &parser)) { + handle_request(&req, &parser.arena); + } - close(client); + fclose(req.client.socket_file); + close(req.client.socket); } free(arena);