Introduction
Introduction Statistics Contact Development Disclaimer Help
initial repo - gopherproxy-c - Gopher HTTP proxy in C (CGI)
git clone git://git.codemadness.org/gopherproxy-c
Log
Files
Refs
README
LICENSE
---
commit 40a6ccd6cfb99c2849dff4501a54bc7752b63620
Author: Hiltjo Posthuma <[email protected]>
Date: Sun, 12 Aug 2018 18:14:09 +0200
initial repo
Diffstat:
A .gitignore | 2 ++
A LICENSE | 15 +++++++++++++++
A Makefile | 17 +++++++++++++++++
A README | 17 +++++++++++++++++
A gopherproxy.c | 586 ++++++++++++++++++++++++++++++
5 files changed, 637 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+gopherproxy
+*.o
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2018 Hiltjo Posthuma <[email protected]>
+
+Permission to use, copy, modify, and/or 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.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,17 @@
+.POSIX:
+
+BIN = gopherproxy
+OBJ = $(BIN:=.o)
+
+# OpenBSD: use pledge(2).
+#CFLAGS += -DUSE_PLEDGE
+# build static: useful in www chroot.
+#LDFLAGS += -static
+
+all: $(BIN)
+
+$(BIN): $(OBJ)
+ $(CC) $(OBJ) $(LDFLAGS) -o $@
+
+clean:
+ rm -f $(BIN) $(OBJ)
diff --git a/README b/README
@@ -0,0 +1,17 @@
+gopherproxy
+===========
+
+Build dependencies:
+- C compiler.
+- libc
+- POSIX system.
+- make (optional).
+
+
+Features:
+- Works in older browsers such as links, lynx, w3m, dillo, etc.
+- No Javascript or CSS required.
+
+
+Cons:
+- Not all gopher types are supported.
diff --git a/gopherproxy.c b/gopherproxy.c
@@ -0,0 +1,586 @@
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAX_RESPONSETIMEOUT 10 /* timeout in seconds */
+#define MAX_RESPONSESIZ 4000000 /* max download size in bytes */
+
+#ifndef USE_PLEDGE
+#define pledge(a,b) 0
+#endif
+
+struct uri {
+ char proto[16];
+ char host[256];
+ char port[8];
+ char path[1024];
+};
+
+struct visited {
+ int _type;
+ char username[1024];
+ char path[1024];
+ char server[256];
+ char port[8];
+};
+
+int headerset = 0;
+
+void
+die(int code, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!headerset) {
+ switch (code) {
+ case 400:
+ fputs("Status: 400 Bad Request\r\n", stdout);
+ break;
+ case 403:
+ fputs("Status: 403 Permission Denied\r\n", stdout);
+ break;
+ default:
+ fputs("Status: 500 Internal Server Error\r\n", stdout);
+ break;
+ }
+ fputs("Content-Type: text/plain; charset=utf-8\r\n\r\n", stdou…
+ }
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+
+ exit(1);
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0. */
+void
+xmlencode(const char *s)
+{
+ for (; *s; s++) {
+ switch(*s) {
+ case '<': fputs("&lt;", stdout); break;
+ case '>': fputs("&gt;", stdout); break;
+ case '\'': fputs("&#39;", stdout); break;
+ case '&': fputs("&amp;", stdout); break;
+ case '"': fputs("&quot;", stdout); break;
+ default: putchar(*s);
+ }
+ }
+}
+
+int
+dial(const char *host, const char *port)
+{
+ struct addrinfo hints, *res, *res0;
+ int error, save_errno, s;
+ const char *cause = NULL;
+ struct timeval timeout = {
+ .tv_sec = MAX_RESPONSETIMEOUT,
+ .tv_usec = 0,
+ };
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICSERV; /* numeric port only */
+ if ((error = getaddrinfo(host, port, &hints, &res0)))
+ die(500, "%s: %s: %s:%s", __func__, gai_strerror(error), host,…
+ s = -1;
+ for (res = res0; res; res = res->ai_next) {
+ s = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol);
+ if (s == -1) {
+ cause = "socket";
+ continue;
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(ti…
+ die(500, "%s: setsockopt: %s\n", __func__, strerror(er…
+ if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(ti…
+ die(500, "%s: setsockopt: %s\n", __func__, strerror(er…
+
+ if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
+ cause = "connect";
+ save_errno = errno;
+ close(s);
+ errno = save_errno;
+ s = -1;
+ continue;
+ }
+ break;
+ }
+ if (s == -1)
+ die(500, "%s: %s: %s:%s\n", __func__, cause, host, port);
+ freeaddrinfo(res0);
+
+ return s;
+}
+
+int
+isblacklisted(const char *host, const char *port, const char *path)
+{
+ char *p;
+
+ if ((p = strstr(host, ".onion")) && strlen(p) == strlen(".onion"))
+ return 1;
+ return 0;
+}
+
+char *
+typestr(int c)
+{
+ switch (c) {
+ case '0': return " TEXT";
+ case '1': return " DIR";
+ case '7': return "SEARCH";
+ case '9': return " BIN";
+ case 'g': return " GIF";
+ case 'h': return " HTML"; /* non-standard */
+ case 's': return " SND"; /* non-standard */
+ case 'A': return " AUDIO"; /* non-standard */
+ case 'I': return " IMG";
+ default: return " ";
+ }
+}
+
+void
+servefile(const char *server, const char *port, const char *path)
+{
+ char buf[1024];
+ int r, w, fd;
+ size_t totalsiz = 0;
+
+ fd = dial(server, port);
+
+ if (pledge("stdio", NULL) == -1)
+ die(500, "pledge: %s\n", strerror(errno));
+
+ w = dprintf(fd, "%s\r\n", path);
+ if (w == -1)
+ die(500, "dprintf: %s\n", strerror(errno));
+
+ while ((r = read(fd, buf, sizeof(buf))) > 0) {
+ /* too big total response */
+ totalsiz += r;
+ if (totalsiz > MAX_RESPONSESIZ) {
+ dprintf(1, "--- transfer too big, truncated ---\n");
+ break;
+ }
+
+ if ((w = write(1, buf, r)) == -1)
+ die(500, "write: %s\n", strerror(errno));
+ }
+ if (r == -1)
+ die(500, "read: %s\n", strerror(errno));
+ close(fd);
+}
+
+void
+servedir(const char *server, const char *port, const char *path, const char *p…
+{
+ struct visited v;
+ FILE *fp;
+ char line[1024], uri[1024];
+ size_t totalsiz, linenr;
+ ssize_t n;
+ int fd, r, i, len;
+
+ fd = dial(server, port);
+
+ if (pledge("stdio", NULL) == -1)
+ die(500, "pledge: %s\n", strerror(errno));
+
+ if (param[0])
+ r = dprintf(fd, "%s\t%s\r\n", path, param);
+ else
+ r = dprintf(fd, "%s\r\n", path);
+ if (r == -1)
+ die(500, "write: %s\n", strerror(errno));
+
+ if (!(fp = fdopen(fd, "rb+")))
+ die(500, "fdopen: %s\n", strerror(errno));
+
+ totalsiz = 0;
+ for (linenr = 1; fgets(line, sizeof(line), fp); linenr++) {
+ n = strcspn(line, "\n");
+ if (line[n] != '\n')
+ die(500, "%s:%s %s:%d: line too long\n",
+ server, port, path, linenr);
+ if (n && line[n] == '\n')
+ line[n] = '\0';
+ if (n && line[n - 1] == '\r')
+ line[--n] = '\0';
+ if (n == 1 && line[0] == '.')
+ break;
+
+ /* too big total response */
+ totalsiz += n;
+ if (totalsiz > MAX_RESPONSESIZ) {
+ dprintf(1, "--- transfer too big, truncated ---\n");
+ break;
+ }
+
+ memset(&v, 0, sizeof(v));
+
+ v._type = line[0];
+
+ /* "username" */
+ i = 1;
+ len = strcspn(line + i, "\t");
+ if (len + 1 < sizeof(v.username)) {
+ memcpy(v.username, line + i, len);
+ v.username[len] = '\0';
+ } else {
+ die(500, "%s:%s %s:%d: username field too long\n",
+ server, port, path, linenr);
+ }
+ if (line[i + len] == '\t')
+ i += len + 1;
+ else
+ die(500, "%s:%s %s:%d: invalid line / field count\n",
+ server, port, path, linenr);
+
+ /* selector / path */
+ len = strcspn(line + i, "\t");
+ if (len + 1 < sizeof(v.path)) {
+ memcpy(v.path, line + i, len);
+ v.path[len] = '\0';
+ } else {
+ die(500, "%s:%s %s:%d: path field too long\n",
+ server, port, path, linenr);
+ }
+ if (line[i + len] == '\t')
+ i += len + 1;
+ else
+ die(500, "%s:%s %s:%d: invalid line / field count\n",
+ server, port, path, linenr);
+
+ /* server */
+ len = strcspn(line + i, "\t");
+ if (len + 1 < sizeof(v.server)) {
+ memcpy(v.server, line + i, len);
+ v.server[len] = '\0';
+ } else {
+ die(500, "%s:%s %s:%d: server field too long\n",
+ server, port, path, linenr);
+ }
+ if (line[i + len] == '\t')
+ i += len + 1;
+ else
+ die(500, "%s:%s %s:%d: invalid line / field count\n",
+ server, port, path, linenr);
+
+ /* port */
+ len = strcspn(line + i, "\t");
+ if (len + 1 < sizeof(v.port)) {
+ memcpy(v.port, line + i, len);
+ v.port[len] = '\0';
+ } else {
+ die(500, "%s:%s %s:%d: port field too long\n",
+ server, port, path, linenr);
+ }
+
+ uri[0] = '\0';
+ switch (line[0]) {
+ case '7':
+ snprintf(uri, sizeof(uri), "gopher://%s:%s/%c%s",
+ v.server, v.port, v._type, v.path);
+ break;
+ case 'h':
+ if (!strncmp(v.path, "URL:", sizeof("URL:") - 1))
+ snprintf(uri, sizeof(uri), "%s", v.path + size…
+ else
+ snprintf(uri, sizeof(uri), "gopher://%s:%s/%c%…
+ v.server, v.port, v._type, v.path);
+ break;
+ case 'i': /* info */
+ case '3': /* error */
+ break;
+ default:
+ snprintf(uri, sizeof(uri), "?q=gopher://%s:%s/%c%s",
+ v.server, v.port, v._type, v.path);
+ }
+
+ /* search */
+ if (v._type == '7') {
+ fputs("</pre><form method=\"get\" action=\"\"><pre>", …
+ fputs(typestr(v._type), stdout);
+ fputs(" <input type=\"hidden\" name=\"q\" value=\"", s…
+ xmlencode(uri);
+ fputs("\" /><input type=\"search\" placeholder=\"", st…
+ xmlencode(v.username);
+ fputs(
+ "\" name=\"p\" value=\"\" size=\"72\" />"
+ "<input type=\"submit\" value=\"Search\" /></p…
+ } else {
+ fputs(typestr(v._type), stdout);
+ if (uri[0]) {
+ fputs(" <a href=\"", stdout);
+ xmlencode(uri);
+ fputs("\">", stdout);
+ xmlencode(v.username);
+ fputs("</a>", stdout);
+ } else {
+ fputs(" ", stdout);
+ xmlencode(v.username);
+ }
+ }
+ putchar('\n');
+ }
+ if (ferror(fp))
+ die(500, "fgets: %s\n", strerror(errno));
+ fclose(fp);
+}
+
+int
+hexdigit(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ return 0;
+}
+
+/* decode until NUL separator or end of "key". */
+int
+decodeparam(char *buf, size_t bufsiz, const char *s)
+{
+ size_t i;
+
+ if (!bufsiz)
+ return -1;
+
+ for (i = 0; *s && *s != '&'; s++) {
+ if (i + 3 >= bufsiz)
+ return -1;
+ switch (*s) {
+ case '%':
+ if (!isxdigit(*(s+1)) || !isxdigit(*(s+2)))
+ return -1;
+ buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2));
+ s += 2;
+ break;
+ case '+':
+ buf[i++] = ' ';
+ break;
+ default:
+ buf[i++] = *s;
+ break;
+ }
+ }
+ buf[i] = '\0';
+
+ return i;
+}
+
+char *
+getparam(const char *query, const char *s)
+{
+ const char *p;
+ size_t len;
+
+ len = strlen(s);
+ for (p = query; (p = strstr(p, s)); p += len) {
+ if (p[len] == '=' && (p == query || p[-1] == '&'))
+ return (char *)p + len + 1;
+ }
+
+ return NULL;
+}
+
+int
+checkparam(const char *s)
+{
+ for (; *s; s++)
+ if (iscntrl(*s))
+ return 0;
+ return 1;
+}
+
+int
+parseuri(const char *str, struct uri *u)
+{
+ const char *s, *e;
+
+ memset(u, 0, sizeof(struct uri));
+
+ /* protocol part */
+ for (e = s = str; *e && (isalpha((int)*e) || isdigit((int)*e) ||
+ *e == '+' || *e == '-' || *e == '.'); e++)
+ ;
+ if (strncmp(e, "://", sizeof("://") - 1))
+ return 0;
+ if (e - s + 1 >= sizeof(u->proto))
+ return 0;
+ memcpy(u->proto, s, e - s);
+ u->proto[e - s] = '\0';
+
+ e += sizeof("://") - 1;
+ s = e;
+
+ e = &e[strcspn(s, ":/")];
+ if (e - s + 1 >= sizeof(u->host))
+ return 0;
+ memcpy(u->host, s, e - s);
+ u->host[e - s] = '\0';
+
+ if (*e == ':') {
+ s = ++e;
+
+ e = &s[strcspn(s, "/")];
+
+ if (e - s + 1 >= sizeof(u->port))
+ return 0;
+ memcpy(u->port, s, e - s);
+ u->port[e - s] = '\0';
+ }
+ if (*e && *e != '/')
+ return 0; /* invalid path */
+
+ s = e;
+ e = s + strlen(s);
+
+ if (e - s + 1 >= sizeof(u->path))
+ return 0;
+ memcpy(u->path, s, e - s);
+ u->path[e - s] = '\0';
+
+ return 1;
+}
+
+int
+main(void)
+{
+ struct uri u;
+ const char *p, *qs, *path;
+ char query[1024] = "", param[1024] = "", uri[1024] = "";
+ int _type = '1';
+
+ if (pledge("stdio inet dns", NULL) == -1)
+ die(500, "pledge: %s\n", strerror(errno));
+
+ if (!(qs = getenv("QUERY_STRING")))
+ qs = "";
+ if ((p = getparam(qs, "q"))) {
+ if (decodeparam(query, sizeof(query), p) == -1 ||
+ !checkparam(query))
+ die(400, "Invalid parameter: q\n");
+ }
+ if ((p = getparam(qs, "p"))) {
+ if (decodeparam(param, sizeof(param), p) == -1 ||
+ !checkparam(param))
+ die(400, "Invalid parameter: p\n");
+ }
+
+ path = "/";
+ if (query[0]) {
+ if (strncmp(query, "gopher://", sizeof("gopher://") - 1))
+ snprintf(uri, sizeof(uri), "gopher://%s", query);
+ else
+ snprintf(uri, sizeof(uri), "%s", query);
+
+ if (!parseuri(uri, &u))
+ die(400, "Invalid uri: %s\n", uri);
+ if (u.host[0] == '\0')
+ die(400, "Invalid hostname\n");
+
+ if (u.path[0] == '\0')
+ memcpy(u.path, "/", 2);
+ if (u.port[0] == '\0')
+ memcpy(u.port, "70", 3);
+
+ path = u.path;
+ if (path[0] == '/') {
+ if (path[1]) {
+ _type = path[1];
+ path += 2;
+ }
+ } else {
+ path = "/";
+ }
+
+ if (isblacklisted(u.host, u.port, path))
+ die(403, "%s:%s %s: blacklisted\n", u.host, u.port, pa…
+
+ headerset = 1;
+ switch (_type) {
+ case '0':
+ printf("Content-Type: text/plain; charset=utf-8\r\n\r\…
+ fflush(stdout);
+ servefile(u.host, u.port, path);
+ return 0;
+ case '1':
+ case '7':
+ break; /* handled below */
+ case '9':
+ printf("Content-Type: application/octet-stream\r\n");
+ if ((p = strrchr(path, '/')))
+ printf("Content-Disposition: attachment; filen…
+ printf("\r\n");
+ fflush(stdout);
+ servefile(u.host, u.port, path);
+ return 0;
+ default:
+ write(1, "\r\n", 2);
+ servefile(u.host, u.port, path);
+ return 0;
+ }
+ }
+
+ fputs("Content-Type: text/html; charset=utf-8\r\n\r\n", stdout);
+ headerset = 1;
+
+ fputs(
+ "<!DOCTYPE html>\n"
+ "<html dir=\"ltr\">\n"
+ "<head>\n"
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charse…
+ "<title>", stdout);
+ xmlencode(query);
+ if (query[0])
+ fputs(" - ", stdout);
+ fputs(
+ "Gopher HTTP proxy</title>\n"
+ "<style type=\"text/css\">a { text-decoration: none; } "
+ "a:hover { text-decoration: underline; }</style>\n"
+ "<meta name=\"robots\" content=\"noindex, nofollow\" />\n"
+ "<meta name=\"robots\" content=\"none\" />\n"
+ "<meta content=\"width=device-width\" name=\"viewport\" />\n"
+ "</head>\n"
+ "<body>\n"
+ "<form method=\"get\" action=\"\"><pre>"
+ " URI: <input type=\"search\" name=\"q\" value=\"", stdout);
+ xmlencode(uri);
+ fputs(
+ "\" placeholder=\"URI...\" size=\"72\" autofocus=\"autofocus\"…
+ "<input type=\"submit\" value=\"Go for it!\" /></pre>"
+ "</form><pre>\n", stdout);
+
+ if (query[0]) {
+ if (_type != '7')
+ param[0] = '\0';
+ servedir(u.host, u.port, path, param);
+ }
+
+ fputs("</pre>\n</body>\n</html>\n", stdout);
+
+ return 0;
+}
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.