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:
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);