Annotation of src/usr.bin/ftp/cmd.c, Revision 1.2
1.2 ! jasper 1: /* $OpenBSD$ */
! 2:
1.1 kmos 3: /*
4: * Copyright (c) 2018 Sunil Nimmagadda <sunil@openbsd.org>
5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18:
19: #include <sys/socket.h>
20: #include <sys/stat.h>
21:
22: #include <arpa/telnet.h>
23:
24: #include <err.h>
25: #include <errno.h>
26: #include <histedit.h>
27: #include <libgen.h>
28: #include <limits.h>
29: #include <pwd.h>
30: #include <signal.h>
31: #include <stdio.h>
32: #include <stdlib.h>
33: #include <string.h>
34: #include <unistd.h>
35:
36: #include "ftp.h"
37:
38: #define ARGVMAX 64
39:
40: static void cmd_interrupt(int);
41: static int cmd_lookup(const char *);
42: static FILE *data_fopen(const char *);
43: static void do_open(int, char **);
44: static void do_help(int, char **);
45: static void do_quit(int, char **);
46: static void do_ls(int, char **);
47: static void do_pwd(int, char **);
48: static void do_cd(int, char **);
49: static void do_get(int, char **);
50: static void do_passive(int, char **);
51: static void do_lcd(int, char **);
52: static void do_lpwd(int, char **);
53: static void do_put(int, char **);
54: static void do_mget(int, char **);
55: static void ftp_abort(void);
56: static char *prompt(void);
57:
58: static FILE *ctrl_fp, *data_fp;
59:
60: static struct {
61: const char *name;
62: const char *info;
63: void (*cmd)(int, char **);
64: int conn_required;
65: } cmd_tbl[] = {
66: { "open", "connect to remote ftp server", do_open, 0 },
67: { "close", "terminate ftp session", do_quit, 1 },
68: { "help", "print local help information", do_help, 0 },
69: { "?", "print local help information", do_help, 0 },
70: { "quit", "terminate ftp session and exit", do_quit, 0 },
71: { "exit", "terminate ftp session and exit", do_quit, 0 },
72: { "ls", "list contents of remote directory", do_ls, 1 },
73: { "pwd", "print working directory on remote machine", do_pwd, 1 },
74: { "cd", "change remote working directory", do_cd, 1 },
75: { "nlist", "nlist contents of remote directory", do_ls, 1 },
76: { "get", "receive file", do_get, 1 },
77: { "passive", "toggle passive transfer mode", do_passive, 0 },
78: { "lcd", "change local working directory", do_lcd, 0 },
79: { "lpwd", "print local working directory", do_lpwd, 0 },
80: { "put", "send one file", do_put, 1 },
81: { "mget", "get multiple files", do_mget, 1 },
82: { "mput", "send multiple files", do_mget, 1 },
83: };
84:
85: static void
86: cmd_interrupt(int signo)
87: {
88: const char msg[] = "\rwaiting for remote to finish abort\n";
89: int save_errno = errno;
90:
91: if (data_fp != NULL)
92: (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
93:
94: interrupted = 1;
95: errno = save_errno;
96: }
97:
98: void
99: cmd(const char *host, const char *port, const char *path)
100: {
101: HistEvent hev;
102: EditLine *el;
103: History *hist;
104: const char *line;
105: char **ap, *argv[ARGVMAX], *cp;
106: int count, i;
107:
108: if ((el = el_init(getprogname(), stdin, stdout, stderr)) == NULL)
109: err(1, "couldn't initialise editline");
110:
111: if ((hist = history_init()) == NULL)
112: err(1, "couldn't initialise editline history");
113:
114: history(hist, &hev, H_SETSIZE, 100);
115: el_set(el, EL_HIST, history, hist);
116: el_set(el, EL_PROMPT, prompt);
117: el_set(el, EL_EDITOR, "emacs");
118: el_set(el, EL_TERMINAL, NULL);
119: el_set(el, EL_SIGNAL, 1);
120: el_source(el, NULL);
121:
122: if (host != NULL) {
123: argv[0] = "open";
124: argv[1] = (char *)host;
125: argv[2] = port ? (char *)port : "21";
126: do_open(3, argv);
127: /* If we don't have a connection, exit */
128: if (ctrl_fp == NULL)
129: exit(1);
130:
131: if (path != NULL) {
132: argv[0] = "cd";
133: argv[1] = (char *)path;
134: do_cd(2, argv);
135: }
136: }
137:
138: for (;;) {
139: signal(SIGINT, SIG_IGN);
140: if ((line = el_gets(el, &count)) == NULL || count <= 0) {
141: if (verbose)
142: fprintf(stderr, "\n");
143: argv[0] = "quit";
144: do_quit(1, argv);
145: break;
146: }
147:
148: if (count <= 1)
149: continue;
150:
151: if ((cp = strrchr(line, '\n')) != NULL)
152: *cp = '\0';
153:
154: history(hist, &hev, H_ENTER, line);
155: for (ap = argv; ap < &argv[ARGVMAX - 1] &&
156: (*ap = strsep((char **)&line, " \t")) != NULL;) {
157: if (**ap != '\0')
158: ap++;
159: }
160: *ap = NULL;
161:
162: if (argv[0] == NULL)
163: continue;
164:
165: if ((i = cmd_lookup(argv[0])) == -1) {
166: fprintf(stderr, "Invalid command.\n");
167: continue;
168: }
169:
170: if (cmd_tbl[i].conn_required && ctrl_fp == NULL) {
171: fprintf(stderr, "Not connected.\n");
172: continue;
173: }
174:
175: interrupted = 0;
176: signal(SIGINT, cmd_interrupt);
177: cmd_tbl[i].cmd(ap - argv, argv);
178:
179: if (strcmp(cmd_tbl[i].name, "quit") == 0 ||
180: strcmp(cmd_tbl[i].name, "exit") == 0)
181: break;
182: }
183:
184: el_end(el);
185: }
186:
187: static int
188: cmd_lookup(const char *cmd)
189: {
190: size_t i;
191:
192: for (i = 0; i < nitems(cmd_tbl); i++)
193: if (strcmp(cmd, cmd_tbl[i].name) == 0)
194: return i;
195:
196: return -1;
197: }
198:
199: static char *
200: prompt(void)
201: {
202: return "ftp> ";
203: }
204:
205: static FILE *
206: data_fopen(const char *mode)
207: {
208: int fd;
209:
210: fd = activemode ? ftp_eprt(ctrl_fp) : ftp_epsv(ctrl_fp);
211: if (fd == -1) {
212: if (io_debug)
213: fprintf(stderr, "Failed to open data connection");
214:
215: return NULL;
216: }
217:
218: return fdopen(fd, mode);
219: }
220:
221: static void
222: ftp_abort(void)
223: {
224: char buf[BUFSIZ];
225:
226: snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC);
227: if (send(fileno(ctrl_fp), buf, 3, MSG_OOB) != 3)
228: warn("abort");
229:
230: ftp_command(ctrl_fp, "%cABOR", DM);
231: }
232:
233: static void
234: do_open(int argc, char **argv)
235: {
236: const char *host = NULL, *port = "21";
237: char *buf = NULL;
238: size_t n = 0;
239: int sock;
240:
241: if (ctrl_fp != NULL) {
242: fprintf(stderr, "already connected, use close first.\n");
243: return;
244: }
245:
246: switch (argc) {
247: case 3:
248: port = argv[2];
249: /* FALLTHROUGH */
250: case 2:
251: host = argv[1];
252: break;
253: default:
254: fprintf(stderr, "usage: open host [port]\n");
255: return;
256: }
257:
258: if ((sock = tcp_connect(host, port, 0)) == -1)
259: return;
260:
261: fprintf(stderr, "Connected to %s.\n", host);
262: if ((ctrl_fp = fdopen(sock, "r+")) == NULL)
263: err(1, "%s: fdopen", __func__);
264:
265: /* greeting */
266: ftp_getline(&buf, &n, 0, ctrl_fp);
267: free(buf);
268: if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) {
269: fclose(ctrl_fp);
270: ctrl_fp = NULL;
271: }
272: }
273:
274: static void
275: do_help(int argc, char **argv)
276: {
277: size_t i;
278: int j;
279:
280: if (argc == 1) {
281: for (i = 0; i < nitems(cmd_tbl); i++)
282: fprintf(stderr, "%s\n", cmd_tbl[i].name);
283:
284: return;
285: }
286:
287: for (i = 1; i < (size_t)argc; i++) {
288: if ((j = cmd_lookup(argv[i])) == -1)
289: fprintf(stderr, "invalid help command %s\n", argv[i]);
290: else
291: fprintf(stderr, "%s\t%s\n", argv[i], cmd_tbl[j].info);
292: }
293: }
294:
295: static void
296: do_quit(int argc, char **argv)
297: {
298: if (ctrl_fp == NULL)
299: return;
300:
301: ftp_command(ctrl_fp, "QUIT");
302: fclose(ctrl_fp);
303: ctrl_fp = NULL;
304: }
305:
306: static void
307: do_ls(int argc, char **argv)
308: {
309: FILE *dst_fp = stdout;
310: const char *cmd, *local_fname = NULL, *remote_dir = NULL;
311: char *buf = NULL;
312: size_t n = 0;
313: ssize_t len;
314: int r;
315:
316: switch (argc) {
317: case 3:
318: if (strcmp(argv[2], "-") != 0)
319: local_fname = argv[2];
320: /* FALLTHROUGH */
321: case 2:
322: remote_dir = argv[1];
323: /* FALLTHROUGH */
324: case 1:
325: break;
326: default:
327: fprintf(stderr, "usage: ls [remote-directory [local-file]]\n");
328: return;
329: }
330:
331: if ((data_fp = data_fopen("r")) == NULL)
332: return;
333:
334: if (local_fname && (dst_fp = fopen(local_fname, "w")) == NULL) {
335: warn("fopen %s", local_fname);
336: fclose(data_fp);
337: data_fp = NULL;
338: return;
339: }
340:
341: cmd = (strcmp(argv[0], "ls") == 0) ? "LIST" : "NLST";
342: if (remote_dir != NULL)
343: r = ftp_command(ctrl_fp, "%s %s", cmd, remote_dir);
344: else
345: r = ftp_command(ctrl_fp, "%s", cmd);
346:
347: if (r != P_PRE) {
348: fclose(data_fp);
349: data_fp = NULL;
350: if (dst_fp != stdout)
351: fclose(dst_fp);
352:
353: return;
354: }
355:
356: while ((len = getline(&buf, &n, data_fp)) != -1 && !interrupted) {
357: buf[len - 1] = '\0';
358: if (len >= 2 && buf[len - 2] == '\r')
359: buf[len - 2] = '\0';
360:
361: fprintf(dst_fp, "%s\n", buf);
362: }
363:
364: if (interrupted)
365: ftp_abort();
366:
367: fclose(data_fp);
368: data_fp = NULL;
369: ftp_getline(&buf, &n, 0, ctrl_fp);
370: free(buf);
371: if (dst_fp != stdout)
372: fclose(dst_fp);
373: }
374:
375: static void
376: do_get(int argc, char **argv)
377: {
378: FILE *dst_fp;
379: const char *local_fname = NULL, *p, *remote_fname;
380: char *buf = NULL;
381: size_t n = 0;
382: off_t file_sz, offset = 0;
383:
384: switch (argc) {
385: case 3:
386: local_fname = argv[2];
387: /* FALLTHROUGH */
388: case 2:
389: remote_fname = argv[1];
390: break;
391: default:
392: fprintf(stderr, "usage: get remote-file [local-file]\n");
393: return;
394: }
395:
396: if (local_fname == NULL)
397: local_fname = remote_fname;
398:
399: if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
400: return;
401:
402: log_info("local: %s remote: %s\n", local_fname, remote_fname);
403: if (ftp_size(ctrl_fp, remote_fname, &file_sz, &buf) != P_OK) {
404: fprintf(stderr, "%s", buf);
405: return;
406: }
407:
408: if ((data_fp = data_fopen("r")) == NULL)
409: return;
410:
411: if ((dst_fp = fopen(local_fname, "w")) == NULL) {
412: warn("%s", local_fname);
413: fclose(data_fp);
414: data_fp = NULL;
415: return;
416: }
417:
418: if (ftp_command(ctrl_fp, "RETR %s", remote_fname) != P_PRE) {
419: fclose(data_fp);
420: data_fp = NULL;
421: fclose(dst_fp);
422: return;
423: }
424:
425: if (progressmeter) {
426: p = basename(remote_fname);
427: start_progress_meter(p, NULL, file_sz, &offset);
428: }
429:
430: copy_file(dst_fp, data_fp, &offset);
431: if (progressmeter)
432: stop_progress_meter();
433:
434: if (interrupted)
435: ftp_abort();
436:
437: fclose(data_fp);
438: data_fp = NULL;
439: fclose(dst_fp);
440: ftp_getline(&buf, &n, 0, ctrl_fp);
441: free(buf);
442: }
443:
444: static void
445: do_pwd(int argc, char **argv)
446: {
447: ftp_command(ctrl_fp, "PWD");
448: }
449:
450: static void
451: do_cd(int argc, char **argv)
452: {
453: if (argc != 2) {
454: fprintf(stderr, "usage: cd remote-directory\n");
455: return;
456: }
457:
458: ftp_command(ctrl_fp, "CWD %s", argv[1]);
459: }
460:
461: static void
462: do_passive(int argc, char **argv)
463: {
464: switch (argc) {
465: case 1:
466: break;
467: case 2:
468: if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "off") == 0)
469: break;
470:
471: /* FALLTHROUGH */
472: default:
473: fprintf(stderr, "usage: passive [on | off]\n");
474: return;
475: }
476:
477: if (argv[1] != NULL) {
478: activemode = (strcmp(argv[1], "off") == 0) ? 1 : 0;
479: fprintf(stderr, "passive mode is %s\n", argv[1]);
480: return;
481: }
482:
483: activemode = !activemode;
484: fprintf(stderr, "passive mode is %s\n", activemode ? "off" : "on");
485: }
486:
487: static void
488: do_lcd(int argc, char **argv)
489: {
490: struct passwd *pw = NULL;
491: const char *dir, *login;
492: char cwd[PATH_MAX];
493:
494: switch (argc) {
495: case 1:
496: case 2:
497: break;
498: default:
499: fprintf(stderr, "usage: lcd [local-directory]\n");
500: return;
501: }
502:
503: if ((login = getlogin()) != NULL)
504: pw = getpwnam(login);
505:
506: if (pw == NULL && (pw = getpwuid(getuid())) == NULL) {
507: fprintf(stderr, "Failed to get home directory\n");
508: return;
509: }
510:
511: dir = argv[1] ? argv[1] : pw->pw_dir;
512: if (chdir(dir) != 0) {
513: warn("local: %s", dir);
514: return;
515: }
516:
517: if (getcwd(cwd, sizeof cwd) == NULL) {
518: warn("getcwd");
519: return;
520: }
521:
522: fprintf(stderr, "Local directory now %s\n", cwd);
523: }
524:
525: static void
526: do_lpwd(int argc, char **argv)
527: {
528: char cwd[PATH_MAX];
529:
530: if (getcwd(cwd, sizeof cwd) == NULL) {
531: warn("getcwd");
532: return;
533: }
534:
535: fprintf(stderr, "Local directory %s\n", cwd);
536: }
537:
538: static void
539: do_put(int argc, char **argv)
540: {
541: struct stat sb;
542: FILE *src_fp;
543: const char *local_fname, *p, *remote_fname = NULL;
544: char *buf = NULL;
545: size_t n = 0;
546: off_t file_sz, offset = 0;
547:
548: switch (argc) {
549: case 3:
550: remote_fname = argv[2];
551: /* FALLTHROUGH */
552: case 2:
553: local_fname = argv[1];
554: break;
555: default:
556: fprintf(stderr, "usage: put local-file [remote-file]\n");
557: return;
558: }
559:
560: if (remote_fname == NULL)
561: remote_fname = local_fname;
562:
563: if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
564: return;
565:
566: log_info("local: %s remote: %s\n", local_fname, remote_fname);
567: if ((data_fp = data_fopen("w")) == NULL)
568: return;
569:
570: if ((src_fp = fopen(local_fname, "r")) == NULL) {
571: warn("%s", local_fname);
572: fclose(data_fp);
573: data_fp = NULL;
574: return;
575: }
576:
577: if (fstat(fileno(src_fp), &sb) != 0) {
578: warn("%s", local_fname);
579: fclose(data_fp);
580: data_fp = NULL;
581: fclose(src_fp);
582: return;
583: }
584: file_sz = sb.st_size;
585:
586: if (ftp_command(ctrl_fp, "STOR %s", remote_fname) != P_PRE) {
587: fclose(data_fp);
588: data_fp = NULL;
589: fclose(src_fp);
590: return;
591: }
592:
593: if (progressmeter) {
594: p = basename(remote_fname);
595: start_progress_meter(p, NULL, file_sz, &offset);
596: }
597:
598: copy_file(data_fp, src_fp, &offset);
599: if (progressmeter)
600: stop_progress_meter();
601:
602: if (interrupted)
603: ftp_abort();
604:
605: fclose(data_fp);
606: data_fp = NULL;
607: fclose(src_fp);
608: ftp_getline(&buf, &n, 0, ctrl_fp);
609: free(buf);
610: }
611:
612: static void
613: do_mget(int argc, char **argv)
614: {
615: void (*fn)(int, char **);
616: const char *usage;
617: char *args[2];
618: int i;
619:
620: if (strcmp(argv[0], "mget") == 0) {
621: fn = do_get;
622: args[0] = "get";
623: usage = "mget remote-files";
624: } else {
625: fn = do_put;
626: args[0] = "put";
627: usage = "mput local-files";
628: }
629:
630: if (argc == 1) {
631: fprintf(stderr, "usage: %s\n", usage);
632: return;
633: }
634:
635: for (i = 1; i < argc && !interrupted; i++) {
636: args[1] = argv[i];
637: fn(2, args);
638: }
639: }