2 * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include <sys/socket.h>
39 #define pledge(a, b) (0)
43 dial(const char *hostname, const char *port)
45 struct addrinfo hints, *res, *res0;
46 int error, save_errno, s;
47 const char *cause = NULL;
49 if (port == NULL || *port == '\0')
52 memset(&hints, 0, sizeof(hints));
53 hints.ai_family = AF_UNSPEC;
54 hints.ai_socktype = SOCK_STREAM;
55 error = getaddrinfo(hostname, port, &hints, &res0);
57 errx(1, "can't resolve %s: %s", hostname, gai_strerror(error));
60 for (res = res0; res; res = res->ai_next) {
61 s = socket(res->ai_family, res->ai_socktype,
68 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
77 if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
87 /* returns read bytes, or -1 on error */
89 iomux(struct tls *ctx, int fd, const char *in, size_t inlen, char *out,
96 memset(&pfd, 0, sizeof(pfd));
98 pfd.events = POLLIN|POLLOUT;
101 if (poll(&pfd, 1, INFTIM) == -1)
103 if (pfd.revents & (POLLERR|POLLNVAL))
104 errx(1, "bad fd %d", pfd.fd);
106 /* attempt to read */
108 switch (ret = tls_read(ctx, out, outlen)) {
109 case TLS_WANT_POLLIN:
110 case TLS_WANT_POLLOUT:
125 * don't write if we're reading; titan works
132 if (inlen == 0 && out == NULL)
137 switch (ret = tls_write(ctx, in, inlen)) {
138 case TLS_WANT_POLLIN:
139 case TLS_WANT_POLLOUT:
154 open_input_file(int argc, char **argv)
163 if ((fp = fopen(argv[1], "r")) == NULL)
164 err(1, "can't open %s", argv[1]);
168 strlcpy(sfn, "/tmp/titan.XXXXXXXXXX", sizeof(sfn));
169 if ((fd = mkstemp(sfn)) == -1 ||
170 (fp = fdopen(fd, "w+")) == NULL) {
176 errx(1, "can't create temp file");
181 r = fread(buf, 1, sizeof(buf), stdin);
184 fwrite(buf, 1, r, fp);
189 if (fseeko(fp, 0, SEEK_SET) == -1)
196 parse_response(char *r)
200 if (r[0] < '0' || r[0] > '9' ||
201 r[1] < '0' || r[1] > '9' ||
203 errx(1, "illegal response");
205 code = (r[0] - '0') * 10 + (r[1] - '0');
206 if (code < 10 || code >= 70)
207 errx(1, "invalid response code: %d", code);
208 if (code >= 20 && code < 30)
210 if (code >= 30 && code < 40) {
214 warnx("server error: %s", r + 3);
222 "usage: %s [-C cert] [-K key] [-m mime] [-t token] url [file]\n",
228 main(int argc, char **argv)
230 struct tls_config *config;
235 const char *cert = NULL, *key = NULL, *mime = NULL, *token = NULL;
241 int sock, ch, ret = 0;
243 if (pledge("stdio rpath tmppath inet dns", NULL) == -1)
246 while ((ch = getopt(argc, argv, "C:K:m:t:")) != -1) {
267 if (cert == NULL && key != NULL)
269 if (cert != NULL && key == NULL)
272 if (argc != 1 && argc != 2)
275 in = open_input_file(argc, argv);
277 /* drop rpath tmppath */
278 if (pledge("stdio inet dns", NULL) == -1)
281 if (fstat(fileno(in), &sb) == -1)
284 /* prepare the IRI */
285 if (strlcpy(iribuf, argv[0], sizeof(iribuf)) >= sizeof(iribuf))
286 errx(1, "IRI too long");
288 if (!parse_iri(iribuf, &iri, &errstr))
289 errx(1, "invalid IRI: %s", errstr);
291 if (strcmp(iri.schema, "titan") != 0)
292 errx(1, "not a titan:// IRI");
295 if (asprintf(&path, "%s;size=%lld;token=%s;mime=%s", iri.path,
296 (long long)sb.st_size, token, mime) == -1)
299 if (asprintf(&path, "%s;size=%lld;token=%s", iri.path,
300 (long long)sb.st_size, token) == -1)
303 if (asprintf(&path, "%s;size=%lld;mime=%s", iri.path,
304 (long long)sb.st_size, mime) == -1)
307 if (asprintf(&path, "%s;size=%lld", iri.path,
308 (long long)sb.st_size) == -1)
313 if (!serialize_iri(&iri, reqbuf, sizeof(reqbuf)) ||
314 strlcat(reqbuf, "\r\n", sizeof(reqbuf)) >= sizeof(reqbuf))
315 errx(1, "IRI too long");
317 if ((config = tls_config_new()) == NULL)
318 err(1, "tls_config_new");
319 tls_config_insecure_noverifycert(config);
320 tls_config_insecure_noverifyname(config);
322 if (cert && tls_config_set_keypair_file(config, cert, key) == -1)
323 errx(1, "cant load certificate client %s", cert);
325 if ((ctx = tls_client()) == NULL)
326 errx(1, "can't create tls context");
328 if (tls_configure(ctx, config) == -1)
329 errx(1, "tls_configure: %s", tls_error(ctx));
331 sock = dial(iri.host, iri.port);
334 if (pledge("stdio", NULL) == -1)
337 if (tls_connect_socket(ctx, sock, iri.host) == -1)
338 errx(1, "failed to connect to %s:%s: %s", iri.host,
339 *iri.port == '\0' ? "1965" : iri.port, tls_error(ctx));
342 if (iomux(ctx, sock, reqbuf, strlen(reqbuf), NULL, 0) == -1)
343 errx(1, "I/O error: %s", tls_error(ctx));
346 static char buf[BUFSIZ];
351 /* will be zero on EOF */
352 buflen = fread(buf, 1, sizeof(buf), in);
354 w = iomux(ctx, sock, buf, buflen, resbuf, sizeof(resbuf));
356 errstr = tls_error(ctx);
358 errstr = "unexpected EOF";
359 errx(1, "I/O error: %s", errstr);
362 if ((m = memmem(resbuf, w, "\r\n", 2)) == NULL)
363 errx(1, "invalid reply");
365 ret = parse_response(resbuf);
370 /* close connection */
374 memset(&pfd, 0, sizeof(pfd));
376 pfd.events = POLLIN|POLLOUT;
378 switch (tls_close(ctx)) {
379 case TLS_WANT_POLLIN:
380 case TLS_WANT_POLLOUT:
381 if (poll(&pfd, 1, INFTIM) == -1)
385 warnx("tls_close: %s", tls_error(ctx));