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