123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- /* ftpget.c - Fetch file(s) from ftp server
- *
- * Copyright 2016 Rob Landley <rob@landley.net>
- *
- * No standard for the command, but see https://www.ietf.org/rfc/rfc959.txt
- * TODO: local can be -
- * TEST: -g -s (when local and remote exist) -gc, -sc
- * zero length file
- USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN))
- USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN))
- config FTPGET
- bool "ftpget"
- default y
- help
- usage: ftpget [-cvgslLmMdD] [-P PORT] [-p PASSWORD] [-u USER] HOST [LOCAL] REMOTE
- Talk to ftp server. By default get REMOTE file via passive anonymous
- transfer, optionally saving under a LOCAL name. Can also send, list, etc.
- -c Continue partial transfer
- -p Use PORT instead of "21"
- -P Use PASSWORD instead of "ftpget@"
- -u Use USER instead of "anonymous"
- -v Verbose
- Ways to interact with FTP server:
- -d Delete file
- -D Remove directory
- -g Get file (default)
- -l List directory
- -L List (filenames only)
- -m Move file on server from LOCAL to REMOTE
- -M mkdir
- -s Send file
- config FTPPUT
- bool "ftpput"
- default y
- help
- An ftpget that defaults to -s instead of -g
- */
- #define FOR_ftpget
- #include "toys.h"
- GLOBALS(
- char *u, *p, *P;
- int fd;
- )
- // we should get one line of data, but it may be in multiple chunks
- static int xread2line(int fd, char *buf, int len)
- {
- int i, total = 0;
- len--;
- while (total<len && (i = xread(fd, buf, len-total))) {
- total += i;
- if (buf[total-1] == '\n') break;
- }
- if (total>=len) error_exit("overflow");
- while (total--)
- if (buf[total]=='\r' || buf[total]=='\n') buf[total] = 0;
- else break;
- if (toys.optflags & FLAG_v) fprintf(stderr, "%s\n", toybuf);
- return total+1;
- }
- static int ftp_line(char *cmd, char *arg, int must)
- {
- int rc = 0;
- if (cmd) {
- char *s = "%s %s\r\n"+3*(!arg);
- if (toys.optflags & FLAG_v) fprintf(stderr, s, cmd, arg);
- dprintf(TT.fd, s, cmd, arg);
- }
- if (must>=0) {
- xread2line(TT.fd, toybuf, sizeof(toybuf));
- if (!sscanf(toybuf, "%d", &rc) || (must && rc != must))
- error_exit_raw(toybuf);
- }
- return rc;
- }
- void ftpget_main(void)
- {
- struct sockaddr_in6 si6;
- int rc, ii = 1, port = 0;
- socklen_t sl = sizeof(si6);
- char *s, *remote = toys.optargs[2];
- unsigned long long lenl = 0, lenr;
- if (!(toys.optflags&(FLAG_v-1)))
- toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s;
- if (!TT.u) TT.u = "anonymous";
- if (!TT.P) TT.P = "ftpget@";
- if (!TT.p) TT.p = "21";
- if (!remote) remote = toys.optargs[1];
- // connect
- TT.fd = xconnectany(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0,
- AI_ADDRCONFIG));
- if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername");
- // Login
- ftp_line(0, 0, 220);
- rc = ftp_line("USER", TT.u, 0);
- if (rc == 331) rc = ftp_line("PASS", TT.P, 0);
- if (rc != 230) error_exit_raw(toybuf);
- if (toys.optflags & FLAG_m) {
- if (toys.optc != 3) error_exit("-m FROM TO");
- ftp_line("RNFR", toys.optargs[1], 350);
- ftp_line("RNTO", toys.optargs[2], 250);
- } else if (toys.optflags & FLAG_M) ftp_line("MKD", toys.optargs[1], 257);
- else if (toys.optflags & FLAG_d) ftp_line("DELE", toys.optargs[1], 250);
- else if (toys.optflags & FLAG_D) ftp_line("RMD", toys.optargs[1], 250);
- else {
- int get = !(toys.optflags&FLAG_s), cnt = toys.optflags&FLAG_c;
- char *cmd;
- // Only do passive binary transfers
- ftp_line("TYPE", "I", 0);
- rc = ftp_line("PASV", 0, 0);
- // PASV means the server opens a port you connect to instead of the server
- // dialing back to the client. (Still insane, but less so.) So need port #
- // PASV output is "227 PASV ok (x,x,x,x,p1,p2)" where x,x,x,x is the IP addr
- // (must match the server you're talking to???) and port is (256*p1)+p2
- s = 0;
- if (rc==227) for (s = toybuf; (s = strchr(s, ',')); s++) {
- int p1, got = 0;
- sscanf(s, ",%u,%u)%n", &p1, &port, &got);
- if (!got) continue;
- port += 256*p1;
- break;
- }
- if (!s || port<1 || port>65535) error_exit_raw(toybuf);
- si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6
- port = xsocket(si6.sin6_family, SOCK_STREAM, 0);
- xconnect(port, (void *)&si6, sizeof(si6));
- // RETR blocks until file data read from data port, so use SIZE to check
- // if file exists before creating local copy
- lenr = 0;
- if (toys.optflags&(FLAG_s|FLAG_g)) {
- if (ftp_line("SIZE", remote, 0) == 213)
- sscanf(toybuf, "%*u %llu", &lenr);
- else if (get) error_exit("no %s", remote);
- }
- // Open file for reading or writing
- if (toys.optflags & (FLAG_g|FLAG_s)) {
- if (strcmp(toys.optargs[1], "-"))
- ii = xcreate(toys.optargs[1],
- get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666);
- lenl = fdlength(ii);
- }
- if (get) {
- cmd = "RETR";
- if (toys.optflags&FLAG_l) cmd = "LIST";
- if (toys.optflags&FLAG_L) cmd = "NLST";
- if (cnt) {
- char buf[32];
- if (lenl>=lenr) goto done;
- sprintf(buf, "%llu", lenl);
- ftp_line("REST", buf, 350);
- } else lenl = 0;
- ftp_line(cmd, remote, -1);
- lenl += xsendfile(port, ii);
- ftp_line(0, 0, (toys.optflags&FLAG_g) ? 226 : 150);
- } else if (toys.optflags & FLAG_s) {
- cmd = "STOR";
- if (cnt && lenr) {
- cmd = "APPE";
- xlseek(ii, lenl, SEEK_SET);
- } else lenr = 0;
- ftp_line(cmd, remote, 150);
- lenr += xsendfile(ii, port);
- close(port);
- }
- if (toys.optflags&(FLAG_g|FLAG_s))
- if (lenl != lenr) error_exit("short %lld/%lld", lenl, lenr);
- }
- ftp_line("QUIT", 0, 0);
- done:
- if (CFG_TOYBOX_FREE) {
- if (ii!=1) xclose(ii);
- xclose(port);
- xclose(TT.fd);
- }
- }
|