From f11c555dd84fdb3a3f33598c453061fdea7ee54e Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 4 Jan 2017 01:33:19 -0600 Subject: First stab at ftpget/ftpput. (Documents a lot of options other than basic upload/download that aren't implemented yet.) --- toys/net/ftpget.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 toys/net/ftpget.c (limited to 'toys/net') diff --git a/toys/net/ftpget.c b/toys/net/ftpget.c new file mode 100644 index 00000000..13618ab2 --- /dev/null +++ b/toys/net/ftpget.c @@ -0,0 +1,196 @@ +/* ftpget.c - Fetch file(s) from ftp server + * + * Copyright 2016 Rob Landley + * + * 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]", 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 *user; + char *port; + char *password; +) + +// we should get one line of data, but it may be in multiple chunks +int xread2line(int fd, char *buf, int len) +{ + int i, total = 0; + + len--; + while (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_check(int fd) +{ + int rc; + + xread2line(fd, toybuf, sizeof(toybuf)); + if (!sscanf(toybuf, "%d", &rc)) error_exit_raw(toybuf); + + return rc; +} + +void ftpget_main(void) +{ + struct sockaddr_in6 si6; + int fd, rc, i, port; + socklen_t sl = sizeof(si6); + char *s, *remote = toys.optargs[2]; + unsigned long long lenl, lenr; + + if (!(toys.optflags&(FLAG_v-1))) + toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s; + + if (!TT.user) TT.user = "anonymous"; + if (!TT.password) TT.password = "ftpget@"; + if (!TT.port) TT.port = "21"; + if (!remote) remote = toys.optargs[1]; + + // connect + fd = xconnect(*toys.optargs, TT.port, 0, SOCK_STREAM, 0, AI_ADDRCONFIG); + if (getpeername(fd, (void *)&si6, &sl)) perror_exit("getpeername"); + + // Login + if (ftp_check(fd) != 220) error_exit_raw(toybuf); + dprintf(fd, "USER %s\r\n", TT.user); + rc = ftp_check(fd); + if (rc == 331) { + dprintf(fd, "PASS %s\r\n", TT.password); + rc = ftp_check(fd); + } + if (rc != 230) error_exit_raw(toybuf); + + // Only do passive binary transfers + dprintf(fd, "TYPE I\r\n"); + ftp_check(fd); + dprintf(fd, "PASV\r\n"); + + // 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 we 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 (ftp_check(fd) == 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); + if (connect(port, (void *)&si6, sizeof(si6))) perror_exit("connect"); + + if (toys.optflags & (FLAG_s|FLAG_g)) { + int get = toys.optflags&FLAG_g, cnt = toys.optflags&FLAG_c; + + // RETR blocks until file data read from data port, so use SIZE to check + // if file exists before creating local copy + lenr = 0; + dprintf(fd, "SIZE %s\r\n", remote); + if (ftp_check(fd) == 213) sscanf(toybuf, "%*u %llu", &lenr); + else if (get) error_exit("no %s", remote); + + // Open file for reading or writing + i = xcreate(toys.optargs[1], + get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666); + lenl = fdlength(i); + if (get) { + if (cnt) { + if (lenl>=lenr) goto done; + dprintf(fd, "REST %llu\r\n", lenl); + if (ftp_check(fd) != 350) error_exit_raw(toybuf); + } else lenl = 0; + + dprintf(fd, "RETR %s\r\n", remote); + lenl += xsendfile(port, i); + if (ftp_check(fd) != 226) error_exit_raw(toybuf); + if (lenl != lenr) error_exit("short read"); + } else if (toys.optflags & FLAG_s) { + char *send = "STOR"; + + if (cnt && lenr) { + send = "APPE"; + xlseek(i, lenl, SEEK_SET); + } else lenr = 0; + dprintf(fd, "%s %s\r\n", send, remote); + if (ftp_check(fd) != 150) error_exit_raw(toybuf); + lenr += xsendfile(i, port); + if (lenl != lenr) error_exit("short write %lld %lld", lenl, lenr); + close(port); + } + } + dprintf(fd, "QUIT\r\n"); + ftp_check(fd); + + // gslLmMdD + // STOR - upload + // APPE - append + // REST - must immediately precede RETR or STOR + // LIST - list directory contents + // NLST - just list names, one per line + // RNFR RNTO - rename from, rename to + // DELE - delete + // RMD - rmdir + // MKD - mkdir + +done: + if (CFG_TOYBOX_FREE) { + xclose(i); + xclose(port); + xclose(fd); + } +} -- cgit v1.2.3