toybox/toys/net/ftpget.c
<<
>>
Prefs
   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 *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
  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    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
  75static 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
  93void 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
 200done:
 201  if (CFG_TOYBOX_FREE) {
 202    if (ii!=1) xclose(ii);
 203    xclose(port);
 204    xclose(TT.fd);
 205  }
 206}
 207