• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* ftpget.c - Fetch file(s) from ftp server
2  *
3  * Copyright 2016 Rob Landley <rob@landley.net>
4  *
5  * No standard for the command, but see https://www.ietf.org/rfc/rfc959.txt
6  * TODO: local can be -
7  * TEST: -g -s (when local and remote exist) -gc, -sc
8  * zero length file
9 
10 USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN))
11 USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN))
12 
13 config FTPGET
14   bool "ftpget"
15   default y
16   help
17     usage: ftpget [-cvgslLmMdD] [-P PORT] [-p PASSWORD] [-u USER] HOST [LOCAL] REMOTE
18 
19     Talk to ftp server. By default get REMOTE file via passive anonymous
20     transfer, optionally saving under a LOCAL name. Can also send, list, etc.
21 
22     -c	Continue partial transfer
23     -p	Use PORT instead of "21"
24     -P	Use PASSWORD instead of "ftpget@"
25     -u	Use USER instead of "anonymous"
26     -v	Verbose
27 
28     Ways to interact with FTP server:
29     -d	Delete file
30     -D	Remove directory
31     -g	Get file (default)
32     -l	List directory
33     -L	List (filenames only)
34     -m	Move file on server from LOCAL to REMOTE
35     -M	mkdir
36     -s	Send file
37 
38 config FTPPUT
39   bool "ftpput"
40   default y
41   help
42     An ftpget that defaults to -s instead of -g
43 */
44 
45 #define FOR_ftpget
46 #include "toys.h"
47 
GLOBALS(char * user;char * port;char * password;int fd;)48 GLOBALS(
49   char *user;
50   char *port;
51   char *password;
52 
53   int fd;
54 )
55 
56 // we should get one line of data, but it may be in multiple chunks
57 static int xread2line(int fd, char *buf, int len)
58 {
59   int i, total = 0;
60 
61   len--;
62   while (total<len && (i = xread(fd, buf, len-total))) {
63     total += i;
64     if (buf[total-1] == '\n') break;
65   }
66   if (total>=len) error_exit("overflow");
67   while (total--)
68     if (buf[total]=='\r' || buf[total]=='\n') buf[total] = 0;
69     else break;
70   if (toys.optflags & FLAG_v) fprintf(stderr, "%s\n", toybuf);
71 
72   return total+1;
73 }
74 
ftp_line(char * cmd,char * arg,int must)75 static int ftp_line(char *cmd, char *arg, int must)
76 {
77   int rc = 0;
78 
79   if (cmd) {
80     char *s = "%s %s\r\n"+3*(!arg);
81     if (toys.optflags & FLAG_v) fprintf(stderr, s, cmd, arg);
82     dprintf(TT.fd, s, cmd, arg);
83   }
84   if (must>=0) {
85     xread2line(TT.fd, toybuf, sizeof(toybuf));
86     if (!sscanf(toybuf, "%d", &rc) || (must && rc != must))
87       error_exit_raw(toybuf);
88   }
89 
90   return rc;
91 }
92 
ftpget_main(void)93 void ftpget_main(void)
94 {
95   struct sockaddr_in6 si6;
96   int rc, ii = 1, port;
97   socklen_t sl = sizeof(si6);
98   char *s, *remote = toys.optargs[2];
99   unsigned long long lenl = 0, lenr;
100 
101   if (!(toys.optflags&(FLAG_v-1)))
102     toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s;
103 
104   if (!TT.user) TT.user = "anonymous";
105   if (!TT.password) TT.password = "ftpget@";
106   if (!TT.port) TT.port = "21";
107   if (!remote) remote = toys.optargs[1];
108 
109   // connect
110   TT.fd = xconnect(xgetaddrinfo(*toys.optargs, TT.port, 0, SOCK_STREAM, 0,
111     AI_ADDRCONFIG));
112   if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername");
113 
114   // Login
115   ftp_line(0, 0, 220);
116   rc = ftp_line("USER", TT.user, 0);
117   if (rc == 331) rc = ftp_line("PASS", TT.password, 0);
118   if (rc != 230) error_exit_raw(toybuf);
119 
120   if (toys.optflags & FLAG_m) {
121     if (toys.optc != 3) error_exit("-m FROM TO");
122     ftp_line("RNFR", toys.optargs[1], 350);
123     ftp_line("RNTO", toys.optargs[2], 250);
124   } else if (toys.optflags & FLAG_M) ftp_line("MKD", toys.optargs[1], 257);
125   else if (toys.optflags & FLAG_d) ftp_line("DELE", toys.optargs[1], 250);
126   else if (toys.optflags & FLAG_D) ftp_line("RMD", toys.optargs[1], 250);
127   else {
128     int get = !(toys.optflags&FLAG_s), cnt = toys.optflags&FLAG_c;
129     char *cmd;
130 
131     // Only do passive binary transfers
132     ftp_line("TYPE", "I", 0);
133     rc = ftp_line("PASV", 0, 0);
134 
135     // PASV means the server opens a port you connect to instead of the server
136     // dialing back to the client. (Still insane, but less so.) So need port #
137 
138     // PASV output is "227 PASV ok (x,x,x,x,p1,p2)" where x,x,x,x is the IP addr
139     // (must match the server you're talking to???) and port is (256*p1)+p2
140     s = 0;
141     if (rc==227) for (s = toybuf; (s = strchr(s, ',')); s++) {
142       int p1, got = 0;
143 
144       sscanf(s, ",%u,%u)%n", &p1, &port, &got);
145       if (!got) continue;
146       port += 256*p1;
147       break;
148     }
149     if (!s || port<1 || port>65535) error_exit_raw(toybuf);
150     si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6
151     port = xsocket(si6.sin6_family, SOCK_STREAM, 0);
152     if (connect(port, (void *)&si6, sizeof(si6))) perror_exit("connect");
153 
154     // RETR blocks until file data read from data port, so use SIZE to check
155     // if file exists before creating local copy
156     lenr = 0;
157     if (toys.optflags&(FLAG_s|FLAG_g)) {
158       if (ftp_line("SIZE", remote, 0) == 213)
159         sscanf(toybuf, "%*u %llu", &lenr);
160       else if (get) error_exit("no %s", remote);
161     }
162 
163     // Open file for reading or writing
164     if (toys.optflags & (FLAG_g|FLAG_s)) {
165       if (strcmp(toys.optargs[1], "-"))
166         ii = xcreate(toys.optargs[1],
167           get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666);
168       lenl = fdlength(ii);
169     }
170     if (get) {
171       cmd = "REST";
172       if (toys.optflags&FLAG_l) cmd = "LIST";
173       if (toys.optflags&FLAG_L) cmd = "NLST";
174       if (cnt) {
175         char buf[32];
176 
177         if (lenl>=lenr) goto done;
178         sprintf(buf, "%llu", lenl);
179         ftp_line("REST", buf, 350);
180       } else lenl = 0;
181 
182       ftp_line(cmd, remote, -1);
183       lenl += xsendfile(port, ii);
184       ftp_line(0, 0, (toys.optflags&FLAG_g) ? 226 : 150);
185     } else if (toys.optflags & FLAG_s) {
186       cmd = "STOR";
187       if (cnt && lenr) {
188         cmd = "APPE";
189         xlseek(ii, lenl, SEEK_SET);
190       } else lenr = 0;
191       ftp_line(cmd, remote, 150);
192       lenr += xsendfile(ii, port);
193       close(port);
194     }
195     if (toys.optflags&(FLAG_g|FLAG_s))
196       if (lenl != lenr) error_exit("short %lld/%lld", lenl, lenr);
197   }
198   ftp_line("QUIT", 0, 0);
199 
200 done:
201   if (CFG_TOYBOX_FREE) {
202     if (ii!=1) xclose(ii);
203     xclose(port);
204     xclose(TT.fd);
205   }
206 }
207