Blob


1 /*
2 * Copyright (c) 2021 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 "compat.h"
19 #include <sys/types.h>
20 #include <sys/socket.h>
22 #include <netdb.h>
24 #include <assert.h>
25 #include <endian.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <inttypes.h>
29 #include <signal.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <syslog.h>
34 #include <tls.h>
35 #include <unistd.h>
37 #include "9pclib.h"
38 #include "kamid.h"
39 #include "log.h"
40 #include "utils.h"
42 #define DEBUG_PACKETS 0
44 #define PROMPT "=% "
46 /* flags */
47 int verbose;
48 int tls;
49 const char *keypath;
50 const char *crtpath;
51 const char *host;
52 const char *port;
54 /* state */
55 struct tls_config *tlsconf;
56 struct tls *ctx;
57 struct bufferevent *bev, *inbev;
59 static void ATTR_DEAD usage(int);
61 static void sig_handler(int, short, void *);
63 static int openconn(void);
64 static void mark_nonblock(int);
66 static void tls_readcb(int, short, void *);
67 static void tls_writecb(int, short, void *);
69 static void client_read(struct bufferevent *, void *);
70 static void client_write(struct bufferevent *, void *);
71 static void client_error(struct bufferevent *, short, void *);
73 static void repl_read(struct bufferevent *, void *);
74 static void repl_error(struct bufferevent *, short, void *);
76 static void excmd_version(const char **, int);
77 static void excmd_attach(const char **, int);
78 static void excmd_clunk(const char **, int);
79 static void excmd_flush(const char **, int);
80 static void excmd_walk(const char ** , int);
81 static void excmd(const char **, int);
83 static const char *pp_qid_type(uint8_t);
84 static void pp_qid(const uint8_t *, uint32_t);
85 static void pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *);
86 static void handle_9p(const uint8_t *, size_t);
87 static void clr(void);
88 static void prompt(void);
90 static void ATTR_DEAD
91 usage(int ret)
92 {
93 fprintf(stderr,
94 "usage: %s [-chv] [-C crt] [-K key] [-H host] [-P port]\n",
95 getprogname());
96 fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE_VERSION "\n");
97 exit(ret);
98 }
100 static void
101 sig_handler(int sig, short event, void *d)
103 /*
104 * Normal signal handler rules don't apply because libevent
105 * decouples for us.
106 */
108 switch (sig) {
109 case SIGINT:
110 case SIGTERM:
111 clr();
112 log_warnx("Shutting down...");
113 event_loopbreak();
114 return;
115 default:
116 fatalx("unexpected signal %d", sig);
120 static int
121 openconn(void)
123 struct addrinfo hints, *res, *res0;
124 int error;
125 int save_errno;
126 int s;
127 const char *cause = NULL;
129 memset(&hints, 0, sizeof(hints));
130 hints.ai_family = AF_UNSPEC;
131 hints.ai_socktype = SOCK_STREAM;
132 if ((error = getaddrinfo(host, port, &hints, &res0))) {
133 warnx("%s", gai_strerror(error));
134 return -1;
137 s = -1;
138 for (res = res0; res; res = res->ai_next) {
139 s = socket(res->ai_family, res->ai_socktype,
140 res->ai_protocol);
141 if (s == -1) {
142 cause = "socket";
143 continue;
146 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
147 cause = "connect";
148 save_errno = errno;
149 close(s);
150 errno = save_errno;
151 s = -1;
152 continue;
155 break;
158 freeaddrinfo(res0);
160 if (s == -1)
161 warn("%s", cause);
163 return s;
166 static void
167 mark_nonblock(int fd)
169 int flags;
171 if ((flags = fcntl(fd, F_GETFL)) == -1)
172 fatal("fcntl(F_GETFL)");
173 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
174 fatal("fcntl(F_SETFL)");
177 static void
178 tls_readcb(int fd, short event, void *d)
180 struct bufferevent *bufev = d;
181 char buf[IBUF_READ_SIZE];
182 int what = EVBUFFER_READ;
183 int howmuch = IBUF_READ_SIZE;
184 ssize_t ret;
185 size_t len;
187 if (event == EV_TIMEOUT) {
188 what |= EVBUFFER_TIMEOUT;
189 goto err;
192 if (bufev->wm_read.high != 0)
193 howmuch = MIN(sizeof(buf), bufev->wm_read.high);
195 switch (ret = tls_read(ctx, buf, howmuch)) {
196 case TLS_WANT_POLLIN:
197 case TLS_WANT_POLLOUT:
198 goto retry;
199 case -1:
200 what |= EVBUFFER_ERROR;
201 goto err;
203 len = ret;
205 if (len == 0) {
206 what |= EVBUFFER_EOF;
207 goto err;
210 if (evbuffer_add(bufev->input, buf, len) == -1) {
211 what |= EVBUFFER_ERROR;
212 goto err;
215 event_add(&bufev->ev_read, NULL);
217 len = EVBUFFER_LENGTH(bufev->input);
218 if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
219 return;
220 if (bufev->readcb != NULL)
221 (*bufev->readcb)(bufev, bufev->cbarg);
222 return;
224 retry:
225 event_add(&bufev->ev_read, NULL);
226 return;
228 err:
229 (*bufev->errorcb)(bufev, what, bufev->cbarg);
232 static void
233 tls_writecb(int fd, short event, void *d)
235 struct bufferevent *bufev = d;
236 ssize_t ret;
237 size_t len;
238 short what = EVBUFFER_WRITE;
239 void *data;
241 if (event == EV_TIMEOUT) {
242 what |= EVBUFFER_TIMEOUT;
243 goto err;
246 len = EVBUFFER_LENGTH(bufev->output);
247 if (len != 0) {
248 data = EVBUFFER_DATA(bufev->output);
250 #if DEBUG_PACKETS
251 hexdump("outgoing msg", data, len);
252 #endif
254 switch (ret = tls_write(ctx, data, len)) {
255 case TLS_WANT_POLLIN:
256 case TLS_WANT_POLLOUT:
257 goto retry;
258 case -1:
259 what |= EVBUFFER_ERROR;
260 goto err;
262 evbuffer_drain(bufev->output, ret);
265 if (EVBUFFER_LENGTH(bufev->output) != 0)
266 event_add(&bufev->ev_write, NULL);
268 if (bufev->writecb != NULL &&
269 EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
270 (*bufev->writecb)(bufev, bufev->cbarg);
271 return;
273 retry:
274 event_add(&bufev->ev_write, NULL);
275 return;
276 err:
277 (*bufev->errorcb)(bufev, what, bufev->cbarg);
280 static void
281 client_read(struct bufferevent *bev, void *d)
283 struct evbuffer *src = EVBUFFER_INPUT(bev);
284 uint32_t len;
285 uint8_t *data;
287 for (;;) {
288 if (EVBUFFER_LENGTH(src) < sizeof(len))
289 return;
291 data = EVBUFFER_DATA(src);
293 memcpy(&len, data, sizeof(len));
294 len = le32toh(len);
296 if (len < HEADERSIZE)
297 fatal("incoming message is too small! (%d bytes)",
298 len);
300 if (len > EVBUFFER_LENGTH(src))
301 return;
303 #if DEBUG_PACKETS
304 hexdump("incoming msg", data, len);
305 #endif
307 handle_9p(data, len);
308 evbuffer_drain(src, len);
312 static void
313 client_write(struct bufferevent *bev, void *data)
315 return; /* nothing to do */
318 static void
319 client_error(struct bufferevent *bev, short err, void *data)
321 if (err & EVBUFFER_ERROR)
322 fatal("buffer event error");
324 if (err & EVBUFFER_EOF) {
325 clr();
326 log_info("EOF");
327 event_loopbreak();
328 return;
331 clr();
332 log_warnx("unknown event error");
333 event_loopbreak();
336 static void
337 repl_read(struct bufferevent *bev, void *d)
339 size_t len;
340 int argc;
341 const char *argv[10], **ap;
342 char *line;
344 line = evbuffer_readln(bev->input, &len, EVBUFFER_EOL_LF);
345 if (line == NULL)
346 return;
348 for (argc = 0, ap = argv; ap < &argv[9] &&
349 (*ap = strsep(&line, " \t")) != NULL;) {
350 if (**ap != '\0')
351 ap++, argc++;
354 clr();
355 excmd(argv, argc);
356 prompt();
358 free(line);
361 static void
362 repl_error(struct bufferevent *bev, short error, void *d)
364 fatalx("an error occurred");
367 static inline void
368 do_send(void)
370 bufferevent_write_buffer(bev, evb);
373 /* version [version-str] */
374 static void
375 excmd_version(const char **argv, int argc)
377 const char *s;
379 s = VERSION9P;
380 if (argc == 2)
381 s = argv[1];
383 tversion(s, MSIZE9P);
384 do_send();
387 /* attach fid uname aname */
388 static void
389 excmd_attach(const char **argv, int argc)
391 uint32_t fid;
392 const char *errstr;
394 if (argc != 4)
395 goto usage;
397 fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
398 if (errstr != NULL) {
399 log_warnx("fid is %s: %s", errstr, argv[1]);
400 return;
403 tattach(fid, NOFID, argv[2], argv[3]);
404 do_send();
405 return;
407 usage:
408 log_warnx("usage: attach fid uname aname");
411 /* clunk fid */
412 static void
413 excmd_clunk(const char **argv, int argc)
415 uint32_t fid;
416 const char *errstr;
418 if (argc != 2)
419 goto usage;
421 fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
422 if (errstr != NULL) {
423 log_warnx("fid is %s: %s", errstr, argv[1]);
424 return;
427 tclunk(fid);
428 do_send();
429 return;
431 usage:
432 log_warnx("usage: clunk fid");
435 /* flush oldtag */
436 static void
437 excmd_flush(const char **argv, int argc)
439 uint16_t oldtag;
440 const char *errstr;
442 if (argc != 2)
443 goto usage;
445 oldtag = strtonum(argv[1], 0, UINT16_MAX, &errstr);
446 if (errstr != NULL) {
447 log_warnx("oldtag is %s: %s", errstr, argv[1]);
448 return;
451 tflush(oldtag);
452 do_send();
453 return;
455 usage:
456 log_warnx("usage: flush oldtag");
459 /* walk fid newfid wnames... */
460 static void
461 excmd_walk(const char **argv, int argc)
463 uint32_t fid, newfid;
464 const char *errstr;
466 if (argc < 3)
467 goto usage;
469 fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
470 if (errstr != NULL) {
471 log_warnx("fid is %s: %s", errstr, argv[1]);
472 return;
475 newfid = strtonum(argv[2], 0, UINT32_MAX, &errstr);
476 if (errstr != NULL) {
477 log_warnx("newfid is %s: %s", errstr, argv[1]);
478 return;
481 twalk(fid, newfid, argv + 3, argc - 3);
482 do_send();
483 return;
485 usage:
486 log_warnx("usage: walk fid newfid wnames...");
489 static void
490 excmd(const char **argv, int argc)
492 struct cmd {
493 const char *name;
494 void (*fn)(const char **, int);
495 } cmds[] = {
496 {"version", excmd_version},
497 {"attach", excmd_attach},
498 {"clunk", excmd_clunk},
499 {"flush", excmd_flush},
500 {"walk", excmd_walk},
501 };
502 size_t i;
504 if (argc == 0)
505 return;
507 for (i = 0; i < sizeof(cmds)/sizeof(cmds[0]); ++i) {
508 if (!strcmp(cmds[i].name, argv[0])) {
509 cmds[i].fn(argv, argc);
510 return;
514 log_warnx("Unknown command %s", *argv);
517 static const char *
518 pp_qid_type(uint8_t type)
520 switch (type) {
521 case QTDIR: return "dir";
522 case QTAPPEND: return "append-only";
523 case QTEXCL: return "exclusive";
524 case QTMOUNT: return "mounted-channel";
525 case QTAUTH: return "authentication";
526 case QTTMP: return "non-backed-up";
527 case QTSYMLINK: return "symlink";
528 case QTFILE: return "file";
531 return "unknown";
534 static void
535 pp_qid(const uint8_t *d, uint32_t len)
537 uint64_t path;
538 uint32_t vers;
539 uint8_t type;
541 if (len < 13) {
542 printf("invalid");
543 return;
546 type = *d++;
548 memcpy(&vers, d, sizeof(vers));
549 d += sizeof(vers);
550 vers = le64toh(vers);
552 memcpy(&path, d, sizeof(path));
553 d += sizeof(path);
554 path = le64toh(path);
556 printf("qid{path=%"PRIu64" version=%"PRIu32" type=0x%x\"%s\"}",
557 path, vers, type, pp_qid_type(type));
560 static void
561 pp_msg(uint32_t len, uint8_t type, uint16_t tag, const uint8_t *d)
563 uint32_t msize;
564 uint16_t slen;
566 printf("len=%"PRIu32" type=%d[%s] tag=0x%x[%d] ", len,
567 type, pp_msg_type(type), tag, tag);
569 len -= HEADERSIZE;
571 switch (type) {
572 case Rversion:
573 if (len < 6) {
574 printf("invalid: not enough space for msize "
575 "and version provided.");
576 break;
579 memcpy(&msize, d, sizeof(msize));
580 d += sizeof(msize);
581 len -= sizeof(msize);
582 msize = le32toh(msize);
584 memcpy(&slen, d, sizeof(slen));
585 d += sizeof(slen);
586 len -= sizeof(slen);
587 slen = le16toh(slen);
589 if (len != slen) {
590 printf("invalid: version string length doesn't "
591 "match. Got %d; want %d", slen, len);
592 break;
595 printf("msize=%"PRIu32" version[%"PRIu16"]=\"",
596 msize, slen);
597 fwrite(d, 1, slen, stdout);
598 printf("\"");
600 break;
602 case Rattach:
603 pp_qid(d, len);
604 break;
606 case Rclunk:
607 if (len != 0)
608 printf("invalid Rclunk: %"PRIu32" extra bytes", len);
609 break;
611 case Rflush:
612 if (len != 0)
613 printf("invalid Rflush: %"PRIu32" extra bytes", len);
614 break;
616 case Rwalk:
617 if (len < 2) {
618 printf("invaild Rwalk: less than two bytes (%d)",
619 (int)len);
620 break;
623 memcpy(&slen, d, sizeof(slen));
624 d += sizeof(slen);
625 len -= sizeof(slen);
626 slen = le16toh(slen);
628 if (len != QIDSIZE * slen) {
629 printf("invalid Rwalk: wanted %d bytes for %d qids "
630 "but got %"PRIu32" bytes instead",
631 QIDSIZE*slen, slen, len);
632 break;
635 printf("nwqid=%"PRIu16, slen);
637 for (; slen != 0; slen--) {
638 printf(" ");
639 pp_qid(d, len);
640 d += QIDSIZE;
641 len -= QIDSIZE;
644 break;
646 case Rerror:
647 memcpy(&slen, d, sizeof(slen));
648 d += sizeof(slen);
649 len -= sizeof(slen);
650 slen = le16toh(slen);
652 if (slen != len) {
653 printf("invalid: error string length doesn't "
654 "match. Got %d; want %d", slen, len);
655 break;
658 printf("error=\"");
659 fwrite(d, 1, slen, stdout);
660 printf("\"");
662 break;
664 default:
665 printf("unknown command type");
668 printf("\n");
671 static void
672 handle_9p(const uint8_t *data, size_t size)
674 uint32_t len;
675 uint16_t tag;
676 uint8_t type;
678 assert(size >= HEADERSIZE);
680 memcpy(&len, data, sizeof(len));
681 data += sizeof(len);
683 memcpy(&type, data, sizeof(type));
684 data += sizeof(type);
686 memcpy(&tag, data, sizeof(tag));
687 data += sizeof(tag);
689 len = le32toh(len);
690 /* type is one byte long, no endianness issues */
691 tag = le16toh(tag);
693 clr();
694 pp_msg(len, type, tag, data);
695 prompt();
698 static void
699 clr(void)
701 printf("\r");
702 fflush(stdout);
705 static void
706 prompt(void)
708 printf("%s", PROMPT);
709 fflush(stdout);
712 int
713 main(int argc, char **argv)
715 int ch, sock, handshake;
716 struct event ev_sigint, ev_sigterm;
718 signal(SIGPIPE, SIG_IGN);
720 while ((ch = getopt(argc, argv, "C:cH:hK:P:v")) != -1) {
721 switch (ch) {
722 case 'C':
723 crtpath = optarg;
724 break;
725 case 'c':
726 tls = 1;
727 break;
728 case 'H':
729 host = optarg;
730 break;
731 case 'h':
732 usage(0);
733 break;
734 case 'K':
735 keypath = optarg;
736 break;
737 case 'P':
738 port = optarg;
739 break;
740 case 'v':
741 verbose = 1;
742 break;
743 default:
744 usage(1);
748 if (host == NULL)
749 host = "localhost";
750 if (port == NULL)
751 port = "1337";
753 argc -= optind;
754 argv += optind;
756 if (argc != 0)
757 usage(1);
758 /* if (!tls || (crtpath != NULL || keypath != NULL)) */
759 /* usage(1); */
760 if (!tls)
761 errx(1, "must enable tls (for now)");
763 log_init(1, LOG_DAEMON);
764 log_setverbose(verbose);
765 log_procinit(getprogname());
767 if ((tlsconf = tls_config_new()) == NULL)
768 fatalx("tls_config_new");
769 tls_config_insecure_noverifycert(tlsconf);
770 tls_config_insecure_noverifyname(tlsconf);
771 if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
772 fatalx("can't load certs (%s, %s)", crtpath, keypath);
774 if ((ctx = tls_client()) == NULL)
775 fatal("tls_client");
776 if (tls_configure(ctx, tlsconf) == -1)
777 fatalx("tls_configure: %s", tls_error(ctx));
779 log_info("connecting to %s:%s...", host, port);
781 if ((sock = openconn()) == -1)
782 fatalx("can't connect to %s:%s", host, port);
784 if (tls_connect_socket(ctx, sock, host) == -1)
785 fatalx("tls_connect_socket: %s", tls_error(ctx));
787 for (handshake = 0; !handshake;) {
788 switch (tls_handshake(ctx)) {
789 case -1:
790 fatalx("tls_handshake: %s", tls_error(ctx));
791 case 0:
792 handshake = 1;
793 break;
797 log_info("connected!");
799 mark_nonblock(sock);
801 event_init();
803 /* initialize global evb */
804 if ((evb = evbuffer_new()) == NULL)
805 fatal("evbuffer_new");
807 signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
808 signal_set(&ev_sigterm, SIGINT, sig_handler, NULL);
810 signal_add(&ev_sigint, NULL);
811 signal_add(&ev_sigterm, NULL);
813 bev = bufferevent_new(sock, client_read, client_write, client_error,
814 NULL);
815 if (bev == NULL)
816 fatal("bufferevent_new");
818 /* setup tls/io */
819 event_set(&bev->ev_read, sock, EV_READ, tls_readcb, bev);
820 event_set(&bev->ev_write, sock, EV_WRITE, tls_writecb, bev);
822 bufferevent_enable(bev, EV_READ|EV_WRITE);
824 mark_nonblock(0);
825 inbev = bufferevent_new(0, repl_read, NULL, repl_error, NULL);
826 bufferevent_enable(inbev, EV_READ);
828 prompt();
829 event_dispatch();
831 bufferevent_free(bev);
832 tls_free(ctx);
833 tls_config_free(tlsconf);
834 close(sock);
836 return 0;