sftp server, file browser

configuration
---
to configure sftp server:
* add `[sftp]` section to `default.ini`
* add `root` option to `[sftp]` section, pointing at root folder for files

to enable uploads:
* create `__uploads__` directory in sftp root

to enable file browser:
* add menu option for `gosub('files')`

things to add
---
* `FILE_ID.DIZ` caching, but that seems like premature optimization at this point
* ability to enter manual descriptions for files (will depend on same storage mechanism that holds `FILE_ID.DIZ` cache)
* virtual `FILES.BBS` generation so that sftp clients can view descriptions of a folder's files without being logged in via terminal server
* integration with x/84's IPC logging mechanism
* better detection of file download completion (currently, file is removed from user's tagged list as soon as it is opened via sftp, rather than when it is finished)
* tracking uploads/downloads (i.e., who uploaded what, how many times has something been downloaded, who downloaded what, etc.)
could have sworn we already wrote this, but i'm confusing with 'timeago' i guess.

anyway, this "human readable" should be separate format, and would do better to use pow(). Here is an awk script that "upsamples" or "downsamples" any human-readable format to the designated format. Notice it uses 1024 all the way -- not sure what is up with these 1024000, 1024*400, etc. variables.
```
~
#!/bin/ksh
# dingo@efnet
nawk -v scale=$1 -v precision=$2 '
BEGIN {
  # f_bytes and b_bytes, forwards and backwards for up and down-scaling
  f_bytes="B K M G T P E Z"
  b_bytes="Z E P T G M K B"

  usage=0 # flipped on error condition, exit with usage
  if ((scale == "-h") || (scale == "--help"))
    usage=1
  split(f_bytes, b, " ");
  split(scale, chk_scale, "");
  badscale=1;
  if (!length(chk_scale))
    { scale="B"; badscale=0; } # default output is bytes
  else
    for(i=1;i<length(b);i++)
    {
      if (toupper(chk_scale[1]) == b[i])
        { badscale=0; scale=b[i]; break; printf ("scale now %s\n", scale);}
    }
  if (badscale) {
    printf("Bad scale: %s, must one of: %s\n", scale, f_bytes);
    usage=1
  }

  # still, no size checking etc., but we make sure its an integer!
  split(precision, chk_precision, "");
  if (!length(chk_precision))
    precision=0;
  else
    for(i=1;i<length(chk_precision);i++) {
      if (match(chk_precision[i],"[0-9]") == 0)
        { printf("Bad precision: %s, must be integer\n", precision); usage=1; break }
    }

  if (usage)
  {
    printf("\n usage: %s [scale] [precision]\n\n", argv[0]);
    printf("program recieves numeric input from stdin as bytesize\n");
    printf("of scale in each input line. If scale is specified, input\n");
    printf("is up or down-scaled, otherwise input is always downscaled to bytes.\n");
    printf("scales are any of: %s\n\n", f_bytes);
    printf("upper or lowercase.\n");
    printf("if input is upscaled, meaning precision is lost, then the\n");
    printf("second option argument is used to determine the float\n");
    printf("decimal precision used. default is 0, meaning no decimals.\n\n");
    exit(1);
  }
} {
  upscaled=0;
  split(f_bytes, b, " ")
  p=match($0,"[ZzEePpTtGgMmKkBb]");
  if (!p)
  {
    t="B"; # unspecified sizes presumed to be 'B'
    d=$1
  } else {
    t=substr($0, p, 1);
    d=substr($0, 0, p-1);
  }

  for(i=1;i<length(b);i++)
    if (toupper(t)==b[i])
    {
      upscaled=1;
      d/=1024;
      t=b[i+1];
      if (toupper(t) == toupper(scale))
        break;
    }

  if (toupper(t) != toupper(scale)) {
    split(b_bytes, b, " ");
    for(i=1;i<length(b);i++)
      if (toupper(t)==b[i])
      {
        d*=1024;
        t=b[i+1];
        if (toupper(t) == toupper(scale))
          break;
      }
  }

  if (upscaled)
    printf ("%0.*f%s\n", precision, d, scale);
  else
    printf ("%i%s\n", d, scale);
}'
```