zygo

ncurses gopher client
Log | Files | Refs

commit 9d52b46d0cad591dd547fcc2f4a17372518a97f0
Author: hhvn <dev@hhvn.uk>
Date:   Sun, 16 Jan 2022 01:29:59 +0000

Init (gets raw text for one URI)

Diffstat:
AMakefile | 34++++++++++++++++++++++++++++++++++
Aconfig.mk | 12++++++++++++
Adebug.c | 36++++++++++++++++++++++++++++++++++++
Aplain.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Atls.c | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Azygo.c | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Azygo.h | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 602 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,34 @@ +# zygo/Makefile +# +# Copyright (c) 2022 hhvn <dev@hhvn.uk> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +BIN = zygo +SRC += zygo.c +OBJ = $(SRC:.c=.o) + +include config.mk + +$(BIN): $(OBJ) + $(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJ) + +$(OBJ): Makefile zygo.h + +.c.o: + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + -rm -f $(OBJ) $(BIN) + +.PHONY: clean diff --git a/config.mk b/config.mk @@ -0,0 +1,12 @@ +# Public domain + +# debug +# CFLAGS += -g3 -O0 -DDEBUG +# SRC += debug.c + +# tls +LDFLAGS += -ltls +SRC += tls.c + +# plain +# SRC += plain.c diff --git a/debug.c b/debug.c @@ -0,0 +1,36 @@ +/* + * zygo/debug.c + * + * Copyright (c) 2022 hhvn <dev@hhvn.uk> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <stdio.h> +#include "zygo.h" + +#define debug_start() printf("--- %s: start ---\n", __func__) +#define debug_end() printf("--- %s: end ---\n", __func__) + +void * +elem_put(Elem *e) { + debug_start(); + fprintf(stderr, "TLS: %d\n", e->tls); + fprintf(stderr, "Type: %c\n", e->type); + fprintf(stderr, "Desc: %s\n", e->desc); + fprintf(stderr, "Selector: %s\n", e->selector); + fprintf(stderr, "Server: %s\n", e->server); + fprintf(stderr, "Port: %s\n", e->port); + debug_end(); +} diff --git a/plain.c b/plain.c @@ -0,0 +1,47 @@ +#include <unistd.h> +#include <netdb.h> +#include "zygo.h" + +static int fd; + +#define net_free() \ + if (ai) freeaddrinfo(ai) + +int +net_connect(Elem *e) { + struct addrinfo *ai = NULL; + int ret; + + if ((ret = getaddrinfo(e->server, e->port, NULL, &ai)) != 0 || ai == NULL) { + error("could not lookup %s:%s", e->server, e->port); + goto fail; + } + + if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1 || + connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { + error("could not connect to %s:%s", e->server, e->port); + goto fail; + } + + net_free(); + return 0; + +fail: + net_free(); + return -1; +} + +int +net_read(void *buf, size_t count) { + return read(fd, buf, count); +} + +int +net_write(void *buf, size_t count) { + return write(fd, buf, count); +} + +int +net_close(void) { + return close(fd); +} diff --git a/tls.c b/tls.c @@ -0,0 +1,121 @@ +#include <unistd.h> +#include <netdb.h> +#include <tls.h> +#include "zygo.h" + +static struct tls *ctx = NULL; +static struct tls_config *conf = NULL; +static int fd; +static int tls; + +#define net_free() \ + if (ai) freeaddrinfo(ai); \ + if (ctx) tls_free(ctx); \ + if (conf) tls_config_free(conf) + +int +net_connect(Elem *e) { + struct addrinfo *ai = NULL; + int ret; + + tls = e->tls; + + if (tls) { + if (conf) + tls_config_free(conf); + + if ((conf = tls_config_new()) == NULL) { + error("tls_config_new(): %s", tls_config_error(conf)); + goto fail; + } + + if (!config[CONF_TLS_VERIFY]) { + tls_config_insecure_noverifycert(conf); + tls_config_insecure_noverifyname(conf); + } + + if ((ctx = tls_client()) == NULL) { + error("tls_client(): %s", tls_error(ctx)); + goto fail; + } + + if (tls_configure(ctx, conf) == -1) { + error("tls_configure(): %s", tls_error(ctx)); + goto fail; + } + } + + if ((ret = getaddrinfo(e->server, e->port, NULL, &ai)) != 0 || ai == NULL) { + error("could not lookup %s:%s", e->server, e->port); + goto fail; + } + + if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1 || + connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { + error("could not connect to %s:%s", e->server, e->port); + goto fail; + } + + if (tls) { + if (tls_connect_socket(ctx, fd, e->server) == -1) { + error("could not tls-ify connection to %s:%s", e->server, e->port); + goto fail; + } + } + + freeaddrinfo(ai); + return 0; + +fail: + net_free(); + return -1; +} + +int +net_read(void *buf, size_t count) { + int ret; + + if (tls) { + do { + ret = tls_read(ctx, buf, count); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + if (ret == -1) + error("tls_read(): %s", tls_error(ctx)); + } else { + ret = read(fd, buf, count); + } + + return ret; +} + +int +net_write(void *buf, size_t count) { + int ret; + + if (tls) { + do { + ret = tls_write(ctx, buf, count); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + if (ret == -1) + error("tls_write(): %s", tls_error(ctx)); + } else { + ret = write(fd, buf, count); + } + + return ret; +} + +int +net_close(void) { + int ret; + + if (tls) { + do { + ret = tls_close(ctx); + } while (ret == TLS_WANT_POLLIN || TLS_WANT_POLLOUT); + tls_free(ctx); + tls_config_free(conf); + } + + return close(fd); +} diff --git a/zygo.c b/zygo.c @@ -0,0 +1,282 @@ +/* + * zygo/zygo.c + * + * Copyright (c) 2022 hhvn <dev@hhvn.uk> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +#include <stdio.h> +#include "zygo.h" + +List *history = NULL; +List *page = NULL; +Elem *current = NULL; + +int config[] = { + [CONF_TLS_VERIFY] = 0, +}; + +void +error(char *format, ...) { + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +void * +emalloc(size_t size) { + void *mem; + + if ((mem = malloc(size)) == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + + return mem; +} + +void * +erealloc(void *ptr, size_t size) { + void *mem; + + if ((mem = realloc(ptr, size)) == NULL) { + perror("realloc()"); + exit(EXIT_FAILURE); + } + + return mem; +} + +char * +estrdup(const char *str) { + char *ret; + + if ((ret = strdup(str)) == NULL) { + perror("strdup()"); + exit(EXIT_FAILURE); + } + + return ret; +} + +void +estrappend(char **s1, const char *s2) { + size_t len; + + len = strlen(*s1) + strlen(s2) + 1; + *s1 = erealloc(*s1, len); + snprintf(*s1 + strlen(*s1), len - strlen(s2), "%s", s2); +} + +void +elem_free(Elem *e) { + if (e) { + free(e->desc); + free(e->selector); + free(e->server); + free(e->port); + free(e); + } +} + +Elem * +elem_create(char type, char *desc, char *selector, char *server, char *port) { + Elem *ret; + +#define DUP(str) str ? NULL : estrdup(str) + ret = emalloc(sizeof(Elem)); + ret->type = type; + ret->desc = DUP(desc); + ret->selector = DUP(selector); + ret->server = DUP(server); + ret->port = DUP(port); +#undef DUP + + return ret; +} + +Elem * +elem_dup(Elem *e) { + if (e) + return elem_create(e->type, e->desc, e->selector, e->server, e->port); + else + return NULL; +} + +char * +elemtouri(Elem *e) { + static char *ret = NULL; + char type[2] = {0, 0}; + + free(ret); + ret = NULL; + + switch (e->type) { + case 'T': + case '8': + ret = estrdup("teln://"); + break; + case 'h': + if (strncmp(e->selector, "URL:", strlen("URL:")) == 0) + return (ret = estrdup(e->selector + strlen("URL:"))); + else if (strncmp(e->selector, "/URL:", strlen("/URL:")) == 0) + return (ret = estrdup(e->selector + strlen("/URL:"))); + /* fallthrough */ + default: + ret = estrdup(e->tls ? "gophers://" : "gopher://"); + break; + } + + assert(e->server); + assert(e->port); + + estrappend(&ret, e->server); + if (strcmp(e->port, "70") != 0) { + estrappend(&ret, ":"); + estrappend(&ret, e->port); + } + + type[0] = e->type; + estrappend(&ret, "/"); + estrappend(&ret, type); + + if (e->selector && *e->selector && strcmp(e->selector, "/") != 0) + estrappend(&ret, e->selector); + + return ret; +} + +Elem * +uritoelem(char *uri) { + Elem *ret; + char *dup = strdup(uri); + char *tmp = dup; + char *p; + enum {SEGSERVER, SEGPORT, SEGTYPE, SEGSELECTOR}; + int seg; + + ret = emalloc(sizeof(Elem)); + ret->tls = ret->type = 0; + ret->desc = ret->selector = ret->server = ret->port; + + if (strncmp(tmp, "gopher://", strlen("gopher://")) == 0) { + tmp += strlen("gopher://"); + } else if (strncmp(tmp, "gophers://", strlen("gophers://")) == 0) { + ret->tls = 1; + tmp += strlen("gophers://"); + } else if (strstr(tmp, "://")) { + error("non-gopher protocol entered"); + free(ret); + ret = NULL; + goto end; + } + + for (p = tmp, seg = SEGSERVER; *p; p++) { + if (seg == SEGSELECTOR || *p == '\t') { + ret->selector = strdup(p); + switch (seg) { + case SEGSERVER: + *p = '\0'; + ret->server = strdup(tmp); + break; + case SEGPORT: + *p = '\0'; + ret->port = strdup(tmp); + break; + } + break; + } else if (seg == SEGSERVER && *p == ':') { + *p = '\0'; + ret->server = strdup(tmp); + tmp = p + 1; + seg = SEGPORT; + } else if (seg == SEGSERVER && *p == '/') { + *p = '\0'; + ret->server = strdup(tmp); + tmp = p + 1; + seg = SEGTYPE; + } else if (seg == SEGPORT && *p == '/') { + *p = '\0'; + ret->port = strdup(tmp); + tmp = p + 1; + seg = SEGTYPE; + } else if (seg == SEGTYPE) { + ret->type = *p; + tmp = p + 1; + break; + } + } + + ret->type = ret->type ? ret->type : '1'; + ret->server = ret->server ? ret->server : strdup(tmp); + ret->port = ret->port ? ret->port : strdup("70"); + ret->selector = ret->selector ? ret->selector : strdup(tmp); + +#ifdef DEBUG + elem_put(ret); +#endif /* DEBUG */ + +end: + free(dup); + return ret; +} + +int +readline(char *buf, size_t count) { + size_t i = 0; + char c = 0; + + do { + if (net_read(&c, sizeof(char)) != sizeof(char)) + return 0; + if (c != '\r') + buf[i++] = c; + } while (c != '\n' && i < count); + + buf[i - 1] = 0; + return 1; +} + +int +go(Elem *e) { + char line[BUFLEN]; + + if (e->type != '1' && e->type != '7' && e->type != '+') { + /* TODO: call plumber */ + return -1; + } + + if (net_connect(e) == -1) + return -1; + + net_write(e->selector, strlen(e->selector)); + net_write("\r\n", 2); + + while (readline(line, sizeof(line))) { + fprintf(stderr, "%s\n", line); + } +} + +int +main(void) { + Elem *s = uritoelem("gophers://hhvn.uk/1/"); + go(s); +} diff --git a/zygo.h b/zygo.h @@ -0,0 +1,70 @@ +/* + * zygo/zygo.h + * + * Copyright (c) 2022 hhvn <dev@hhvn.uk> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#define BUFLEN 2048 + +typedef struct Elem Elem; +struct Elem { + int tls; + char type; + char *desc; + char *selector; + char *server; + char *port; +}; + +typedef struct List List; +struct List { + struct Elem **elems; + size_t len; +}; + +enum { + CONF_TLS_VERIFY = 'k', +}; + +extern List *history; +extern List *page; +extern Elem *current; +extern int config[]; + +void error(char *format, ...); +void *emalloc(size_t size); +void *erealloc(void *ptr, size_t size); +char *estrdup(const char *str); +void estrappend(char **s1, const char *s2); + +void elem_free(Elem *e); +Elem *elem_create(char type, char *desc, char *selector, char *server, char *port); +Elem *elem_dup(Elem *e); +Elem *uritoelem(char *uri); +char *elemtouri(Elem *e); +int readline(char *buf, size_t count); +int go(Elem *e); + +/* only works with one fd/ctx at + * a time, reset at net_connect */ +int net_connect(Elem *e); +int net_read(void *buf, size_t count); +int net_write(void *buf, size_t count); +int net_close(void); + +#ifdef DEBUG +void *elem_put(Elem *e); /* debug */ +#endif /* DEBUG */