Blob


1 /*
2 * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
3 *
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.
7 *
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.
15 */
17 #include "config.h"
19 #include <sys/stat.h>
20 #include <sys/socket.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <netdb.h>
25 #include <poll.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <tls.h>
30 #include <unistd.h>
32 #include "iri.h"
34 #ifndef INFTIM
35 #define INFTIM -1
36 #endif
38 #ifndef __OpenBSD__
39 #define pledge(a, b) (0)
40 #endif
42 static int
43 dial(const char *hostname, const char *port)
44 {
45 struct addrinfo hints, *res, *res0;
46 int error, save_errno, s;
47 const char *cause = NULL;
49 if (port == NULL || *port == '\0')
50 port = "1965";
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);
56 if (error)
57 errx(1, "can't resolve %s: %s", hostname, gai_strerror(error));
59 s = -1;
60 for (res = res0; res; res = res->ai_next) {
61 s = socket(res->ai_family, res->ai_socktype,
62 res->ai_protocol);
63 if (s == -1) {
64 cause = "socket";
65 continue;
66 }
68 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
69 cause = "connect";
70 save_errno = errno;
71 close(s);
72 errno = save_errno;
73 s = -1;
74 continue;
75 }
77 if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
78 err(1, "fcntl");
79 break; /* got one */
80 }
81 if (s == -1)
82 err(1, "%s", cause);
83 freeaddrinfo(res0);
84 return (s);
85 }
87 /* returns read bytes, or -1 on error */
88 static ssize_t
89 iomux(struct tls *ctx, int fd, const char *in, size_t inlen, char *out,
90 size_t outlen)
91 {
92 struct pollfd pfd;
93 size_t outwrote = 0;
94 ssize_t ret;
96 memset(&pfd, 0, sizeof(pfd));
97 pfd.fd = fd;
98 pfd.events = POLLIN|POLLOUT;
100 for (;;) {
101 if (poll(&pfd, 1, INFTIM) == -1)
102 err(1, "poll");
103 if (pfd.revents & (POLLERR|POLLNVAL))
104 errx(1, "bad fd %d", pfd.fd);
106 /* attempt to read */
107 if (out != NULL) {
108 switch (ret = tls_read(ctx, out, outlen)) {
109 case TLS_WANT_POLLIN:
110 case TLS_WANT_POLLOUT:
111 break;
112 case -1:
113 return -1;
114 case 0:
115 return outwrote;
116 default:
117 outwrote += ret;
118 out += ret;
119 outlen -= ret;
122 /*
123 * don't write if we're reading; titan works
124 * like this.
125 */
126 if (outwrote != 0)
127 continue;
130 if (inlen == 0 && out == NULL)
131 break;
132 if (inlen == 0)
133 continue;
135 switch (ret = tls_write(ctx, in, inlen)) {
136 case TLS_WANT_POLLIN:
137 case TLS_WANT_POLLOUT:
138 continue;
139 case 0:
140 case -1:
141 return -1;
142 default:
143 in += ret;
144 inlen -= ret;
148 return 0;
151 static FILE *
152 open_input_file(int argc, char **argv)
154 FILE *fp;
155 char buf[BUFSIZ];
156 char sfn[22];
157 size_t r;
158 int fd;
160 if (argc > 1) {
161 if ((fp = fopen(argv[1], "r")) == NULL)
162 err(1, "can't open %s", argv[1]);
163 return fp;
166 strlcpy(sfn, "/tmp/titan.XXXXXXXXXX", sizeof(sfn));
167 if ((fd = mkstemp(sfn)) == -1 ||
168 (fp = fdopen(fd, "w+")) == NULL) {
169 warn("%s", sfn);
170 if (fd != -1) {
171 unlink(sfn);
172 close(fd);
174 errx(1, "can't create temp file");
176 unlink(sfn);
178 for (;;) {
179 r = fread(buf, 1, sizeof(buf), stdin);
180 if (r == 0)
181 break;
182 fwrite(buf, 1, r, fp);
184 if (ferror(fp))
185 err(1, "I/O error");
187 if (fseeko(fp, 0, SEEK_SET) == -1)
188 err(1, "fseeko");
190 return fp;
193 static int
194 parse_response(char *r)
196 int code;
198 if (r[0] < '0' || r[0] > '9' ||
199 r[1] < '0' || r[1] > '9' ||
200 r[2] != ' ')
201 errx(1, "illegal response");
203 code = (r[0] - '0') * 10 + (r[1] - '0');
204 if (code < 10 || code >= 70)
205 errx(1, "invalid response code: %d", code);
206 if (code >= 20 && code < 30)
207 return 0;
208 if (code >= 30 && code < 40) {
209 puts(r + 3);
210 return 0;
212 warnx("server error: %s", r + 3);
213 return 2;
216 static void __dead
217 usage(void)
219 fprintf(stderr,
220 "usage: %s [-C cert] [-K key] [-m mime] [-t token] url [file]\n",
221 getprogname());
222 exit(1);
225 int
226 main(int argc, char **argv)
228 struct tls_config *config;
229 struct tls *ctx;
230 struct stat sb;
231 struct iri iri;
232 FILE *in;
233 const char *cert = NULL, *key = NULL, *mime = NULL, *token = NULL;
234 const char *parse_err;
235 char iribuf[1025];
236 char resbuf[1025];
237 char *req;
238 int sock, ch, ret = 0;
240 if (pledge("stdio rpath tmppath inet dns", NULL) == -1)
241 err(1, "pledge");
243 while ((ch = getopt(argc, argv, "C:K:m:t:")) != -1) {
244 switch (ch) {
245 case 'C':
246 cert = optarg;
247 break;
248 case 'K':
249 key = optarg;
250 break;
251 case 'm':
252 mime = optarg;
253 break;
254 case 't':
255 token = optarg;
256 break;
259 argc -= optind;
260 argv += optind;
262 if (cert == NULL && key != NULL)
263 usage();
264 if (cert != NULL && key == NULL)
265 key = cert;
267 if (argc > 2)
268 usage();
270 in = open_input_file(argc, argv);
272 /* drop rpath tmppath */
273 if (pledge("stdio inet dns", NULL) == -1)
274 err(1, "pledge");
276 if (fstat(fileno(in), &sb) == -1)
277 err(1, "fstat");
279 /* prepare the URL */
280 if (token && mime) {
281 if (asprintf(&req, "%s;size=%lld;token=%s;mime=%s\r\n", argv[0],
282 (long long)sb.st_size, token, mime) == -1)
283 err(1, "asprintf");
284 } else if (token) {
285 if (asprintf(&req, "%s;size=%lld;token=%s\r\n", argv[0],
286 (long long)sb.st_size, token) == -1)
287 err(1, "asprintf");
288 } else if (mime) {
289 if (asprintf(&req, "%s;size=%lld;mime=%s\r\n", argv[0],
290 (long long)sb.st_size, mime) == -1)
291 err(1, "asprintf");
292 } else {
293 if (asprintf(&req, "%s;size=%lld\r\n", argv[0],
294 (long long)sb.st_size) == -1)
295 err(1, "asprintf");
298 if (strlcpy(iribuf, argv[0], sizeof(iribuf)) >= sizeof(iribuf))
299 errx(1, "URL too long");
301 if (!parse_iri(iribuf, &iri, &parse_err))
302 errx(1, "invalid IRI: %s", parse_err);
304 if ((config = tls_config_new()) == NULL)
305 err(1, "tls_config_new");
306 tls_config_insecure_noverifycert(config);
307 tls_config_insecure_noverifyname(config);
309 if (cert && tls_config_set_keypair_file(config, cert, key) == -1)
310 errx(1, "cant load certificate client %s", cert);
312 if ((ctx = tls_client()) == NULL)
313 errx(1, "can't create tls context");
315 if (tls_configure(ctx, config) == -1)
316 errx(1, "tls_configure: %s", tls_error(ctx));
318 sock = dial(iri.host, iri.port);
320 /* drop inet tls */
321 if (pledge("stdio", NULL) == -1)
322 err(1, "pledge");
324 if (tls_connect_socket(ctx, sock, iri.host) == -1)
325 errx(1, "failed to connect to %s:%s: %s", iri.host,
326 *iri.port == '\0' ? "1965" : iri.port, tls_error(ctx));
328 /* send request */
329 if (iomux(ctx, sock, req, strlen(req), NULL, 0) == -1)
330 errx(1, "I/O error: %s", tls_error(ctx));
332 for (;;) {
333 static char buf[BUFSIZ];
334 size_t buflen;
335 ssize_t w;
336 char *m;
338 /* will be zero on EOF */
339 buflen = fread(buf, 1, sizeof(buf), in);
341 w = iomux(ctx, sock, buf, buflen, resbuf, sizeof(resbuf));
342 if (w == -1)
343 errx(1, "I/O error: %s", tls_error(ctx));
344 if (w != 0) {
345 if ((m = memmem(resbuf, w, "\r\n", 2)) == NULL)
346 errx(1, "invalid reply");
347 *m = '\0';
348 ret = parse_response(resbuf);
349 break;
353 /* close connection */
354 for (;;) {
355 struct pollfd pfd;
357 memset(&pfd, 0, sizeof(pfd));
358 pfd.fd = sock;
359 pfd.events = POLLIN|POLLOUT;
361 switch (tls_close(ctx)) {
362 case TLS_WANT_POLLIN:
363 case TLS_WANT_POLLOUT:
364 if (poll(&pfd, 1, INFTIM) == -1)
365 err(1, "poll");
366 break;
367 case -1:
368 warnx("tls_close: %s", tls_error(ctx));
369 /* fallthrough */
370 default:
371 tls_free(ctx);
372 return (ret);