diff options
-rw-r--r-- | toys/net/ftpget.c | 196 | ||||
-rw-r--r-- | toys/pending/ftpget.c | 293 |
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]); -} |