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 <assert.h>
23 #include <errno.h>
24 #include <netdb.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <tls.h>
31 #include <unistd.h>
33 #if HAVE_LIBREADLINE
34 #include <readline/readline.h>
35 #include <readline/history.h>
36 #endif
38 #ifndef nitems
39 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
40 #endif
42 #include "9pclib.h"
43 #include "kamid.h"
44 #include "utils.h"
45 #include "log.h"
47 /* flags */
48 int tls;
49 const char *crtpath;
50 const char *keypath;
52 /* state */
53 struct tls_config *tlsconf;
54 struct tls *ctx;
55 int sock;
56 struct evbuffer *buf;
57 struct evbuffer *dirbuf;
58 uint32_t msize;
59 int bell;
61 #define PWDFID 0
63 #define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0)
65 #if HAVE_LIBREADLINE
66 static char *
67 read_line(const char *prompt)
68 {
69 char *line;
71 again:
72 if ((line = readline(prompt)) == NULL)
73 return NULL;
74 /* XXX: trim spaces? */
75 if (*line == '\0') {
76 free(line);
77 goto again;
78 }
80 add_history(line);
81 return line;
82 }
83 #else
84 static char *
85 read_line(const char *prompt)
86 {
87 char *ch, *line = NULL;
88 size_t linesize = 0;
89 ssize_t linelen;
91 printf("%s", prompt);
92 fflush(stdout);
94 linelen = getline(&line, &linesize, stdin);
95 if (linelen == -1)
96 return NULL;
98 if ((ch = strchr(line, '\n')) != NULL)
99 *ch = '\0';
100 return line;
102 #endif
104 static void __dead
105 usage(int ret)
107 fprintf(stderr, "usage: %s [-c] host[:port] [path]\n",
108 getprogname());
109 fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE VERSION "\n");
110 exit(ret);
113 static void
114 do_send(void)
116 const void *buf;
117 size_t nbytes;
118 ssize_t r;
120 while (EVBUFFER_LENGTH(evb) != 0) {
121 buf = EVBUFFER_DATA(evb);
122 nbytes = EVBUFFER_LENGTH(evb);
124 if (ctx == NULL) {
125 r = write(sock, buf, nbytes);
126 if (r == 0 || r == -1)
127 errx(1, "EOF");
128 } else {
129 r = tls_write(ctx, buf, nbytes);
130 if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
131 continue;
132 if (r == -1)
133 errx(1, "tls: %s", tls_error(ctx));
136 evbuffer_drain(evb, r);
140 static void
141 mustread(void *d, size_t len)
143 ssize_t r;
145 while (len != 0) {
146 if (ctx == NULL) {
147 r = read(sock, d, len);
148 if (r == 0 || r == -1)
149 errx(1, "EOF");
150 } else {
151 r = tls_read(ctx, d, len);
152 if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
153 continue;
154 if (r == -1)
155 errx(1, "tls: %s", tls_error(ctx));
158 d += r;
159 len -= r;
163 static void
164 recv_msg(void)
166 uint32_t len, l;
167 char tmp[BUFSIZ];
169 mustread(&len, sizeof(len));
170 len = le32toh(len);
171 if (len < HEADERSIZE)
172 errx(1, "read message of invalid length %d", len);
174 len -= 4; /* skip the length just read */
176 while (len != 0) {
177 l = MIN(len, sizeof(tmp));
178 mustread(tmp, l);
179 len -= l;
180 evbuffer_add(buf, tmp, l);
184 static uint64_t
185 np_read64(struct evbuffer *buf)
187 uint64_t n;
189 evbuffer_remove(buf, &n, sizeof(n));
190 return le64toh(n);
193 static uint32_t
194 np_read32(struct evbuffer *buf)
196 uint32_t n;
198 evbuffer_remove(buf, &n, sizeof(n));
199 return le32toh(n);
202 static uint16_t
203 np_read16(struct evbuffer *buf)
205 uint16_t n;
207 evbuffer_remove(buf, &n, sizeof(n));
208 return le16toh(n);
211 static uint16_t
212 np_read8(struct evbuffer *buf)
214 uint8_t n;
216 evbuffer_remove(buf, &n, sizeof(n));
217 return n;
220 static char *
221 np_readstr(struct evbuffer *buf)
223 uint16_t len;
224 char *str;
226 len = np_read16(buf);
227 assert(EVBUFFER_LENGTH(buf) >= len);
229 if ((str = calloc(1, len+1)) == NULL)
230 err(1, "calloc");
231 evbuffer_remove(buf, str, len);
232 return str;
235 static void
236 np_read_qid(struct evbuffer *buf, struct qid *qid)
238 assert(EVBUFFER_LENGTH(buf) >= QIDSIZE);
240 qid->type = np_read8(buf);
241 qid->vers = np_read32(buf);
242 qid->path = np_read64(buf);
245 static void
246 expect(uint8_t type)
248 uint8_t t;
250 t = np_read8(buf);
251 if (t == type)
252 return;
254 if (t == Terror) {
255 char *err;
257 err = np_readstr(buf);
258 errx(1, "expected %s, got error %s",
259 pp_msg_type(type), err);
262 errx(1, "expected %s, got msg type %s",
263 pp_msg_type(type), pp_msg_type(t));
266 static void
267 expect2(uint8_t type, uint16_t tag)
269 uint16_t t;
271 expect(type);
273 t = np_read16(buf);
274 if (t == tag)
275 return;
277 errx(1, "expected tag 0x%x, got 0x%x", tag, t);
280 static void
281 do_version(void)
283 char *version;
285 tversion(VERSION9P, MSIZE9P);
286 do_send();
287 recv_msg();
288 expect2(Rversion, NOTAG);
290 msize = np_read32(buf);
291 version = np_readstr(buf);
293 if (msize > MSIZE9P)
294 errx(1, "got unexpected msize: %d", msize);
295 if (strcmp(version, VERSION9P))
296 errx(1, "unexpected 9p version: %s", version);
298 free(version);
299 ASSERT_EMPTYBUF();
302 static void
303 do_attach(const char *path)
305 const char *user;
306 struct qid qid;
308 if (path == NULL)
309 path = "/";
310 if ((user = getenv("USER")) == NULL)
311 user = "flan";
313 tattach(PWDFID, NOFID, user, path);
314 do_send();
315 recv_msg();
316 expect2(Rattach, iota_tag);
317 np_read_qid(buf, &qid);
319 ASSERT_EMPTYBUF();
322 static uint32_t
323 do_open(uint32_t fid, uint8_t mode)
325 struct qid qid;
326 uint32_t iounit;
328 topen(fid, mode);
329 do_send();
330 recv_msg();
331 expect2(Ropen, iota_tag);
333 np_read_qid(buf, &qid);
334 iounit = np_read32(buf);
336 ASSERT_EMPTYBUF();
338 return iounit;
341 static void
342 do_clunk(uint32_t fid)
344 tclunk(fid);
345 do_send();
346 recv_msg();
347 expect2(Rclunk, iota_tag);
349 ASSERT_EMPTYBUF();
352 static void
353 dup_fid(int fid, int nfid)
355 uint16_t nwqid;
357 twalk(fid, nfid, NULL, 0);
358 do_send();
359 recv_msg();
360 expect2(Rwalk, iota_tag);
362 nwqid = np_read16(buf);
363 assert(nwqid == 0);
365 ASSERT_EMPTYBUF();
368 static void
369 do_tls_connect(const char *host, const char *port)
371 int handshake;
373 if ((tlsconf = tls_config_new()) == NULL)
374 fatalx("tls_config_new");
375 tls_config_insecure_noverifycert(tlsconf);
376 tls_config_insecure_noverifyname(tlsconf);
377 if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
378 fatalx("can't load certs (%s, %s)", crtpath, keypath);
380 if ((ctx = tls_client()) == NULL)
381 fatal("tls_client");
382 if (tls_configure(ctx, tlsconf) == -1)
383 fatalx("tls_configure: %s", tls_error(ctx));
385 if (tls_connect(ctx, host, port) == -1)
386 fatalx("can't connect to %s:%s: %s", host, port,
387 tls_error(ctx));
389 for (handshake = 0; !handshake;) {
390 switch (tls_handshake(ctx)) {
391 case -1:
392 fatalx("tls_handshake: %s", tls_error(ctx));
393 case 0:
394 handshake = 1;
395 break;
400 static void
401 do_ctxt_connect(const char *host, const char *port)
403 struct addrinfo hints, *res, *res0;
404 int error, saved_errno;
405 const char *cause = NULL;
407 memset(&hints, 0, sizeof(hints));
408 hints.ai_family = AF_UNSPEC;
409 hints.ai_socktype = SOCK_STREAM;
410 error = getaddrinfo(host, port, &hints, &res0);
411 if (error)
412 errx(1, "%s", gai_strerror(error));
414 sock = -1;
415 for (res = res0; res != NULL; res = res->ai_next) {
416 sock = socket(res->ai_family, res->ai_socktype,
417 res->ai_protocol);
418 if (sock == -1) {
419 cause = "socket";
420 continue;
423 if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
424 cause = "connect";
425 saved_errno = errno;
426 close(sock);
427 errno = saved_errno;
428 sock = -1;
429 continue;
432 break;
435 if (sock == -1)
436 err(1, "%s", cause);
437 freeaddrinfo(res0);
440 static void
441 do_connect(const char *connspec, const char *path)
443 char *host, *colon;
444 const char *port;
446 host = xstrdup(connspec);
447 if ((colon = strchr(host, ':')) != NULL) {
448 *colon = '\0';
449 port = ++colon;
450 } else
451 port = "1337";
453 printf("connecting to %s:%s...", host, port);
454 fflush(stdout);
456 if (tls)
457 do_tls_connect(host, port);
458 else
459 do_ctxt_connect(host, port);
461 printf(" done!\n");
463 do_version();
464 do_attach(path);
466 free(host);
469 static void
470 cmd_bell(int argc, const char **argv)
472 if (argc == 0) {
473 bell = !bell;
474 if (bell)
475 puts("bell mode enabled");
476 else
477 puts("bell mode disabled");
478 return;
481 if (argc != 1)
482 goto usage;
484 if (!strcmp(*argv, "on")) {
485 bell = 1;
486 puts("bell mode enabled");
487 return;
490 if (!strcmp(*argv, "off")) {
491 bell = 0;
492 puts("bell mode disabled");
493 return;
496 usage:
497 printf("bell [on | off]\n");
500 static void
501 cmd_bye(int argc, const char **argv)
503 log_warnx("bye\n");
504 exit(0);
507 static void
508 cmd_lcd(int argc, const char **argv)
510 const char *dir;
512 if (argc > 1) {
513 printf("lcd takes only one argument\n");
514 return;
517 if (argc == 1)
518 dir = *argv;
520 if (argc == 0 && (dir = getenv("HOME")) == NULL) {
521 printf("HOME is not defined\n");
522 return;
525 if (chdir(dir) == -1)
526 printf("cd: %s: %s\n", dir, strerror(errno));
529 static void
530 cmd_lpwd(int argc, const char **argv)
532 char path[PATH_MAX];
534 if (getcwd(path, sizeof(path)) == NULL) {
535 printf("lpwd: %s\n", strerror(errno));
536 return;
539 printf("%s\n", path);
542 static void
543 cmd_ls(int argc, const char **argv)
545 uint64_t off = 0;
546 uint32_t len;
548 if (argc != 0) {
549 printf("ls don't take arguments (yet)\n");
550 return;
553 dup_fid(PWDFID, 1);
554 do_open(1, KOREAD);
556 evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf));
558 for (;;) {
559 tread(1, off, BUFSIZ);
560 do_send();
561 recv_msg();
562 expect2(Rread, iota_tag);
564 len = np_read32(buf);
565 if (len == 0)
566 break;
568 evbuffer_add_buffer(dirbuf, buf);
569 off += len;
571 ASSERT_EMPTYBUF();
574 while (EVBUFFER_LENGTH(dirbuf) != 0) {
575 struct qid qid;
576 uint64_t len;
577 uint16_t size;
578 char *name;
580 size = np_read16(dirbuf);
581 assert(size <= EVBUFFER_LENGTH(dirbuf));
583 np_read16(dirbuf); /* skip type */
584 np_read32(dirbuf); /* skip dev */
586 np_read_qid(dirbuf, &qid);
587 printf("%s ", pp_qid_type(qid.type));
589 np_read32(dirbuf); /* skip mode */
590 np_read32(dirbuf); /* skip atime */
591 np_read32(dirbuf); /* skip mtime */
593 len = np_read64(dirbuf);
594 printf("%llu ", (unsigned long long)len);
596 name = np_readstr(dirbuf);
597 printf("%s\n", name);
598 free(name);
600 free(np_readstr(dirbuf)); /* skip uid */
601 free(np_readstr(dirbuf)); /* skip gid */
602 free(np_readstr(dirbuf)); /* skip muid */
605 do_clunk(1);
608 static void
609 cmd_verbose(int argc, const char **argv)
611 if (argc == 0) {
612 log_setverbose(!log_getverbose());
613 if (log_getverbose())
614 puts("verbose mode enabled");
615 else
616 puts("verbose mode disabled");
617 return;
620 if (argc != 1)
621 goto usage;
623 if (!strcmp(*argv, "on")) {
624 log_setverbose(1);
625 puts("verbose mode enabled");
626 return;
629 if (!strcmp(*argv, "off")) {
630 log_setverbose(0);
631 puts("verbose mode disabled");
632 return;
635 usage:
636 printf("verbose [on | off]\n");
639 static void
640 excmd(int argc, const char **argv)
642 struct cmd {
643 const char *name;
644 void (*fn)(int, const char **);
645 } cmds[] = {
646 {"bell", cmd_bell},
647 {"bye", cmd_bye},
648 {"lcd", cmd_lcd},
649 {"lpwd", cmd_lpwd},
650 {"ls", cmd_ls},
651 {"quit", cmd_bye},
652 {"verbose", cmd_verbose},
653 };
654 size_t i;
656 if (argc == 0)
657 return;
658 for (i = 0; i < nitems(cmds); ++i) {
659 if (!strcmp(cmds[i].name, *argv)) {
660 cmds[i].fn(argc-1, argv+1);
661 return;
665 log_warnx("unknown command %s", *argv);
668 int
669 main(int argc, char **argv)
671 int ch;
673 log_init(1, LOG_DAEMON);
674 log_setverbose(0);
675 log_procinit(getprogname());
677 while ((ch = getopt(argc, argv, "C:cK:")) != -1) {
678 switch (ch) {
679 case 'C':
680 crtpath = optarg;
681 break;
682 case 'c':
683 tls = 1;
684 break;
685 case 'K':
686 keypath = optarg;
687 break;
688 default:
689 usage(1);
692 argc -= optind;
693 argv += optind;
695 if (argc == 0)
696 usage(1);
698 if ((evb = evbuffer_new()) == NULL)
699 fatal("evbuffer_new");
701 if ((buf = evbuffer_new()) == NULL)
702 fatal("evbuffer_new");
704 if ((dirbuf = evbuffer_new()) == NULL)
705 fatal("evbuferr_new");
707 do_connect(argv[0], argv[1]);
709 /* cmd_ls(0, NULL); */
711 for (;;) {
712 int argc = 0;
713 char *line, *argv[16] = {0}, **ap;
715 if ((line = read_line("kamiftp> ")) == NULL)
716 break;
718 for (argc = 0, ap = argv; ap < &argv[15] &&
719 (*ap = strsep(&line, " \t")) != NULL;) {
720 if (**ap != '\0')
721 ap++, argc++;
723 excmd(argc, (const char **)argv);
725 if (bell) {
726 printf("\a");
727 fflush(stdout);
730 free(line);
733 printf("\n");