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 10USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN)) 11USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN)) 12 13config 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 38config 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 48GLOBALS( 49 char *u, *p, *P; 50 51 int fd; 52 int datafd; 53 int filefd; 54) 55 56// we should get one line of data, but it may be in multiple chunks 57static 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 if (i <= 0) { 64 error_msg("xread2line line %d, len %d total %d i %d toybox buf %s\r\n", 65 __LINE__, len, total, i, toybuf); 66 break; 67 } 68 total += i; 69 if (buf[total-1] == '\n') break; 70 } 71 if (total>=len) { 72 error_msg("xread2line line %d, len %d total %d toybox buf %s\r\n", 73 __LINE__, len, total, toybuf); 74 if (TT.fd >= 0) { 75 xclose(TT.fd); 76 } 77 if (TT.datafd >= 0) { 78 xclose(TT.datafd); 79 } 80 if (TT.filefd >= 0) { 81 xclose(TT.filefd); 82 } 83 error_exit("overflow"); 84 } 85 while (total--) 86 if (buf[total]=='\r' || buf[total]=='\n') buf[total] = 0; 87 else break; 88 if (toys.optflags & FLAG_v) fprintf(stderr, "%s\n", toybuf); 89 90 return total+1; 91} 92 93static int ftp_line(char *cmd, char *arg, int must) 94{ 95 int rc = 0; 96 97 if (cmd) { 98 char *s = "%s %s\r\n"+3*(!arg); 99 if (toys.optflags & FLAG_v) fprintf(stderr, s, cmd, arg); 100 dprintf(TT.fd, s, cmd, arg); 101 } 102 if (must>=0) { 103 xread2line(TT.fd, toybuf, sizeof(toybuf)); 104 if (!sscanf(toybuf, "%d", &rc) || (must && rc != must)) { 105 error_msg("ftp_line line %d, must %d rc %d toybox buf %s\r\n", 106 __LINE__, must, rc, toybuf); 107 if (TT.fd >= 0) { 108 xclose(TT.fd); 109 } 110 if (TT.datafd >= 0) { 111 xclose(TT.datafd); 112 } 113 if (TT.filefd >= 0) { 114 xclose(TT.filefd); 115 } 116 error_exit_raw(toybuf); 117 } 118 } 119 120 return rc; 121} 122 123void ftpget_main(void) 124{ 125 struct sockaddr_in6 si6; 126 int rc, ii = 1, port = 0; 127 socklen_t sl = sizeof(si6); 128 char *s, *remote = toys.optargs[2]; 129 unsigned long long lenl = 0, lenr; 130 TT.fd = -1; 131 TT.datafd = -1; 132 TT.filefd = -1; 133 if (!(toys.optflags&(FLAG_v-1))) 134 toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s; 135 136 if (!TT.u) TT.u = "anonymous"; 137 if (!TT.P) TT.P = "ftpget@"; 138 if (!TT.p) TT.p = "21"; 139 if (!remote) remote = toys.optargs[1]; 140 141 // connect 142 TT.fd = xconnectany(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0, 143 AI_ADDRCONFIG)); 144 if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername"); 145 146 // Login 147 ftp_line(0, 0, 220); 148 rc = ftp_line("USER", TT.u, 0); 149 if (rc == 331) rc = ftp_line("PASS", TT.P, 0); 150 if (rc != 230) { 151 error_msg("ftpget_main line %d, PASS ret %d toybox buf %s\r\n", 152 __LINE__, rc, toybuf); 153 if (TT.fd >= 0) { 154 xclose(TT.fd); 155 } 156 error_exit_raw(toybuf); 157 } 158 159 if (toys.optflags & FLAG_m) { 160 if (toys.optc != 3) { 161 error_msg("ftpget_main line %d, toys.optflags 0x%x toys.optc %d toybox buf %s\r\n", 162 __LINE__, toys.optflags, toys.optc, toybuf); 163 if (TT.fd >= 0) { 164 xclose(TT.fd); 165 } 166 error_exit("-m FROM TO"); 167 } 168 ftp_line("RNFR", toys.optargs[1], 350); 169 ftp_line("RNTO", toys.optargs[2], 250); 170 } else if (toys.optflags & FLAG_M) ftp_line("MKD", toys.optargs[1], 257); 171 else if (toys.optflags & FLAG_d) ftp_line("DELE", toys.optargs[1], 250); 172 else if (toys.optflags & FLAG_D) ftp_line("RMD", toys.optargs[1], 250); 173 else { 174 int get = !(toys.optflags&FLAG_s), cnt = toys.optflags&FLAG_c; 175 char *cmd; 176 177 // Only do passive binary transfers 178 ftp_line("TYPE", "I", 0); 179 rc = ftp_line("PASV", 0, 0); 180 181 // PASV means the server opens a port you connect to instead of the server 182 // dialing back to the client. (Still insane, but less so.) So need port # 183 184 // PASV output is "227 PASV ok (x,x,x,x,p1,p2)" where x,x,x,x is the IP addr 185 // (must match the server you're talking to???) and port is (256*p1)+p2 186 s = 0; 187 if (rc==227) { 188 for (s = toybuf; (s = strchr(s, ',')); s++) { 189 int p1, got = 0; 190 191 sscanf(s, ",%u,%u)%n", &p1, &port, &got); 192 if (!got) continue; 193 port += 256*p1; 194 break; 195 } 196 } 197 if (!s || port<1 || port>65535) { 198 error_msg("ftpget_main line %d, port %d toybox buf %s\r\n", __LINE__, port, toybuf); 199 ftp_line("QUIT", 0, 0); 200 if (TT.fd >= 0) { 201 xclose(TT.fd); 202 } 203 error_exit_raw(toybuf); 204 } 205 si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6 206 port = xsocket(si6.sin6_family, SOCK_STREAM, 0); 207 TT.datafd = port; 208 xconnect(port, (void *)&si6, sizeof(si6)); 209 210 // RETR blocks until file data read from data port, so use SIZE to check 211 // if file exists before creating local copy 212 lenr = 0; 213 if (toys.optflags&(FLAG_s|FLAG_g)) { 214 if (ftp_line("SIZE", remote, 0) == 213) { 215 sscanf(toybuf, "%*u %llu", &lenr); 216 } else if (get) { 217 error_msg("ftpget_main line %d, port %d get %d toybox buf %s\r\n", __LINE__, port, get, toybuf); 218 ftp_line("QUIT", 0, 0); 219 if (TT.fd >= 0) { 220 xclose(TT.fd); 221 } 222 if (TT.datafd >= 0) { 223 xclose(TT.datafd); 224 } 225 error_exit("no %s", remote); 226 } 227 } 228 229 // Open file for reading or writing 230 if (toys.optflags & (FLAG_g|FLAG_s)) { 231 if (strcmp(toys.optargs[1], "-")) { 232 ii = xcreate(toys.optargs[1], 233 get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666); 234 TT.filefd = ii; 235 } 236 lenl = fdlength(ii); 237 } 238 if (get) { 239 cmd = "RETR"; 240 if (toys.optflags&FLAG_l) cmd = "LIST"; 241 if (toys.optflags&FLAG_L) cmd = "NLST"; 242 if (cnt) { 243 char buf[32]; 244 245 if (lenl>=lenr) goto done; 246 sprintf(buf, "%llu", lenl); 247 ftp_line("REST", buf, 350); 248 } else lenl = 0; 249 250 ftp_line(cmd, remote, -1); 251 lenl += xsendfile(port, ii); 252 ftp_line(0, 0, (toys.optflags&FLAG_g) ? 226 : 150); 253 close(port); 254 port = -1; 255 TT.datafd = -1; 256 } else if (toys.optflags & FLAG_s) { 257 cmd = "STOR"; 258 if (cnt && lenr) { 259 cmd = "APPE"; 260 xlseek(ii, lenl, SEEK_SET); 261 } else lenr = 0; 262 ftp_line(cmd, remote, 150); 263 lenr += xsendfile(ii, port); 264 close(port); 265 port = -1; 266 TT.datafd = -1; 267 ftp_line(0, 0, 226); 268 } 269 if (toys.optflags&(FLAG_g|FLAG_s)) { 270 if (lenl != lenr) { 271 error_msg("ftpget_main line %d, len local %d len remote %d toybox buf %s\r\n", __LINE__, lenl, lenr, toybuf); 272 ftp_line("QUIT", 0, ((port == -1) ? -1 : 0)); 273 if (TT.fd >= 0) { 274 xclose(TT.fd); 275 } 276 if (TT.datafd >= 0) { 277 xclose(TT.datafd); 278 } 279 if (TT.filefd >= 0) { 280 xclose(TT.filefd); 281 } 282 error_exit("short %lld/%lld", lenl, lenr); 283 } 284 } 285 } 286 287done: 288 ftp_line("QUIT", 0, ((port == -1) ? -1 : 0)); 289 if (ii!=1) xclose(ii); 290 if (port>=0) xclose(port); 291 if (TT.fd>=0) xclose(TT.fd); 292} 293