From 4c7f0d617cfbb8927870ff8f6a1701372788b693 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Feb 2001 18:21:44 +0000 Subject: [PATCH] First stab at an SFTP client. Currently a Unixland testing app, not integrated into PuTTY. git-svn-id: svn://svn.tartarus.org/sgt/putty@938 cda61777-01e9-0310-a592-d414129be87e --- psftp.c | 510 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ sftp.c | 605 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sftp.h | 124 +++++++++++++ 3 files changed, 1239 insertions(+) create mode 100644 psftp.c create mode 100644 sftp.c create mode 100644 sftp.h diff --git a/psftp.c b/psftp.c new file mode 100644 index 00000000..4445b8b7 --- /dev/null +++ b/psftp.c @@ -0,0 +1,510 @@ +/* + * psftp.c: front end for PSFTP. + */ + +#include +#include +#include +#include + +#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 index 00000000..6b519b69 --- /dev/null +++ b/sftp.c @@ -0,0 +1,605 @@ +/* + * sftp.c: SFTP generic client code. + */ + +#include +#include +#include +#include + +#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 index 00000000..f62f2a12 --- /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); -- 2.11.0