First stab at an SFTP client. Currently a Unixland testing app, not
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 23 Feb 2001 18:21:44 +0000 (18:21 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 23 Feb 2001 18:21:44 +0000 (18:21 +0000)
integrated into PuTTY.

git-svn-id: svn://svn.tartarus.org/sgt/putty@938 cda61777-01e9-0310-a592-d414129be87e

psftp.c [new file with mode: 0644]
sftp.c [new file with mode: 0644]
sftp.h [new file with mode: 0644]

diff --git a/psftp.c b/psftp.c
new file mode 100644 (file)
index 0000000..4445b8b
--- /dev/null
+++ b/psftp.c
@@ -0,0 +1,510 @@
+/*
+ * psftp.c: front end for PSFTP.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include "sftp.h"
+#include "int64.h"
+
+#define smalloc malloc
+#define srealloc realloc
+#define sfree free
+
+/* ----------------------------------------------------------------------
+ * String handling routines.
+ */
+
+char *dupstr(char *s) {
+    int len = strlen(s);
+    char *p = smalloc(len+1);
+    strcpy(p, s);
+    return p;
+}
+
+/* ----------------------------------------------------------------------
+ * sftp client state.
+ */
+
+char *pwd, *homedir;
+
+/* ----------------------------------------------------------------------
+ * Higher-level helper functions used in commands.
+ */
+
+/*
+ * Canonify a pathname starting from the pwd.
+ */
+char *canonify(char *name) {
+    if (name[0] == '/')
+       return fxp_realpath(name, NULL);
+    else
+       return fxp_realpath(pwd, name);
+}
+
+/* ----------------------------------------------------------------------
+ * Actual sftp commands.
+ */
+struct sftp_command {
+    char **words;
+    int nwords, wordssize;
+    int (*obey)(struct sftp_command *);/* returns <0 to quit */
+};
+
+int sftp_cmd_null(struct sftp_command *cmd) {
+    return 0;
+}
+
+int sftp_cmd_unknown(struct sftp_command *cmd) {
+    printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
+    return 0;
+}
+
+int sftp_cmd_quit(struct sftp_command *cmd) {
+    return -1;
+}
+
+/*
+ * List a directory. If no arguments are given, list pwd; otherwise
+ * list the directory given in words[1].
+ */
+static int sftp_ls_compare(const void *av, const void *bv) {
+    const struct fxp_name *a = (const struct fxp_name *)av;
+    const struct fxp_name *b = (const struct fxp_name *)bv;
+    return strcmp(a->filename, b->filename);
+}
+int sftp_cmd_ls(struct sftp_command *cmd) {
+    struct fxp_handle *dirh;
+    struct fxp_names *names;
+    struct fxp_name *ournames;
+    int nnames, namesize;
+    char *dir, *cdir;
+    int i;
+
+    if (cmd->nwords < 2)
+       dir = ".";
+    else
+       dir = cmd->words[1];
+
+    cdir = canonify(dir);
+    if (!cdir) {
+       printf("%s: %s\n", dir, fxp_error());
+       return 0;
+    }
+
+    printf("Listing directory %s\n", cdir);
+
+    dirh = fxp_opendir(cdir);
+    if (dirh == NULL) {
+       printf("Unable to open %s: %s\n", dir, fxp_error());
+    } else {
+       nnames = namesize = 0;
+       ournames = NULL;
+
+       while (1) {
+
+           names = fxp_readdir(dirh);
+           if (names == NULL) {
+               if (fxp_error_type() == SSH_FX_EOF)
+                   break;
+               printf("Reading directory %s: %s\n", dir, fxp_error());
+               break;
+           }
+           if (names->nnames == 0) {
+               fxp_free_names(names);
+               break;
+           }
+
+           if (nnames + names->nnames >= namesize) {
+               namesize += names->nnames + 128;
+               ournames = srealloc(ournames, namesize * sizeof(*ournames));
+           }
+
+           for (i = 0; i < names->nnames; i++)
+               ournames[nnames++] = names->names[i];
+
+           names->nnames = 0;         /* prevent free_names */
+           fxp_free_names(names);
+       }
+       fxp_close(dirh);
+
+       /*
+        * Now we have our filenames. Sort them by actual file
+        * name, and then output the longname parts.
+        */
+       qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
+
+       /*
+        * And print them.
+        */
+       for (i = 0; i < nnames; i++)
+           printf("%s\n", ournames[i].longname);
+    }
+
+    sfree(cdir);
+
+    return 0;
+}
+
+/*
+ * Change directories. We do this by canonifying the new name, then
+ * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
+ */
+int sftp_cmd_cd(struct sftp_command *cmd) {
+    struct fxp_handle *dirh;
+    char *dir;
+
+    if (cmd->nwords < 2)
+       dir = fxp_realpath(".", NULL);
+    else
+       dir = canonify(cmd->words[1]);
+
+    if (!dir) {
+       printf("%s: %s\n", dir, fxp_error());
+       return 0;
+    }
+
+    dirh = fxp_opendir(dir);
+    if (!dirh) {
+       printf("Directory %s: %s\n", dir, fxp_error());
+       sfree(dir);
+       return 0;
+    }
+
+    fxp_close(dirh);
+
+    sfree(pwd);
+    pwd = dir;
+    printf("Remote directory is now %s\n", pwd);
+
+    return 0;
+}
+
+/*
+ * Get a file and save it at the local end.
+ */
+int sftp_cmd_get(struct sftp_command *cmd) {
+    struct fxp_handle *fh;
+    char *fname, *outfname;
+    uint64 offset;
+    FILE *fp;
+
+    if (cmd->nwords < 2) {
+       printf("get: expects a filename\n");
+       return 0;
+    }
+
+    fname = canonify(cmd->words[1]);
+    if (!fname) {
+       printf("%s: %s\n", cmd->words[1], fxp_error());
+       return 0;
+    }
+    outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
+
+    fh = fxp_open(fname, SSH_FXF_READ);
+    if (!fh) {
+       printf("%s: %s\n", fname, fxp_error());
+       sfree(fname);
+       return 0;
+    }
+    fp = fopen(outfname, "wb");
+    if (!fp) {
+       printf("local: unable to open %s\n", outfname);
+        fxp_close(fh);
+       sfree(fname);
+       return 0;
+    }
+
+    printf("remote:%s => local:%s\n", fname, outfname);
+
+    offset = uint64_make(0,0);
+
+    /*
+     * FIXME: we can use FXP_FSTAT here to get the file size, and
+     * thus put up a progress bar.
+     */
+    while (1) {
+       char buffer[4096];
+       int len;
+       int wpos, wlen;
+
+       len = fxp_read(fh, buffer, offset, sizeof(buffer));
+       if ((len == -1 && fxp_error_type() == SSH_FX_EOF) ||
+           len == 0)
+           break;
+       if (len == -1) {
+           printf("error while reading: %s\n", fxp_error());
+           break;
+       }
+       
+       wpos = 0;
+       while (wpos < len) {
+           wlen = fwrite(buffer, 1, len-wpos, fp);
+           if (wlen <= 0) {
+               printf("error while writing local file\n");
+               break;
+           }
+           wpos += wlen;
+       }
+       if (wpos < len)                /* we had an error */
+           break;
+       offset = uint64_add32(offset, len);
+    }
+
+    fclose(fp);
+    fxp_close(fh);
+    sfree(fname);
+
+    return 0;
+}
+
+/*
+ * Send a file and store it at the remote end.
+ */
+int sftp_cmd_put(struct sftp_command *cmd) {
+    struct fxp_handle *fh;
+    char *fname, *origoutfname, *outfname;
+    uint64 offset;
+    FILE *fp;
+
+    if (cmd->nwords < 2) {
+       printf("put: expects a filename\n");
+       return 0;
+    }
+
+    fname = cmd->words[1];
+    origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
+    outfname = canonify(origoutfname);
+~|~    if (!outfname) {
+       printf("%s: %s\n", origoutfname, fxp_error());
+       return 0;
+    }
+
+    fp = fopen(fname, "rb");
+    if (!fp) {
+       printf("local: unable to open %s\n", fname);
+        fxp_close(fh);
+       sfree(outfname);
+       return 0;
+    }
+    fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
+    if (!fh) {
+       printf("%s: %s\n", outfname, fxp_error());
+       sfree(outfname);
+       return 0;
+    }
+
+    printf("local:%s => remote:%s\n", fname, outfname);
+
+    offset = uint64_make(0,0);
+
+    /*
+     * FIXME: we can use FXP_FSTAT here to get the file size, and
+     * thus put up a progress bar.
+     */
+    while (1) {
+       char buffer[4096];
+       int len;
+
+       len = fread(buffer, 1, len, fp);
+       if (len == -1) {
+           printf("error while reading local file\n");
+           break;
+       } else if (len == 0) {
+           break;
+       }
+       if (!fxp_write(fh, buffer, offset, sizeof(buffer))) {
+           printf("error while writing: %s\n", fxp_error());
+           break;
+       }
+       offset = uint64_add32(offset, len);
+    }
+
+    fxp_close(fh);
+    fclose(fp);
+    sfree(outfname);
+
+    return 0;
+}
+
+static struct sftp_cmd_lookup {
+    char *name;
+    int (*obey)(struct sftp_command *);
+} sftp_lookup[] = {
+    /*
+     * List of sftp commands. This is binary-searched so it MUST be
+     * in ASCII order.
+     */
+    {"bye", sftp_cmd_quit},
+    {"cd", sftp_cmd_cd},
+    {"exit", sftp_cmd_quit},
+    {"get", sftp_cmd_get},
+    {"ls", sftp_cmd_ls},
+    {"put", sftp_cmd_put},
+    {"quit", sftp_cmd_quit},
+};
+
+/* ----------------------------------------------------------------------
+ * Command line reading and parsing.
+ */
+struct sftp_command *sftp_getcmd(void) {
+    char *line;
+    int linelen, linesize;
+    struct sftp_command *cmd;
+    char *p, *q, *r;
+    int quoting;
+
+    printf("psftp> ");
+    fflush(stdout);
+
+    cmd = smalloc(sizeof(struct sftp_command));
+    cmd->words = NULL;
+    cmd->nwords = 0;
+    cmd->wordssize = 0;
+
+    line = NULL;
+    linesize = linelen = 0;
+    while (1) {
+       int len;
+       char *ret;
+
+       linesize += 512;
+       line = srealloc(line, linesize);
+       ret = fgets(line+linelen, linesize-linelen, stdin);
+
+       if (!ret || (linelen == 0 && line[0] == '\0')) {
+           cmd->obey = sftp_cmd_quit;
+           printf("quit\n");
+           return cmd;                /* eof */
+       }
+       len = linelen + strlen(line+linelen);
+       linelen += len;
+       if (line[linelen-1] == '\n') {
+           linelen--;
+           line[linelen] = '\0';
+           break;
+       }
+    }
+
+    /*
+     * Parse the command line into words. The syntax is:
+     *  - double quotes are removed, but cause spaces within to be
+     *    treated as non-separating.
+     *  - a double-doublequote pair is a literal double quote, inside
+     *    _or_ outside quotes. Like this:
+     * 
+     *      firstword "second word" "this has ""quotes"" in" sodoes""this""
+     * 
+     * becomes
+     * 
+     *      >firstword<
+     *      >second word<
+     *      >this has "quotes" in<
+     *      >sodoes"this"<
+     */
+    p = line;
+    while (*p) {
+       /* skip whitespace */
+       while (*p && (*p == ' ' || *p == '\t')) p++;
+       /* mark start of word */
+       q = r = p;                     /* q sits at start, r writes word */
+       quoting = 0;
+       while (*p) {
+           if (!quoting && (*p == ' ' || *p == '\t'))
+               break;                 /* reached end of word */
+           else if (*p == '"' && p[1] == '"')
+               p+=2, *r++ = '"';      /* a literal quote */
+           else if (*p == '"')
+               p++, quoting = !quoting;
+           else
+               *r++ = *p++;
+       }
+       if (*p) p++;                           /* skip over the whitespace */
+       *r = '\0';
+       if (cmd->nwords >= cmd->wordssize) {
+           cmd->wordssize = cmd->nwords + 16;
+           cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
+       }
+       cmd->words[cmd->nwords++] = q;
+    }
+
+    /*
+     * Now parse the first word and assign a function.
+     */
+
+    if (cmd->nwords == 0)
+       cmd->obey = sftp_cmd_null;
+    else {
+       int i, j, k, cmp;
+
+       cmd->obey = sftp_cmd_unknown;
+
+       i = -1;
+       j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
+       while (j - i > 1) {
+           k = (j + i) / 2;
+           cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
+           if (cmp < 0)
+               j = k;
+           else if (cmp > 0)
+               i = k;
+           else {
+               cmd->obey = sftp_lookup[k].obey;
+               break;
+           }
+       }
+    }
+
+    return cmd;
+}
+
+void do_sftp(void) {
+    /*
+     * Do protocol initialisation. 
+     */
+    if (!fxp_init()) {
+       fprintf(stderr,
+               "Fatal: unable to initialise SFTP: %s\n",
+               fxp_error());
+    }
+
+    /*
+     * Find out where our home directory is.
+     */
+    homedir = fxp_realpath(".", NULL);
+    if (!homedir) {
+       fprintf(stderr,
+               "Warning: failed to resolve home directory: %s\n",
+               fxp_error());
+       homedir = dupstr(".");
+    } else {
+       printf("Remote working directory is %s\n", homedir);
+    }
+    pwd = dupstr(homedir);
+
+    /* ------------------------------------------------------------------
+     * Now we're ready to do Real Stuff.
+     */
+    while (1) {
+       struct sftp_command *cmd;
+       cmd = sftp_getcmd();
+       if (!cmd)
+           break;
+       if (cmd->obey(cmd) < 0)
+           break;
+    }
+
+    /* ------------------------------------------------------------------
+     * We've received an exit command. Tidy up and leave.
+     */
+    io_finish();
+}
+
+int main(void) {
+    io_init();
+    do_sftp();
+    return 0;
+}
diff --git a/sftp.c b/sftp.c
new file mode 100644 (file)
index 0000000..6b519b6
--- /dev/null
+++ b/sftp.c
@@ -0,0 +1,605 @@
+/*
+ * sftp.c: SFTP generic client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include "int64.h"
+#include "sftp.h"
+
+#define smalloc malloc
+#define srealloc realloc
+#define sfree free
+
+#define GET_32BIT(cp) \
+    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+    ((unsigned long)(unsigned char)(cp)[3]))
+
+#define PUT_32BIT(cp, value) { \
+    (cp)[0] = (unsigned char)((value) >> 24); \
+    (cp)[1] = (unsigned char)((value) >> 16); \
+    (cp)[2] = (unsigned char)((value) >> 8); \
+    (cp)[3] = (unsigned char)(value); }
+
+struct sftp_packet {
+    char *data;
+    int length, maxlen;
+    int savedpos;
+    int type;
+};
+
+/* ----------------------------------------------------------------------
+ * SFTP packet construction functions.
+ */
+static void sftp_pkt_ensure(struct sftp_packet *pkt, int length) {
+    if (pkt->maxlen < length) {
+        pkt->maxlen = length + 256;
+       pkt->data = srealloc(pkt->data, pkt->maxlen);
+    }
+}
+static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len) {
+    pkt->length += len;
+    sftp_pkt_ensure(pkt, pkt->length);
+    memcpy(pkt->data+pkt->length-len, data, len);
+}
+static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) {
+    sftp_pkt_adddata(pkt, &byte, 1);
+}
+static struct sftp_packet *sftp_pkt_init(int pkt_type) {
+    struct sftp_packet *pkt;
+    pkt = smalloc(sizeof(struct sftp_packet));
+    pkt->data = NULL;
+    pkt->savedpos = -1;
+    pkt->length = 0;
+    pkt->maxlen = 0;
+    sftp_pkt_addbyte(pkt, (unsigned char)pkt_type);
+    return pkt;
+}
+static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) {
+    sftp_pkt_adddata(pkt, &value, 1);
+}
+static void sftp_pkt_adduint32(struct sftp_packet *pkt, unsigned long value) {
+    unsigned char x[4];
+    PUT_32BIT(x, value);
+    sftp_pkt_adddata(pkt, x, 4);
+}
+static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) {
+    unsigned char x[8];
+    PUT_32BIT(x, value.hi);
+    PUT_32BIT(x+4, value.lo);
+    sftp_pkt_adddata(pkt, x, 8);
+}
+static void sftp_pkt_addstring_start(struct sftp_packet *pkt) {
+    sftp_pkt_adduint32(pkt, 0);
+    pkt->savedpos = pkt->length;
+}
+static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data) {
+    sftp_pkt_adddata(pkt, data, strlen(data));
+    PUT_32BIT(pkt->data + pkt->savedpos - 4,
+              pkt->length - pkt->savedpos);
+}
+static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
+                                   char *data, int len) {
+    sftp_pkt_adddata(pkt, data, len);
+    PUT_32BIT(pkt->data + pkt->savedpos - 4,
+              pkt->length - pkt->savedpos);
+}
+static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data) {
+    sftp_pkt_addstring_start(pkt);
+    sftp_pkt_addstring_str(pkt, data);
+}
+
+/* ----------------------------------------------------------------------
+ * SFTP packet decode functions.
+ */
+
+static unsigned char sftp_pkt_getbyte(struct sftp_packet *pkt) {
+    unsigned long value;
+    if (pkt->length - pkt->savedpos < 1)
+        return 0;                      /* arrgh, no way to decline (FIXME?) */
+    value = (unsigned char) pkt->data[pkt->savedpos];
+    pkt->savedpos++;
+    return value;
+}
+static unsigned long sftp_pkt_getuint32(struct sftp_packet *pkt) {
+    unsigned long value;
+    if (pkt->length - pkt->savedpos < 4)
+        return 0;                      /* arrgh, no way to decline (FIXME?) */
+    value = GET_32BIT(pkt->data+pkt->savedpos);
+    pkt->savedpos += 4;
+    return value;
+}
+static void sftp_pkt_getstring(struct sftp_packet *pkt,
+                              char **p, int *length) {
+    *p = NULL;
+    if (pkt->length - pkt->savedpos < 4)
+        return;
+    *length = GET_32BIT(pkt->data+pkt->savedpos);
+    pkt->savedpos += 4;
+    if (pkt->length - pkt->savedpos < *length)
+        return;
+    *p = pkt->data+pkt->savedpos;
+    pkt->savedpos += *length;
+}
+static struct fxp_attrs sftp_pkt_getattrs(struct sftp_packet *pkt) {
+    struct fxp_attrs ret;
+    ret.flags = sftp_pkt_getuint32(pkt);
+    if (ret.flags & SSH_FILEXFER_ATTR_SIZE) {
+       unsigned long hi, lo;
+       hi = sftp_pkt_getuint32(pkt);
+       lo = sftp_pkt_getuint32(pkt);
+       ret.size = uint64_make(hi, lo);
+    }
+    if (ret.flags & SSH_FILEXFER_ATTR_UIDGID) {
+       ret.uid = sftp_pkt_getuint32(pkt);
+       ret.gid = sftp_pkt_getuint32(pkt);
+    }
+    if (ret.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+       ret.permissions = sftp_pkt_getuint32(pkt);
+    }
+    if (ret.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+       ret.atime = sftp_pkt_getuint32(pkt);
+       ret.mtime = sftp_pkt_getuint32(pkt);
+    }
+    if (ret.flags & SSH_FILEXFER_ATTR_EXTENDED) {
+       int count;
+       count = sftp_pkt_getuint32(pkt);
+       while (count--) {
+           char *str;
+           int len;
+           /*
+            * We should try to analyse these, if we ever find one
+            * we recognise.
+            */
+           sftp_pkt_getstring(pkt, &str, &len);
+           sftp_pkt_getstring(pkt, &str, &len);
+       }
+    }
+    return ret;
+}
+static void sftp_pkt_free(struct sftp_packet *pkt) {
+    if (pkt->data) sfree(pkt->data);
+    sfree(pkt);
+}
+
+/* ----------------------------------------------------------------------
+ * Send and receive packet functions. FIXME: change for PuTTY.
+ */
+int tossh, fromssh;
+int io_init(void) {
+    int to[2], from[2];
+    int pid;
+
+    assert(pipe(to) == 0);
+    assert(pipe(from) == 0);
+    pid = fork();
+    assert(pid >= 0);
+    if (pid == 0) {
+       /* We are child. Dup one end of each pipe to our std[io],
+        * close other end, exec. */
+       close(0); dup2(to[0], 0); close(to[1]);
+       close(1); dup2(from[1], 1); close(from[0]);
+       execl("/home/simon/src/openssh/openssh_cvs/prefix/bin/ssh", "ssh", "-2", "simon@localhost", "-s", "sftp", NULL);
+       assert(0);                     /* bomb out if not */
+    } else {
+       /* We are parent. Close wrong end of each pipe, assign to glob vars. */
+       close(to[0]); tossh = to[1];
+       close(from[1]); fromssh = from[0];
+    }
+}
+int io_finish(void) {
+    int pid, status;
+    close(tossh);
+    close(fromssh);
+    pid = wait(&status);
+}
+int sftp_send(struct sftp_packet *pkt) {
+    char x[4];
+    PUT_32BIT(x, pkt->length);
+    assert(4 == write(tossh, x, 4));
+    assert(pkt->length = write(tossh, pkt->data, pkt->length));
+    sftp_pkt_free(pkt);
+}
+struct sftp_packet *sftp_recv(void) {
+    struct sftp_packet *pkt;
+    char x[4];
+    int p, ret;
+
+    for (p = 0; p < 4 ;) {
+       ret = read(fromssh, x+p, 4-p);
+       assert(ret >= 0);
+       if (ret == 0)
+           return NULL;
+       p += ret;
+    }
+
+    pkt = smalloc(sizeof(struct sftp_packet));
+    pkt->savedpos = 0;
+    pkt->length = pkt->maxlen = GET_32BIT(x);
+    pkt->data = smalloc(pkt->length);
+
+    for (p = 0; p < pkt->length ;) {
+       ret = read(fromssh, pkt->data+p, pkt->length-p);
+       assert(ret >= 0);
+       if (ret == 0) {
+           sftp_pkt_free(pkt);
+           return NULL;
+       }
+       p += ret;
+    }
+
+    pkt->type = sftp_pkt_getbyte(pkt);
+
+    return pkt;
+}
+
+/* ----------------------------------------------------------------------
+ * String handling routines.
+ */
+
+static char *mkstr(char *s, int len) {
+    char *p = smalloc(len+1);
+    memcpy(p, s, len);
+    p[len] = '\0';
+    return p;
+}
+
+/* ----------------------------------------------------------------------
+ * SFTP primitives.
+ */
+
+static const char *fxp_error_message;
+static int fxp_errtype;
+
+/*
+ * Deal with (and free) an FXP_STATUS packet. Return 1 if
+ * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
+ * Also place the status into fxp_errtype.
+ */
+static int fxp_got_status(struct sftp_packet *pktin) {
+    static const char *const messages[] = {
+       /* SSH_FX_OK. The only time we will display a _message_ for this
+        * is if we were expecting something other than FXP_STATUS on
+        * success, so this is actually an error message! */
+       "unexpected OK response",
+       "end of file",
+       "no such file or directory",
+       "permission denied",
+       "failure",
+       "bad message",
+       "no connection",
+       "connection lost",
+       "operation unsupported",
+    };
+
+    if (pktin->type != SSH_FXP_STATUS) {
+       fxp_error_message = "expected FXP_STATUS packet";
+       fxp_errtype = -1;
+    } else {
+       fxp_errtype = sftp_pkt_getuint32(pktin);
+       if (fxp_errtype < 0 ||
+           fxp_errtype >= sizeof(messages)/sizeof(*messages))
+           fxp_error_message = "unknown error code";
+       else
+           fxp_error_message = messages[fxp_errtype];
+    }
+
+    if (fxp_errtype == SSH_FX_OK)
+       return 1;
+    else if (fxp_errtype == SSH_FX_EOF)
+       return 0;
+    else
+       return -1;
+}
+
+static int fxp_internal_error(char *msg) {
+    fxp_error_message = msg;
+    fxp_errtype = -1;
+}
+
+const char *fxp_error(void) {
+    return fxp_error_message;
+}
+
+int fxp_error_type(void) {
+    return fxp_errtype;
+}
+
+/*
+ * Perform exchange of init/version packets. Return 0 on failure.
+ */
+int fxp_init(void) {
+    struct sftp_packet *pktout, *pktin;
+    int remotever;
+
+    pktout = sftp_pkt_init(SSH_FXP_INIT);
+    sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
+    sftp_send(pktout);
+
+    pktin = sftp_recv();
+    if (pktin->type != SSH_FXP_VERSION) {
+       fxp_internal_error("did not receive FXP_VERSION");
+       return 0;
+    }
+    remotever = sftp_pkt_getuint32(pktin);
+    if (remotever > SFTP_PROTO_VERSION) {
+       fxp_internal_error("remote protocol is more advanced than we support");
+       return 0;
+    }
+    /*
+     * In principle, this packet might also contain extension-
+     * string pairs. We should work through them and look for any
+     * we recognise. In practice we don't currently do so because
+     * we know we don't recognise _any_.
+     */
+    sftp_pkt_free(pktin);
+
+    return 1;
+}
+
+/*
+ * Canonify a pathname. Concatenate the two given path elements
+ * with a separating slash, unless the second is NULL.
+ */
+char *fxp_realpath(char *path, char *path2) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_REALPATH);
+    sftp_pkt_adduint32(pktout, 0x123); /* request id */
+    sftp_pkt_addstring_start(pktout);
+    sftp_pkt_addstring_str(pktout, path);
+    if (path2) {
+       sftp_pkt_addstring_str(pktout, "/");
+       sftp_pkt_addstring_str(pktout, path2);
+    }
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0x123) {
+       fxp_internal_error("request ID mismatch\n");
+       return NULL;
+    }
+    if (pktin->type == SSH_FXP_NAME) {
+       int count;
+       char *path;
+       int len;
+
+       count = sftp_pkt_getuint32(pktin);
+       if (count != 1) {
+           fxp_internal_error("REALPATH returned name count != 1\n");
+           return NULL;
+       }
+       sftp_pkt_getstring(pktin, &path, &len);
+       if (!path) {
+           fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
+           return NULL;
+       }
+       path = mkstr(path, len);
+       sftp_pkt_free(pktin);
+       return path;
+    } else {
+       fxp_got_status(pktin);
+       return NULL;
+    }
+}
+
+/*
+ * Open a file.
+ */
+struct fxp_handle *fxp_open(char *path, int type) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_OPEN);
+    sftp_pkt_adduint32(pktout, 0x567); /* request id */
+    sftp_pkt_addstring(pktout, path);
+    sftp_pkt_adduint32(pktout, type);
+    sftp_pkt_adduint32(pktout, 0);     /* (FIXME) empty ATTRS structure */
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0x567) {
+       fxp_internal_error("request ID mismatch\n");
+       return NULL;
+    }
+    if (pktin->type == SSH_FXP_HANDLE) {
+       int count;
+       char *hstring;
+       struct fxp_handle *handle;
+       int len;
+
+       sftp_pkt_getstring(pktin, &hstring, &len);
+       if (!hstring) {
+           fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
+           return NULL;
+       }
+       handle = smalloc(sizeof(struct fxp_handle));
+       handle->hstring = mkstr(hstring, len);
+       sftp_pkt_free(pktin);
+       return handle;
+    } else {
+       fxp_got_status(pktin);
+       return NULL;
+    }
+}
+
+/*
+ * Open a directory.
+ */
+struct fxp_handle *fxp_opendir(char *path) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
+    sftp_pkt_adduint32(pktout, 0x456); /* request id */
+    sftp_pkt_addstring(pktout, path);
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0x456) {
+       fxp_internal_error("request ID mismatch\n");
+       return NULL;
+    }
+    if (pktin->type == SSH_FXP_HANDLE) {
+       int count;
+       char *hstring;
+       struct fxp_handle *handle;
+       int len;
+
+       sftp_pkt_getstring(pktin, &hstring, &len);
+       if (!hstring) {
+           fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
+           return NULL;
+       }
+       handle = smalloc(sizeof(struct fxp_handle));
+       handle->hstring = mkstr(hstring, len);
+       sftp_pkt_free(pktin);
+       return handle;
+    } else {
+       fxp_got_status(pktin);
+       return NULL;
+    }
+}
+
+/*
+ * Close a file/dir.
+ */
+void fxp_close(struct fxp_handle *handle) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_CLOSE);
+    sftp_pkt_adduint32(pktout, 0x789); /* request id */
+    sftp_pkt_addstring(pktout, handle->hstring);
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0x789) {
+       fxp_internal_error("request ID mismatch\n");
+       return;
+    }
+    fxp_got_status(pktin);
+    sfree(handle->hstring);
+    sfree(handle);
+}
+
+/*
+ * Read from a file. Returns the number of bytes read, or -1 on an
+ * error, or possibly 0 if EOF. (I'm not entirely sure whether it
+ * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
+ * error indicator. It might even depend on the SFTP server.)
+ */
+int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_READ);
+    sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
+    sftp_pkt_addstring(pktout, handle->hstring);
+    sftp_pkt_adduint64(pktout, offset);
+    sftp_pkt_adduint32(pktout, len);
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0xBCD) {
+       fxp_internal_error("request ID mismatch");
+       return;
+    }
+    if (pktin->type == SSH_FXP_DATA) {
+       char *str;
+       int rlen;
+
+       sftp_pkt_getstring(pktin, &str, &rlen);
+
+       if (rlen > len || rlen < 0) {
+           fxp_internal_error("READ returned more bytes than requested");
+           return -1;
+       }
+
+       memcpy(buffer, str, rlen);
+       sfree(pktin);
+       return rlen;
+    } else {
+       fxp_got_status(pktin);
+       return -1;
+    }
+}
+
+/*
+ * Read from a directory.
+ */
+struct fxp_names *fxp_readdir(struct fxp_handle *handle) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_READDIR);
+    sftp_pkt_adduint32(pktout, 0xABC); /* request id */
+    sftp_pkt_addstring(pktout, handle->hstring);
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    if (id != 0xABC) {
+       fxp_internal_error("request ID mismatch\n");
+       return;
+    }
+    if (pktin->type == SSH_FXP_NAME) {
+       struct fxp_names *ret;
+       int i;
+       ret = smalloc(sizeof(struct fxp_names));
+       ret->nnames = sftp_pkt_getuint32(pktin);
+       ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
+       for (i = 0; i < ret->nnames; i++) {
+           char *str;
+           int len;
+           sftp_pkt_getstring(pktin, &str, &len);
+           ret->names[i].filename = mkstr(str, len);
+           sftp_pkt_getstring(pktin, &str, &len);
+           ret->names[i].longname = mkstr(str, len);
+           ret->names[i].attrs = sftp_pkt_getattrs(pktin);
+       }
+       return ret;
+    } else {
+       fxp_got_status(pktin);
+       return NULL;
+    }
+}
+
+/*
+ * Write to a file. Returns 0 on error, 1 on OK.
+ */
+int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
+    struct sftp_packet *pktin, *pktout;
+    int id;
+
+    pktout = sftp_pkt_init(SSH_FXP_WRITE);
+    sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
+    sftp_pkt_addstring(pktout, handle->hstring);
+    sftp_pkt_adduint64(pktout, offset);
+    sftp_pkt_addstring_start(pktout);
+    sftp_pkt_addstring_data(pktout, buffer, len);
+    sftp_send(pktout);
+    pktin = sftp_recv();
+    id = sftp_pkt_getuint32(pktin);
+    fxp_got_status(pktin);
+    return fxp_errtype == SSH_FX_OK;
+}
+
+/*
+ * Free up an fxp_names structure.
+ */
+void fxp_free_names(struct fxp_names *names) {
+    int i;
+
+    for (i = 0; i < names->nnames; i++) {
+       sfree(names->names[i].filename);
+       sfree(names->names[i].longname);
+    }
+    sfree(names->names);
+    sfree(names);
+}
diff --git a/sftp.h b/sftp.h
new file mode 100644 (file)
index 0000000..f62f2a1
--- /dev/null
+++ b/sftp.h
@@ -0,0 +1,124 @@
+/*
+ * sftp.h: definitions for SFTP and the sftp.c routines.
+ */
+
+#include "int64.h"
+
+#define SSH_FXP_INIT                              1    /* 0x1 */
+#define SSH_FXP_VERSION                           2    /* 0x2 */
+#define SSH_FXP_OPEN                              3    /* 0x3 */
+#define SSH_FXP_CLOSE                             4    /* 0x4 */
+#define SSH_FXP_READ                              5    /* 0x5 */
+#define SSH_FXP_WRITE                             6    /* 0x6 */
+#define SSH_FXP_LSTAT                             7    /* 0x7 */
+#define SSH_FXP_FSTAT                             8    /* 0x8 */
+#define SSH_FXP_SETSTAT                           9    /* 0x9 */
+#define SSH_FXP_FSETSTAT                          10   /* 0xa */
+#define SSH_FXP_OPENDIR                           11   /* 0xb */
+#define SSH_FXP_READDIR                           12   /* 0xc */
+#define SSH_FXP_REMOVE                            13   /* 0xd */
+#define SSH_FXP_MKDIR                             14   /* 0xe */
+#define SSH_FXP_RMDIR                             15   /* 0xf */
+#define SSH_FXP_REALPATH                          16   /* 0x10 */
+#define SSH_FXP_STAT                              17   /* 0x11 */
+#define SSH_FXP_RENAME                            18   /* 0x12 */
+#define SSH_FXP_STATUS                            101  /* 0x65 */
+#define SSH_FXP_HANDLE                            102  /* 0x66 */
+#define SSH_FXP_DATA                              103  /* 0x67 */
+#define SSH_FXP_NAME                              104  /* 0x68 */
+#define SSH_FXP_ATTRS                             105  /* 0x69 */
+#define SSH_FXP_EXTENDED                          200  /* 0xc8 */
+#define SSH_FXP_EXTENDED_REPLY                    201  /* 0xc9 */
+
+#define SSH_FX_OK                                 0
+#define SSH_FX_EOF                                1
+#define SSH_FX_NO_SUCH_FILE                       2
+#define SSH_FX_PERMISSION_DENIED                  3
+#define SSH_FX_FAILURE                            4
+#define SSH_FX_BAD_MESSAGE                        5
+#define SSH_FX_NO_CONNECTION                      6
+#define SSH_FX_CONNECTION_LOST                    7
+#define SSH_FX_OP_UNSUPPORTED                     8
+
+#define SSH_FILEXFER_ATTR_SIZE                    0x00000001
+#define SSH_FILEXFER_ATTR_UIDGID                  0x00000002
+#define SSH_FILEXFER_ATTR_PERMISSIONS             0x00000004
+#define SSH_FILEXFER_ATTR_ACMODTIME               0x00000008
+#define SSH_FILEXFER_ATTR_EXTENDED                0x80000000
+
+#define SSH_FXF_READ                              0x00000001
+#define SSH_FXF_WRITE                             0x00000002
+#define SSH_FXF_APPEND                            0x00000004
+#define SSH_FXF_CREAT                             0x00000008
+#define SSH_FXF_TRUNC                             0x00000010
+#define SSH_FXF_EXCL                              0x00000020
+
+#define SFTP_PROTO_VERSION 3
+
+struct fxp_attrs {
+    unsigned long flags;
+    uint64 size;
+    unsigned long uid;
+    unsigned long gid;
+    unsigned long permissions;
+    unsigned long atime;
+    unsigned long mtime;
+};
+
+struct fxp_handle {
+    char *hstring;
+};
+
+struct fxp_name {
+    char *filename, *longname;
+    struct fxp_attrs attrs;
+};
+
+struct fxp_names {
+    int nnames;
+    struct fxp_name *names;
+};
+
+const char *fxp_error(void);
+int fxp_error_type(void);
+
+/*
+ * Perform exchange of init/version packets. Return 0 on failure.
+ */
+int fxp_init(void);
+
+/*
+ * Canonify a pathname. Concatenate the two given path elements
+ * with a separating slash, unless the second is NULL.
+ */
+char *fxp_realpath(char *path, char *path2);
+
+/*
+ * Open a file.
+ */
+struct fxp_handle *fxp_open(char *path, int type);
+
+/*
+ * Open a directory.
+ */
+struct fxp_handle *fxp_opendir(char *path);
+
+/*
+ * Close a file/dir.
+ */
+void fxp_close(struct fxp_handle *handle);
+
+/*
+ * Read from a file.
+ */
+int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset, int len);
+
+/*
+ * Read from a directory.
+ */
+struct fxp_names *fxp_readdir(struct fxp_handle *handle);
+
+/*
+ * Free up an fxp_names structure.
+ */
+void fxp_free_names(struct fxp_names *names);