~jb55/chibipub

a9cb8c1f980c97ba0d1a3021b6772d57dbce0315 — William Casarin a month ago dc80174
initial requests from masto working
6 files changed, 280 insertions(+), 56 deletions(-)

M Makefile
M default.nix
M src/http.h
M src/inbox.c
M src/json.c
M src/wolfsocks.c
M Makefile => Makefile +5 -1
@@ 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' > $@


M default.nix => default.nix +3 -3
@@ 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 ];
}

M src/http.h => src/http.h +10 -0
@@ 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;
};

M src/inbox.c => src/inbox.c +2 -2
@@ 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;
}

M src/json.c => src/json.c +6 -7
@@ 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;
		}

M src/wolfsocks.c => src/wolfsocks.c +254 -43
@@ 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);