aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/net/ftpget.c196
-rw-r--r--toys/pending/ftpget.c293
2 files changed, 196 insertions, 293 deletions
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 <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]", 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 && (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_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);
+ }
+}
diff --git a/toys/pending/ftpget.c b/toys/pending/ftpget.c
deleted file mode 100644
index a1447139..00000000
--- a/toys/pending/ftpget.c
+++ /dev/null
@@ -1,293 +0,0 @@
-/* ftpget.c - Get a remote file from FTP.
- *
- * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com>
- * Copyright 2013 Kyungwan Han <asura321@gmail.com>
- *
- * No Standard.
- *
-USE_FTPGET(NEWTOY(ftpget, "<2cvu:p:P#<0=21>65535", TOYFLAG_BIN))
-USE_FTPGET(OLDTOY(ftpput, ftpget, TOYFLAG_BIN))
-
-config FTPGET
- bool "ftpget/ftpput"
- default n
- help
- usage: ftpget [-cv] [-u USER -p PASSWORD -P PORT] HOST_NAME [LOCAL_FILENAME] REMOTE_FILENAME
- usage: ftpput [-v] [-u USER -p PASSWORD -P PORT] HOST_NAME [REMOTE_FILENAME] LOCAL_FILENAME
-
- ftpget - Get a remote file from FTP.
- ftpput - Upload a local file on remote machine through FTP.
-
- -c Continue previous transfer.
- -v Verbose.
- -u User name.
- -p Password.
- -P Port Number (default 21).
-*/
-#define FOR_ftpget
-#include "toys.h"
-
-GLOBALS(
- long port; // char *port;
- char *password;
- char *username;
-
- FILE *sockfp;
- int c;
- int isget;
- char buf[sizeof(struct sockaddr_storage)];
-)
-
-#define DATACONNECTION_OPENED 125
-#define FTPFILE_STATUSOKAY 150
-#define FTP_COMMAND_OKAY 200
-#define FTPFILE_STATUS 213
-#define FTPSERVER_READY 220
-#define CLOSE_DATACONECTION 226
-#define PASSIVE_MODE 227
-#define USERLOGGED_SUCCESS 230
-#define PASSWORD_REQUEST 331
-#define REQUESTED_PENDINGACTION 350
-
-
-static void setport(unsigned port_num)
-{
- int af = ((struct sockaddr *)TT.buf)->sa_family;
-
- if (af == AF_INET) ((struct sockaddr_in*)TT.buf)->sin_port = port_num;
- else if (af == AF_INET6) ((struct sockaddr_in6*)TT.buf)->sin6_port = port_num;
-}
-
-static int connect_to_stream()
-{
- int sockfd, af = ((struct sockaddr *)TT.buf)->sa_family;
-
- sockfd = xsocket(af, SOCK_STREAM, 0);
- if (connect(sockfd, (struct sockaddr*)TT.buf,((af == AF_INET)?
- sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6))) < 0) {
- close(sockfd);
- perror_exit("can't connect to remote host");
- }
- return sockfd;
-}
-
-//close ftp connection and print the message.
-static void close_stream(char *msg_str)
-{
- char *str = toybuf; //toybuf holds response data.
-
- //Remove garbage chars (from ' ' space to '\x7f') DEL remote server response.
- while ((*str >= 0x20) && (*str < 0x7f)) str++;
- *str = '\0';
- if (TT.sockfp) fclose(TT.sockfp);
- error_exit("%s server response: %s", (msg_str) ? msg_str:"", toybuf);
-}
-
-//send command to ftp and get return status.
-static int get_ftp_response(char *command, char *param)
-{
- unsigned cmd_status = 0;
- char *fmt = "%s %s\r\n";
-
- if (command) {
- if (!param) fmt += 3;
- fprintf(TT.sockfp, fmt, command, param);
- fflush(TT.sockfp);
- if (toys.optflags & FLAG_v)
- fprintf(stderr, "FTP Request: %s %s\r\n", command, param);
- }
-
- do {
- if (!fgets(toybuf, sizeof(toybuf)-1, TT.sockfp)) close_stream(NULL);
- } while (!isdigit(toybuf[0]) || toybuf[3] != ' ');
-
- toybuf[3] = '\0';
- cmd_status = atolx_range(toybuf, 0, INT_MAX);
- toybuf[3] = ' ';
- return cmd_status;
-}
-
-static void send_requests(void)
-{
- int cmd_status = 0;
-
- //FTP connection request.
- if (get_ftp_response(NULL, NULL) != FTPSERVER_READY) close_stream(NULL);
-
- //230 User authenticated, password please; 331 Password request.
- cmd_status = get_ftp_response("USER", TT.username);
- if (cmd_status == PASSWORD_REQUEST) { //user logged in. Need Password.
- if (get_ftp_response("PASS", TT.password) != USERLOGGED_SUCCESS)
- close_stream("PASS");
- } else if (cmd_status == USERLOGGED_SUCCESS); //do nothing
- else close_stream("USER");
- //200 Type Binary. Command okay.
- if (get_ftp_response("TYPE I", NULL) != FTP_COMMAND_OKAY)
- close_stream("TYPE I");
-}
-
-static void get_sockaddr(char *host)
-{
- struct addrinfo hints, *result;
- char port[6];
- int status;
-
- errno = 0;
- snprintf(port, 6, "%ld", TT.port);
-
- memset(&hints, 0 , sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
-
- status = getaddrinfo(host, port, &hints, &result);
- if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
-
- memcpy(TT.buf, result->ai_addr, result->ai_addrlen);
- freeaddrinfo(result);
-}
-
-// send commands to ftp fo PASV mode.
-static void verify_pasv_mode(char *r_filename)
-{
- char *pch;
- unsigned portnum;
-
- //vsftpd reply like:- "227 Entering Passive Mode (125,19,39,117,43,39)".
- if (get_ftp_response("PASV", NULL) != PASSIVE_MODE) goto close_stream;
-
- //Response is "NNN <some text> (N1,N2,N3,N4,P1,P2) garbage.
- //Server's IP is N1.N2.N3.N4
- //Server's port for data connection is P1*256+P2.
- if (!(pch = strrchr(toybuf, ')'))) goto close_stream;
- *pch = '\0';
- if (!(pch = strrchr(toybuf, ','))) goto close_stream;
- *pch = '\0';
-
- portnum = atolx_range(pch + 1, 0, 255);
-
- if (!(pch = strrchr(toybuf, ','))) goto close_stream;
- *pch = '\0';
- portnum = portnum + (atolx_range(pch + 1, 0, 255) * 256);
- setport(htons(portnum));
-
- if (TT.isget && get_ftp_response("SIZE", r_filename) != FTPFILE_STATUS)
- TT.c = 0;
- return;
-
-close_stream:
- close_stream("PASV");
-}
-
-/*
- * verify the local file presence.
- * if present, get the size of the file.
- */
-static void is_localfile_present(char *l_filename)
-{
- struct stat sb;
-
- if (stat(l_filename, &sb) < 0) perror_exit("stat");
- //if local file present, then request for pending file action.
- if (sb.st_size > 0) {
- sprintf(toybuf, "REST %lu", (unsigned long) sb.st_size);
- if (get_ftp_response(toybuf, NULL) != REQUESTED_PENDINGACTION) TT.c = 0;
- } else TT.c = 0;
-}
-
-static void transfer_file(int local_fd, int remote_fd)
-{
- int len, rfd = (TT.isget)?remote_fd:local_fd,
- wfd = (TT.isget)?local_fd:remote_fd;
-
- if (rfd < 0 || wfd < 0) error_exit("Error in file creation:");
- while ((len = xread(rfd, toybuf, sizeof(toybuf)))) xwrite(wfd, toybuf, len);
-}
-
-static void get_file(char *l_filename, char *r_filename)
-{
- int local_fd = -1, remote_fd;
-
- verify_pasv_mode(r_filename);
- remote_fd = connect_to_stream(); //Connect to data socket.
-
- //if local file name will be '-' then local fd will be stdout.
- if ((l_filename[0] == '-') && !l_filename[1]) {
- local_fd = 1; //file descriptor will become stdout.
- TT.c = 0;
- }
-
- //if continue, check for local file existance.
- if (TT.c) is_localfile_present(l_filename);
-
- //verify the remote file presence.
- if (get_ftp_response("RETR", r_filename) > FTPFILE_STATUSOKAY)
- close_stream("RETR");
-
- //if local fd is not stdout, create a file descriptor.
- if (local_fd == -1) {
- int flags = O_WRONLY;
-
- flags |= (TT.c)? O_APPEND : (O_CREAT | O_TRUNC);
- local_fd = xcreate((char *)l_filename, flags, 0666);
- }
- transfer_file(local_fd, remote_fd);
- xclose(remote_fd);
- xclose(local_fd);
- if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
- get_ftp_response("QUIT", NULL);
- toys.exitval = EXIT_SUCCESS;
-}
-
-static void put_file(char *r_filename, char *l_filename)
-{
- int local_fd = 0, remote_fd;
- unsigned cmd_status = 0;
-
- verify_pasv_mode(r_filename);
- remote_fd = connect_to_stream(); //Connect to data socket.
-
- //open the local file for transfer.
- if ((l_filename[0] != '-') || l_filename[1])
- local_fd = xcreate((char *)l_filename, O_RDONLY, 0666);
-
- //verify for the remote file status, Ok or Open: transfer File.
- cmd_status = get_ftp_response("STOR", r_filename);
- if ( (cmd_status == DATACONNECTION_OPENED) ||
- (cmd_status == FTPFILE_STATUSOKAY)) {
- transfer_file(local_fd, remote_fd);
- if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
- get_ftp_response("QUIT", NULL);
- toys.exitval = EXIT_SUCCESS;
- } else {
- toys.exitval = EXIT_FAILURE;
- close_stream("STOR");
- }
- xclose(remote_fd);
- xclose(local_fd);
-}
-
-void ftpget_main(void)
-{
- char **argv = toys.optargs; //host name + file name.
-
- TT.isget = toys.which->name[3] == 'g';
- TT.c = 1;
- //if user name is not specified.
- if (!(toys.optflags & FLAG_u) && (toys.optflags & FLAG_p))
- error_exit("Missing username:");
- //if user name and password is not specified in command line.
- if (!(toys.optflags & FLAG_u) && !(toys.optflags & FLAG_p))
- TT.username = TT.password ="anonymous";
-
- //if continue is not in the command line argument.
- if (TT.isget && !(toys.optflags & FLAG_c)) TT.c = 0;
-
- if (toys.optflags & FLAG_v) fprintf(stderr, "Connecting to %s\n", argv[0]);
- get_sockaddr(argv[0]);
-
- TT.sockfp = xfdopen(connect_to_stream(), "r+");
- send_requests();
-
- if (TT.isget) get_file(argv[1], argv[2] ? argv[2] : argv[1]);
- else put_file(argv[1], argv[2] ? argv[2] : argv[1]);
-}