#include #include #include #include #include #include #include #include #include #ifdef LIBBSD #include #else #include #endif #include #include #ifndef BUFFER_SIZE #define BUFFER_SIZE 4096 #endif static ssize_t stripcntrl(char *, size_t, const char *, size_t); int main(int argc, char *argv[]) { char b[BUFFER_SIZE]; struct gemini gemini = GEMINI_INITIALIZER; const char *query = NULL; char *p; ssize_t r; size_t i; int prompt = 0; int rawout; int c; gemini_envinit(&gemini); if ((p = getenv("GEMINI_TOFU_WRITE")) != NULL && *p != '\0') gemini.flags |= GEMINI_TOFU_WRITE; rawout = !isatty(STDOUT_FILENO); #ifdef __OpenBSD__ if (pledge("stdio cpath rpath wpath flock inet dns tty", NULL) != 0) err(EX_NOPERM, "Failed pledge(2)"); #endif while ((c = getopt(argc, argv, "RSc:h:ik:p:r:st:w")) != -1) switch (c) { case 'R': rawout = 1; break; case 'S': rawout = 0; break; case 'c': gemini.certfile = optarg; break; case 'h': gemini.proxy = optarg; break; case 'i': prompt = 1; break; case 'k': gemini.keyfile = optarg; break; case 'p': for (i = 0, gemini.port = 0; optarg[i] >= '0' && optarg[i] <= '9'; i++) gemini.port = gemini.port * 10 + optarg[i] - '0'; break; case 'r': for (i = 0, gemini.maxredirects = 0; gemini.maxredirects <= (INT_MAX - 9) / 10 && optarg[i] >= '0' && optarg[i] <= '9'; gemini.maxredirects = gemini.maxredirects * 10 + (optarg[i] - '0'), i++) /* do nothing */; if (i == 0 || optarg[i] != '\0') warnx("Invalid number: %s", optarg); break; case 's': prompt = 2; break; case 't': gemini.tofufile = optarg; break; case 'w': gemini.flags |= GEMINI_TOFU_WRITE; break; default: goto usage; } argc -= optind; argv += optind; #ifdef __OpenBSD__ if (!(gemini.flags & GEMINI_TOFU_WRITE) && pledge("stdio rpath flock inet dns tty", NULL) != 0) err(EX_NOPERM, "Failed pledge(2)"); #endif for (i = 0; i < argc; i += (query == NULL)) { if (gemini_connect_query(&gemini, argv[i], query) != 0) { warn("Failed to connect to %s", argv[i]); goto err; } #ifdef __OpenBSD__ if (i == 0 && pledge("stdio rpath flock inet dns tty", NULL) != 0) err(EX_NOPERM, "Failed pledge(2)"); #endif while ((r = gemini_read(&gemini, b, sizeof(b))) > 0) { if (!rawout) r = stripcntrl(b, sizeof(b), b, r); (void)fwrite(b, 1, r, stdout); } if (r < 0) { warn("Gemini read error"); goto err; } query = NULL; switch (gemini.status / 10) { case 1: if (prompt == 0) { warnx("User-input is not enabled"); break; } if (readpassphrase(gemini.meta, b, sizeof(b), prompt > 1 ? RPP_ECHO_OFF : RPP_ECHO_ON) == NULL) { warn("Could not get user-input"); goto err; } query = prompt > 0 ? b : NULL; break; case 2: break; case 4: break; case 5: break; case 6: warnx("Gemini certificate error %02hd: %s", gemini.status, gemini.meta); break; default: warnx("Gemini error %02hd: %s", gemini.status, gemini.meta); break; } gemini_reset(&gemini); } gemini_fini(&gemini); return (0); err: gemini_fini(&gemini); return (EX_SOFTWARE); usage: (void)fprintf(stderr, "usage: gemini-cat [-RSisw] " "[-c cert-file] [-h proxy-host] [-k key-file]\n" " [-p port] [-r max-redirects] " "[-t tofu-file] URL...\n"); return (EX_USAGE); } ssize_t stripcntrl(char *dst, size_t dl, const char *src, size_t sl) { size_t i; size_t j; for (i = 0, j = 0; i < sl && j < dl; i++) if (!iscntrl(src[i]) || isspace(src[i])) dst[j++] = src[i]; return (j); }