Add SSH File Transfer Protocol (SFTP) file server. It works with both ssh1 and the new ssh2 client. Notes: Fri Jan 25 17:26:48 EST 2013 geoff see u9fs via ssh instead. Reference: /n/sources/patch/sorry/add-sftpfs Date: Fri Dec 14 01:29:46 CET 2012 Signed-off-by: fshahriar@gmail.com Reviewed-by: geoff --- /sys/src/cmd/sftpfs/README Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/README Fri Dec 14 01:25:18 2012 @@ -0,0 +1,9 @@ +AUTHOR Fazlul Shahriar + +BUGS + openssh server bugs: + READDIR gives 0 entries if the directory has no executable permission, + but is readable. + +Small changes for ssh2 by Steve Simon, Apr 2012. + --- /sys/src/cmd/sftpfs/fxp.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/fxp.c Fri Dec 14 01:25:19 2012 @@ -0,0 +1,1169 @@ +/* Copyright © 2008 Fazlul Shahriar*/ + +#include "fxp.h" + +enum{ + MaxPktLen = 34000, + FMsgid = 0x99999999, +}; + +enum{ + /* Fxp.type */ + SSH_FXP_INIT = 1, + SSH_FXP_VERSION = 2, /* reply */ + SSH_FXP_OPEN = 3, + SSH_FXP_CLOSE = 4, + SSH_FXP_READ = 5, + SSH_FXP_WRITE = 6, + SSH_FXP_LSTAT = 7, + SSH_FXP_FSTAT = 8, + SSH_FXP_SETSTAT = 9, + SSH_FXP_FSETSTAT = 10, + SSH_FXP_OPENDIR = 11, + SSH_FXP_READDIR = 12, + SSH_FXP_REMOVE = 13, + SSH_FXP_MKDIR = 14, + SSH_FXP_RMDIR = 15, + SSH_FXP_REALPATH = 16, + SSH_FXP_STAT = 17, + SSH_FXP_RENAME = 18, + SSH_FXP_READLINK = 19, + SSH_FXP_SYMLINK = 20, + SSH_FXP_STATUS = 101, /* reply */ + SSH_FXP_HANDLE = 102, /* reply */ + SSH_FXP_DATA = 103, /* reply */ + SSH_FXP_NAME = 104, /* reply */ + SSH_FXP_ATTRS = 105, /* reply */ + SSH_FXP_EXTENDED = 200, + SSH_FXP_EXTENDED_REPLY = 201, + + /* Fxp.status */ + SSH_FX_OK = 0, + SSH_FX_EOF = 1, + SSH_FX_NO_SUCH_FILE = 2, + SSH_FX_PERMISSION_DENIED = 3, + SSH_FX_FAILURE = 4, + SSH_FX_BAD_MESSAGE = 5, + SSH_FX_NO_CONNECTION = 6, + SSH_FX_CONNECTION_LOST = 7, + SSH_FX_OP_UNSUPPORTED = 8, + + /* Fxp.pflags */ + SSH_FXF_READ = 0x00000001, + SSH_FXF_WRITE = 0x00000002, + SSH_FXF_APPEND = 0x00000004, + SSH_FXF_CREAT = 0x00000008, + SSH_FXF_TRUNC = 0x00000010, + SSH_FXF_EXCL = 0x00000020, + + /* FAttrs.flags */ + SSH_FILEXFER_ATTR_SIZE = 0x00000001, + SSH_FILEXFER_ATTR_UIDGID = 0x00000002, + SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004, + SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008, + SSH_FILEXFER_ATTR_EXTENDED = 0x80000000, +}; + +typedef struct FAttrs FAttrs; +typedef struct FName FName; +typedef struct Fxp Fxp; + +/* File Attributes */ +struct FAttrs{ + u32int flags; + u64int size; + u32int uid; + u32int gid; + u32int perm; + u32int atime; + u32int mtime; + /*u32int nextended; */ +}; + +struct Fxp{ + uchar type; + u32int id; /* except Init and Version */ + union{ + struct{ /* Init,Version */ + u32int version; + }; + struct{ /* Open/Read/Write/Data */ + String filename; /* Remove */ + u32int pflags; + String handle; /* Handle,Close,Readdir,Fstat */ + u64int offset; + u32int len; + String data; + /* Rmdir,Opendir,Realpath,Stat,Lstat,Readlink */ + String path; + FAttrs attrs; /* Fsetstat,Mkdir,Setstat,Attrs */ + }; + struct{ /* Rename */ + String oldpath; + String newpath; + }; + struct{ /* Symlink */ + String linkpath; + String targetpath; + }; + struct{ /* Status */ + u32int status; + String errmsg; + String lang; + }; + struct{ /* Name */ + u32int count; + String *filenames; + String *longnames; + FAttrs *attrsv; + }; + }; +}; + + +enum{ + STACK = 8192, +}; + +static int debug = 0; + +typedef struct Conn Conn; +struct Conn{ + char *host; + char sshver; + char *serverpath; + Map *map; + + uchar buf[MaxPktLen]; + Channel *rdchan; + Channel *wrchan; + int sshfd; + int fd[2]; +}; + +static Conn conn; + +static int +put8(u64int v, uchar *a) +{ + a[0] = v>>56; + a[1] = v>>48; + a[2] = v>>40; + a[3] = v>>32; + a[4] = v>>24; + a[5] = v>>16; + a[6] = v>>8; + a[7] = v; + return 8; +} + +static int +put4(u32int v, uchar *a) +{ + a[0] = v>>24; + a[1] = v>>16; + a[2] = v>>8; + a[3] = v; + return 4; +} + +static int +put1(uchar v, uchar *a) +{ + a[0] = v; + return 1; +} + +static int +putstring(String *s, uchar *buf) +{ + put4(s->len, buf); + memmove(buf+4, s->s, s->len); + + return s->len+4; +} + +static int +putattrs(FAttrs *a, uchar *buf) +{ + uchar *bp; + + bp = buf; + bp += put4(a->flags, bp); + if(a->flags & SSH_FILEXFER_ATTR_SIZE) + bp += put8(a->size, bp); + if(a->flags & SSH_FILEXFER_ATTR_UIDGID){ + bp += put4(a->uid, bp); + bp += put4(a->gid, bp); + } + if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) + bp += put4(a->perm, bp); + if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ + bp += put4(a->atime, bp); + bp += put4(a->mtime, bp); + } + return bp-buf; +} + +static int +fxpattrslen(FAttrs *a) +{ + int n; + + n = 4; + n += (a->flags & SSH_FILEXFER_ATTR_SIZE) ? 8 : 0; + n += (a->flags & SSH_FILEXFER_ATTR_UIDGID) ? 8 : 0; + n += (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) ? 4 : 0; + n += (a->flags & SSH_FILEXFER_ATTR_ACMODTIME) ? 8 : 0; + return n; +} + +static int +fxpencode(Fxp *m, uchar *buf, int bufsz) +{ + uchar *bp; + + if(bufsz < MaxPktLen) + sysfatal("buffer size (%d) too small", bufsz); + + bp = buf; + switch(m->type){ + default: + sysfatal("encoding msg of unknown type %d", m->type); + break; + + case SSH_FXP_INIT: + bp += put4(1+4, bp); + bp += put1(m->type, bp); + bp += put4(m->version, bp); + break; + + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_REALPATH: + case SSH_FXP_STAT: + case SSH_FXP_LSTAT: + case SSH_FXP_READLINK: + bp += put4(1+4+4+m->path.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->path, bp); + break; + + case SSH_FXP_READDIR: + case SSH_FXP_CLOSE: + case SSH_FXP_FSTAT: + bp += put4(1+4+4+m->handle.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->handle, bp); + break; + + case SSH_FXP_OPEN: + bp += put4(1+4+4+m->filename.len+4+fxpattrslen(&m->attrs), bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->filename, bp); + bp += put4(m->pflags, bp); + bp += putattrs(&m->attrs, bp); + break; + + case SSH_FXP_READ: + bp += put4(1+4+4+m->handle.len+8+4, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->handle, bp); + bp += put8(m->offset, bp); + bp += put4(m->len, bp); + break; + + case SSH_FXP_WRITE: + bp += put4(1+4+4+m->handle.len+8+4+m->data.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->handle, bp); + bp += put8(m->offset, bp); + bp += putstring(&m->data, bp); + break; + + case SSH_FXP_REMOVE: + bp += put4(1+4+4+m->filename.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->filename, bp); + break; + + case SSH_FXP_RENAME: + bp += put4(1+4+4+m->oldpath.len+4+m->newpath.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->oldpath, bp); + bp += putstring(&m->newpath, bp); + break; + + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + bp += put4(1+4+4+m->path.len+fxpattrslen(&m->attrs), bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->path, bp); + bp += putattrs(&m->attrs, bp); + break; + + case SSH_FXP_FSETSTAT: + bp += put4(1+4+4+m->handle.len+fxpattrslen(&m->attrs), bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->handle, bp); + bp += putattrs(&m->attrs, bp); + break; + + case SSH_FXP_SYMLINK: + bp += put4(1+4+4+m->linkpath.len+4+m->targetpath.len, bp); + bp += put1(m->type, bp); + bp += put4(m->id, bp); + bp += putstring(&m->linkpath, bp); + bp += putstring(&m->targetpath, bp); + break; + } + return bp-buf; +} + +static int +get8(u64int *x, uchar *a) +{ + u64int v; + + v = (uvlong)a[0]<<56; + v |= (uvlong)a[1]<<48; + v |= (uvlong)a[2]<<40; + v |= (uvlong)a[3]<<32; + v |= a[4]<<24; + v |= a[5]<<16; + v |= a[6]<<8; + v |= a[7]<<0; + *x = v; + return 8; +} + +static int +get4(u32int *x, uchar *a) +{ + *x = (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0); + return 4; +} + +static int +get1(uchar *x, uchar *a) +{ + *x = a[0]; + return 1; +} + +static int +getstring(String *s, uchar *buf) +{ + uchar *bp; + + bp = buf; + bp += get4(&s->len, bp); + s->s = emalloc9p(s->len+1); + memmove(s->s, bp, s->len); + bp += s->len; + s->s[s->len] = 0; + + return bp-buf; +} + +static int +getattrs(FAttrs *a, uchar *buf) +{ + uchar *bp; + + bp = buf; + bp += get4(&a->flags, bp); + if(a->flags & SSH_FILEXFER_ATTR_SIZE) + bp += get8(&a->size, bp); + if(a->flags & SSH_FILEXFER_ATTR_UIDGID){ + bp += get4(&a->uid, bp); + bp += get4(&a->gid, bp); + } + if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) + bp += get4(&a->perm, bp); + if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ + bp += get4(&a->atime, bp); + bp += get4(&a->mtime, bp); + } + return bp-buf; +} + +static Fxp* +fxpdecode(uchar *buf) +{ + Fxp *m; + int i; + uchar *bp; + + bp = buf; + bp += 4; /* ignore length */ + m = emalloc9p(sizeof(*m)); + bp += get1(&m->type, bp); + + switch(m->type){ + default: + sysfatal("bad msg type %d when decoding", m->type); + break; + + case SSH_FXP_VERSION: + bp += get4(&m->version, bp); + break; + + case SSH_FXP_HANDLE: + bp += get4(&m->id, bp); + bp += getstring(&m->handle, bp); + break; + + case SSH_FXP_DATA: + bp += get4(&m->id, bp); + bp += getstring(&m->data, bp); + break; + + case SSH_FXP_STATUS: + bp += get4(&m->id, bp); + bp += get4(&m->status, bp); + bp += getstring(&m->errmsg, bp); + bp += getstring(&m->lang, bp); + break; + + case SSH_FXP_NAME: + bp += get4(&m->id, bp); + bp += get4(&m->count, bp); + m->filenames = emalloc9p(m->count*sizeof(*m->filenames)); + m->longnames = emalloc9p(m->count*sizeof(*m->longnames)); + m->attrsv = emalloc9p(m->count*sizeof(*m->attrsv)); + for(i = 0; i < m->count; i++){ + bp += getstring(&m->filenames[i], bp); + bp += getstring(&m->longnames[i], bp); + bp += getattrs(&m->attrsv[i], bp); + } + break; + + case SSH_FXP_ATTRS: + bp += get4(&m->id, bp); + bp += getattrs(&m->attrs, bp); + break; + } + USED(bp); + return m; +} + +static void +sshproc(void*) +{ + int *p; + + threadsetname("sshproc"); + + p = conn.fd; + close(p[0]); + dup(p[1], 0); + dup(p[1], 1); + close(p[1]); + + switch(conn.sshver){ + case '1': + procexecl(nil, "/bin/ssh1", "ssh1", "-P", "-m", "-I", "-f", + conn.host, conn.serverpath, nil); + break; + case '2': + procexecl(nil, "/bin/ssh", "ssh", "-m", "-i", "-C", "-s", "sftp", conn.host, nil); + break; + case 'o': + procexecl(nil, "/bin/openssh/ssh", "ssh", "-x", "-a", + "-oClearAllForwardings=yes", "-2", conn.host, "-s", "sftp", nil); + break; + } + sysfatal("exec ssh: %r"); +} + +static void +serverproc(void*) +{ + uchar *buf; + Fxp *req, *reply; + int fd; + uint n; + + threadsetname("serverproc"); + + fd = conn.sshfd; + for(;;){ + req = recvp(conn.rdchan); + if(req == nil) + threadexits(nil); + reply = nil; + + n = fxpencode(req, conn.buf, sizeof conn.buf); + buf = conn.buf; + if(debug) + hexdump("sending: ", buf, n); + if(write(fd, buf, n) != n) + goto sendreply; + if(readn(fd, buf, 4) != 4) + goto sendreply; + get4(&n, buf); + if(n >= sizeof(conn.buf)) + sysfatal("reply silly big (%d > %d)\n", n, sizeof(conn.buf)); + if(debug) + fprint(2, "response length: %d\n", n); + if(readn(fd, buf+4, n) != n) + goto sendreply; + if(debug) + hexdump("response: ", buf, n+4); + reply = fxpdecode(buf); + + if(req->type != SSH_FXP_INIT) + if(req->id != reply->id) + sysfatal("reply id doesn't match request id"); +sendreply: + sendp(conn.wrchan, reply); + } +} + +static Fxp* +fxpgetreply(Fxp *msg) +{ + Fxp *r; + + sendp(conn.rdchan, msg); + r = recvp(conn.wrchan); + if(r == nil) /* probably ssh exited */ + threadexitsall(nil); + return r; +} + +static void +stringinit(String *s, char *t) +{ + s->s = (uchar*)estrdup9p(t); + s->len = strlen(t); +} + +static void +stringcpy(String *dst, String *src) +{ + dst->len = src->len; + dst->s = emalloc9p(src->len+1); + memmove(dst->s, src->s, src->len); + dst->s[dst->len] = 0; +} + +static String* +stringdup(String *s) +{ + String *q; + + q = emalloc9p(sizeof *q); + q->len = s->len; + q->s = emalloc9p(s->len+1); + memmove(q->s, s->s, s->len); + q->s[q->len] = 0; + return q; +} + +static void +freestring(String *s) +{ + if(s){ + free(s->s); + free(s); + } +} + +struct{ + u32int status; + char *str; + int iserror; +} errtab[] = { + SSH_FX_OK, "", 0, + SSH_FX_EOF, "", 0, + SSH_FX_NO_SUCH_FILE, Enofile, 1, + SSH_FX_PERMISSION_DENIED, Eperm, 1, + SSH_FX_FAILURE, Efail, 1, + SSH_FX_BAD_MESSAGE, Emsg, 1, + SSH_FX_NO_CONNECTION, Enocn, 1, + SSH_FX_CONNECTION_LOST, Elostcn, 1, + SSH_FX_OP_UNSUPPORTED, Eunsup, 1, +}; + +static char* +errlookup(u32int status, int *err) +{ + int i; + + for(i = 0; i < nelem(errtab); i++) + if(errtab[i].status == status){ + if(err) + *err = errtab[i].iserror; + return errtab[i].str; + } + if(err) + *err = 1; + return Ebotch; +} + +static int +fxpwerrstr(u32int status) +{ + int i; + + for(i = 0; i < nelem(errtab); i++) + if(errtab[i].status == status){ + werrstr(errtab[i].str); + return errtab[i].iserror; + } + werrstr("%s", Ebotch); + return 1; +} + +static void +freefxp(Fxp *m) +{ + int i; + + if(m == nil) + return; + switch(m->type){ + default: + fprint(2, "how to free msg of type %d?\n", m->type); + break; + + case SSH_FXP_INIT: + case SSH_FXP_VERSION: + case SSH_FXP_ATTRS: + break; + + case SSH_FXP_OPEN: + case SSH_FXP_REMOVE: + free(m->filename.s); + break; + + case SSH_FXP_CLOSE: + case SSH_FXP_READ: + case SSH_FXP_READDIR: + case SSH_FXP_FSTAT: + case SSH_FXP_FSETSTAT: + case SSH_FXP_HANDLE: + free(m->handle.s); + break; + + case SSH_FXP_WRITE: + free(m->handle.s); + free(m->data.s); + break; + + case SSH_FXP_RENAME: + free(m->oldpath.s); + free(m->newpath.s); + break; + + case SSH_FXP_MKDIR: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_STAT: + case SSH_FXP_LSTAT: + case SSH_FXP_SETSTAT: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + free(m->path.s); + break; + + case SSH_FXP_SYMLINK: + free(m->linkpath.s); + free(m->targetpath.s); + break; + + case SSH_FXP_STATUS: + free(m->errmsg.s); + free(m->lang.s); + break; + + case SSH_FXP_DATA: + free(m->data.s); + break; + + case SSH_FXP_NAME: + for(i = 0; i < m->count; i++){ + free(m->filenames[i].s); + free(m->longnames[i].s); + } + free(m->filenames); + free(m->longnames); + free(m->attrsv); + break; + } + free(m); +} + +static FHandle* +replyhandle(Fxp *m) +{ + Fxp *r; + FHandle *h; + + r = fxpgetreply(m); + freefxp(m); + switch(r->type){ + default: + werrstr("%s", Ebotch); + freefxp(r); + return nil; + case SSH_FXP_STATUS: + fxpwerrstr(r->status); + freefxp(r); + return nil; + case SSH_FXP_HANDLE: + h = stringdup(&r->handle); + freefxp(r); + return h; + } +} + +static int +replystatus(Fxp *r) +{ + int err; + + switch(r->type){ + default: + werrstr("%s", Ebotch); + freefxp(r); + return 1; + case SSH_FXP_STATUS: + err = fxpwerrstr(r->status); + freefxp(r); + return err; + } +} + +static Fxp* +newfxp(uchar type) +{ + Fxp *m; + + m = emalloc9p(sizeof *m); + m->type = type; + m->id = FMsgid; + return m; +} + +int +fxpremove(char *file) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_REMOVE); + stringinit(&m->filename, file); + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + +int +fxprmdir(char *path) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_RMDIR); + stringinit(&m->path, path); + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + +int +fxpmkdir(char *path, ulong perm) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_MKDIR); + stringinit(&m->path, path); + m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + m->attrs.perm = perm&0777; + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + +long +fxpwrite(FHandle *h, void *buf, long nbuf, vlong off) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_WRITE); + stringcpy(&m->handle, h); + m->offset = off; + m->data.len = nbuf; + m->data.s = emalloc9p(nbuf+1); + memmove(m->data.s, buf, nbuf); + m->data.s[m->data.len] = 0; + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : nbuf; +} + +int +fxpread(FHandle *h, void *buf, long nbuf, vlong off) +{ + Fxp *m, *r; + int n; + + m = newfxp(SSH_FXP_READ); + stringcpy(&m->handle, h); + m->offset = off; + m->len = nbuf; + r = fxpgetreply(m); + freefxp(m); + + if(r->type == SSH_FXP_DATA){ + n = r->data.len; + memmove(buf, r->data.s, n); + freefxp(r); + return n; + } + return replystatus(r) ? -1 : 0; +} + +FHandle* +fxpcreate(char *file, int omode, ulong perm) +{ + int mode; + Fxp *m; + + mode = SSH_FXF_CREAT | SSH_FXF_TRUNC; + if(omode&OREAD) + mode |= SSH_FXF_READ; + if(omode&OWRITE) + mode |= SSH_FXF_WRITE; + if(omode&ORDWR) + mode |= SSH_FXF_READ | SSH_FXF_WRITE; + + m = newfxp(SSH_FXP_OPEN); + stringinit(&m->filename, file); + m->pflags = mode; + m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + m->attrs.perm = perm&0777; + + return replyhandle(m); +} + +FHandle* +fxpopen(char *file, int omode) +{ + Fxp *m; + int mode; + + /* + * We can't do any permission checks for OEXEC or + * ORCLOSE, as we don't know the UID of the user or what + * group(s) the user belongs to. + */ + mode = 0; + if(omode&OREAD) + mode |= SSH_FXF_READ; + if(omode&OWRITE) + mode |= SSH_FXF_WRITE; + if(omode&ORDWR) + mode |= SSH_FXF_READ | SSH_FXF_WRITE; + /* + * If file doesn't exists, there should be no Topen, + * as Twalk will fail. So, the CREAT flag is here only to + * statisfy sftp protocol. + */ + if(omode&OTRUNC) + mode |= SSH_FXF_TRUNC | SSH_FXF_CREAT; + + m = newfxp(SSH_FXP_OPEN); + stringinit(&m->filename, file); + m->pflags = mode; + m->attrs.flags = 0; + + return replyhandle(m); +} + +void +freedir(Dir *d) +{ + if(d){ + free(d->uid); + free(d->gid); + free(d->muid); + free(d->name); + free(d); + } +} + +enum { + UIDSz = 20, + ATDIR = 0040000, +}; + +static void +attrs2dir(Dir *d, FAttrs *a, char *name) +{ + /* caller should fill in Qid properly later */ + d->qid = (Qid){0, 0, QTFILE}; + d->mode = 0; + d->atime = 0; + d->mtime = 0; + d->length = 0; + d->muid = estrdup9p("unknown"); + d->name = estrdup9p(name); + if(conn.map == nil){ + d->uid = estrdup9p("unknown"); + d->gid = estrdup9p("unknown"); + }else{ + d->uid = uidtostr(conn.map, a->uid); + d->gid = gidtostr(conn.map, a->gid); + } + + if(a->flags & SSH_FILEXFER_ATTR_SIZE) + d->length = a->size; + if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS){ + d->mode = a->perm&0777; + if(a->perm & ATDIR){ + d->mode |= DMDIR; + d->qid.type = QTDIR; + } + } + if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ + d->atime = a->atime; + d->mtime = a->mtime; + } +} + +static char* +basename(char *p) +{ + char *s; + + s = strrchr(p, '/'); + if(s == nil) + s = p; + else + s++; + return estrdup9p(s); +} + +Dir* +fxpstat1(char *name, char **err) +{ + Fxp *m, *r; + Dir *d; + + m = newfxp(SSH_FXP_STAT); + stringinit(&m->path, name); + r = fxpgetreply(m); + freefxp(m); + + /* walk1 kludge. We can't use responderror there */ + if(err) + *err = errlookup(r->status, nil); + + if(r->type == SSH_FXP_ATTRS){ + d = emalloc9p(sizeof *d); + name = basename(name); + attrs2dir(d, &r->attrs, name); + free(name); + freefxp(r); + return d; + } + return replystatus(r) ? nil : nil; +} + +Dir* +fxpstat(char *name) +{ + return fxpstat1(name, nil); +} + +static void +dir2attrs(FAttrs *a, Dir *d, Dir *od) +{ + a->flags = 0; + if(d->length != ~0){ + a->flags |= SSH_FILEXFER_ATTR_SIZE; + a->size = d->length; + } + if(d->mode != ~0){ + a->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; + a->perm = d->mode&0777; + } + d->atime = od->atime; + d->mtime = od->mtime; + if(d->atime != ~0) + a->atime = d->atime; + if(d->mtime != ~0) + a->mtime = d->mtime; + if(d->atime != ~0 || d->mtime != ~0) + a->flags |= SSH_FILEXFER_ATTR_ACMODTIME; +} + +int +fxpsetstat(char *name, Dir *d) +{ + Fxp *m, *r; + Dir *od; + + if((od = fxpstat(name)) == nil) + return -1; + m = newfxp(SSH_FXP_SETSTAT); + stringinit(&m->path, name); + dir2attrs(&m->attrs, d, od); + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + +int +fxprename(char *old, char *new) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_RENAME); + stringinit(&m->oldpath, old); + stringinit(&m->newpath, new); + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + +long +fxpreaddir(FHandle *h, Dir ***buf) +{ + Fxp *m, *r; + int i, j; + char *name; + Dir **ds; + FAttrs *a; + + m = newfxp(SSH_FXP_READDIR); + stringcpy(&m->handle, h); + r = fxpgetreply(m); + freefxp(m); + + if(r->type == SSH_FXP_NAME){ + ds = emalloc9p(r->count*sizeof(Dir*)); + j = 0; + for(i = 0; i < r->count; i++){ + name = (char*)r->filenames[i].s; + a = &r->attrsv[i]; + if(strcmp(name, ".") != 0 && strcmp(name, "..") != 0){ + ds[j] = emalloc9p(sizeof(Dir)); + attrs2dir(ds[j], a, name); + j++; + } + } + *buf = ds; + freefxp(r); + return j; + } + return replystatus(r) ? -1 : 0; +} + +int +fxpclose(FHandle *h) +{ + Fxp *m, *r; + + m = newfxp(SSH_FXP_CLOSE); + stringcpy(&m->handle, h); + freestring(h); + r = fxpgetreply(m); + freefxp(m); + + return replystatus(r) ? -1 : 0; +} + + +FHandle* +fxpopendir(char *path) +{ + Fxp *m; + + m = newfxp(SSH_FXP_OPENDIR); + stringinit(&m->path, path); + + return replyhandle(m); +} + +static int +fxpversion(int ver) +{ + Fxp *m, *r; + int n; + + m = newfxp(SSH_FXP_INIT); + m->version = ver; + r = fxpgetreply(m); + freefxp(m); + + if(r->type != SSH_FXP_VERSION){ + freefxp(r); + return -1; + } + n = r->version; + freefxp(r); + return n; +} + +int +fxpinit(char *host, char ver, char *path) +{ + int *p, n; + + p = conn.fd; + if(pipe(p) < 0) + sysfatal("pipe: %r"); + conn.host = estrdup9p(host); + conn.sshver = ver; + conn.serverpath = estrdup9p(path); + procrfork(sshproc, nil, STACK, RFFDG); + close(p[1]); + conn.sshfd = p[0]; + + conn.rdchan = chancreate(sizeof(Fxp*), 0); + conn.wrchan = chancreate(sizeof(Fxp*), 0); + proccreate(serverproc, nil, STACK); + + if((n = fxpversion(3)) != 3){ + werrstr("got protocol version %d; want 3", n); + return -1; + } + return 0; +} + +void +fxpreadmap(char *pfile, char *gfile) +{ + conn.map = readmap(pfile, gfile); +} + +void +fxpterm(void) +{ + close(conn.sshfd); + sendp(conn.rdchan, nil); /* terminate serverproc */ + chanfree(conn.rdchan); + chanfree(conn.wrchan); + free(conn.host); + free(conn.serverpath); + if(conn.map) + closemap(conn.map); +} --- /sys/src/cmd/sftpfs/fxp.h Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/fxp.h Fri Dec 14 01:25:20 2012 @@ -0,0 +1,104 @@ +/* Copyright © 2008 Fazlul Shahriar*/ + +#include +#include +#include +#include +#include <9p.h> +#include + +/* + * util.c + */ +extern char* estrstrdup(char*, char*); +extern char* estr3dup(char*, char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void hexdump(char*, uchar*, int); + + +/* + * map.c + */ +typedef struct Map Map; +typedef struct User User; +typedef struct Group Group; + +struct User{ + char *name; + uint uid; + uint gid; + uint g[16]; + uint ng; + uchar *auth; + int nauth; +}; + +struct Group{ + char *name; /* same pos as in User struct */ + uint gid; /* same pos as in User struct */ +}; + +struct Map{ + int nuser; + int ngroup; + User *user; + User **ubyname; + User **ubyid; + Group *group; + Group **gbyname; + Group **gbyid; +}; + +extern Map* readmap(char*, char*); +extern void closemap(Map*); +extern char* uidtostr(Map*, u32int); +extern char* gidtostr(Map*, u32int); + +/* + * fxp.c + */ +typedef struct String String; +typedef struct String FHandle; + +struct String{ + u32int len; + uchar *s; +}; + +extern int fxpinit(char*,char,char*); +extern void fxpterm(void); +extern FHandle* fxpopendir(char*); +extern int fxpclose(FHandle*); +extern long fxpreaddir(FHandle*, Dir***); +extern FHandle* fxpopen(char*, int); +extern FHandle* fxpcreate(char*, int, ulong); +extern int fxpread(FHandle*, void*, long, vlong); +extern long fxpwrite(FHandle*, void*, long, vlong); +extern int fxpmkdir(char*, ulong); +extern int fxprmdir(char*); +extern int fxpremove(char*); +extern Dir* fxpstat1(char*, char**); +extern Dir* fxpstat(char*); +extern int fxpsetstat(char*, Dir*); +extern FHandle* handledup(FHandle*); +extern int fxprename(char*, char*); +extern void freedir(Dir*); +extern void fxpreadmap(char*, char*); + +/* sftp defined errors */ +static char Enofile[] = "no such file"; +static char Eperm[] = "permission denied"; +static char Efail[] = "failed"; +static char Emsg[] = "bad message"; +static char Enocn[] = "no connection"; /* fake */ +static char Elostcn[] = "connection lost"; /* fake */ +static char Eunsup[] = "operation unsupported"; + +/* our errors */ +static char Ebotch[] = "sftp protocol botch"; +static char Ehand[] = "bad handle"; +static char Epath[] = "bad fid path"; +static char Eopen[] = "handle already open"; +static char Einter[] = "internal error"; +static char Ebuf[] = "short buffer"; --- /sys/src/cmd/sftpfs/map.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/map.c Fri Dec 14 01:25:21 2012 @@ -0,0 +1,246 @@ +/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */ + +/* + * name <-> int translation from nfs(4) + */ + +#include "fxp.h" + +static Map emptymap; +static int verbose; + +static User* +finduser(User **u, int nu, char *s) +{ + int lo, hi, mid, n; + + hi = nu; + lo = 0; + while(hi > lo){ + mid = (lo+hi)/2; + n = strcmp(u[mid]->name, s); + if(n == 0) + return u[mid]; + if(n < 0) + lo = mid+1; + else + hi = mid; + } + return nil; +} + +static int +strtoid(User **u, int nu, char *s, u32int *id) +{ + u32int x; + char *p; + User *uu; + + x = strtoul(s, &p, 10); + if(*s != 0 && *p == 0){ + *id = x; + return 0; + } + + uu = finduser(u, nu, s); + if(uu == nil) + return -1; + *id = uu->uid; + return 0; +} + +static char* +idtostr(User **u, int nu, u32int id) +{ + char buf[32]; + int lo, hi, mid; + + hi = nu; + lo = 0; + while(hi > lo){ + mid = (lo+hi)/2; + if(u[mid]->uid == id) + return estrdup9p(u[mid]->name); + if(u[mid]->uid < id) + lo = mid+1; + else + hi = mid; + } + snprint(buf, sizeof buf, "%ud", id); + return estrdup9p(buf); +} + +char* +uidtostr(Map *map, u32int uid) +{ + return idtostr(map->ubyid, map->nuser, uid); +} + +char* +gidtostr(Map *map, u32int gid) +{ + return idtostr((User**)map->gbyid, map->ngroup, gid); +} + +static int +strtouid(Map *map, char *s, u32int *id) +{ + return strtoid(map->ubyname, map->nuser, s, id); +} + +static int +strtogid(Map *map, char *s, u32int *id) +{ + return strtoid((User**)map->gbyid, map->ngroup, s, id); +} + + +static int +idcmp(const void *va, const void *vb) +{ + User **a, **b; + + a = (User**)va; + b = (User**)vb; + return (*a)->uid - (*b)->uid; +} + +static int +namecmp(const void *va, const void *vb) +{ + User **a, **b; + + a = (User**)va; + b = (User**)vb; + return strcmp((*a)->name, (*b)->name); +} + +void +closemap(Map *m) +{ + int i; + + for(i=0; inuser; i++){ + free(m->user[i].name); + free(m->user[i].auth); + } + for(i=0; ingroup; i++) + free(m->group[i].name); + free(m->user); + free(m->group); + free(m->ubyid); + free(m->ubyname); + free(m->gbyid); + free(m->gbyname); + free(m); +} + +Map* +readmap(char *passwd, char *group) +{ + char *s, *f[10], *p, *nextp, *name; + int i, nf, line, uid, gid; + Biobuf *b; + Map *m; + User *u; + Group *g; + + m = emalloc9p(sizeof(Map)); + + if((b = Bopen(passwd, OREAD)) == nil){ + free(m); + return nil; + } + line = 0; + for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){ + line++; + if(s[0] == '#') + continue; + nf = getfields(s, f, nelem(f), 0, ":"); + if(nf < 4) + continue; + name = f[0]; + uid = strtol(f[2], &p, 10); + if(f[2][0] == 0 || *p != 0){ + fprint(2, "%s:%d: non-numeric id in third field\n", passwd, line); + continue; + } + gid = strtol(f[3], &p, 10); + if(f[3][0] == 0 || *p != 0){ + fprint(2, "%s:%d: non-numeric id in fourth field\n", passwd, line); + continue; + } + if(m->nuser%32 == 0) + m->user = erealloc9p(m->user, (m->nuser+32)*sizeof(m->user[0])); + u = &m->user[m->nuser++]; + u->name = estrdup9p(name); + u->uid = uid; + u->gid = gid; + u->ng = 0; + u->auth = 0; + u->nauth = 0; + } + Bterm(b); + m->ubyname = emalloc9p(m->nuser*sizeof(User*)); + m->ubyid = emalloc9p(m->nuser*sizeof(User*)); + for(i=0; inuser; i++){ + m->ubyname[i] = &m->user[i]; + m->ubyid[i] = &m->user[i]; + } + qsort(m->ubyname, m->nuser, sizeof(m->ubyname[0]), namecmp); + qsort(m->ubyid, m->nuser, sizeof(m->ubyid[0]), idcmp); + + if((b = Bopen(group, OREAD)) == nil){ + closemap(m); + return nil; + } + line = 0; + for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){ + line++; + if(s[0] == '#') + continue; + nf = getfields(s, f, nelem(f), 0, ":"); + if(nf < 4) + continue; + name = f[0]; + gid = strtol(f[2], &p, 10); + if(f[2][0] == 0 || *p != 0){ + fprint(2, "%s:%d: non-numeric id in third field\n", group, line); + continue; + } + if(m->ngroup%32 == 0) + m->group = erealloc9p(m->group, (m->ngroup+32)*sizeof(m->group[0])); + g = &m->group[m->ngroup++]; + g->name = estrdup9p(name); + g->gid = gid; + + for(p=f[3]; *p; p=nextp){ + if((nextp = strchr(p, ',')) != nil) + *nextp++ = 0; + else + nextp = p+strlen(p); + u = finduser(m->ubyname, m->nuser, p); + if(u == nil){ + if(verbose) + fprint(2, "%s:%d: unknown user %s\n", group, line, p); + continue; + } + if(u->ng >= nelem(u->g)){ + fprint(2, "%s:%d: user %s is in too many groups; ignoring %s\n", group, line, p, name); + continue; + } + u->g[u->ng++] = gid; + } + } + Bterm(b); + m->gbyname = emalloc9p(m->ngroup*sizeof(Group*)); + m->gbyid = emalloc9p(m->ngroup*sizeof(Group*)); + for(i=0; ingroup; i++){ + m->gbyname[i] = &m->group[i]; + m->gbyid[i] = &m->group[i]; + } + qsort(m->gbyname, m->ngroup, sizeof(m->gbyname[0]), namecmp); + qsort(m->gbyid, m->ngroup, sizeof(m->gbyid[0]), idcmp); + + return m; +} --- /sys/src/cmd/sftpfs/mkfile Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/mkfile Fri Dec 14 01:25:22 2012 @@ -0,0 +1,20 @@ +mode&DMDIR ? QTDIR : QTFILE; + q.vers = d->mtime; + return q; +} + +FAux* +newfaux(char *path) +{ + FAux *fa; + + fa = emalloc9p(sizeof *fa); + fa->path = estrdup9p(path); + fa->handle = nil; + fa->dir = nil; + fa->ndir = 0; + + return fa; +} + +void +freefaux(FAux *fa) +{ + int i; + + free(fa->path); + if(fa->dir != nil){ + for(i = 0; i < fa->ndir; i++) + freedir(fa->dir[i]); + free(fa->dir); + } + free(fa); +} + +static void +fsattach(Req *r) +{ + FAux *fa; + Dir *d; + + if((d = fxpstat("/")) == nil){ + responderror(r); + return; + } + r->fid->qid = getqid("/", "", d); + freedir(d); + r->ofcall.qid = r->fid->qid; + fa = newfaux("/"); + r->fid->aux = fa; + respond(r, nil); +} + +static char* +fsclone(Fid *oldfid, Fid *newfid) +{ + FAux *fa, *ofa; + + ofa = oldfid->aux; + if(ofa->handle != nil) + return Ehand; + fa = newfaux(ofa->path); + newfid->aux = fa; + + return nil; +} + +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + FAux *fa; + char *s, *dot, *err; + Dir *d; + + fa = fid->aux; + if(fa->handle != nil) + return Ehand; + if(strcmp(name, "..") == 0){ + s = strrchr(fa->path, '/'); + if(s == nil) + return Epath; + if(s != fa->path) /* not / */ + *s = 0; + }else + fa->path = eappend(fa->path, "/", name); + + d = fxpstat1(fa->path, &err); + if(d == nil) + return err; + fid->qid = getqid(fa->path, "", d); + *qid = fid->qid; + freedir(d); + + if(d->mode&DMDIR){ + /* + * /some/path/ is executable iff /some/path/. is + * stat-able + */ + dot = estrstrdup(fa->path, "/."); + if((d = fxpstat1(dot, &err)) == nil) + return err; + freedir(d); + free(dot); + } + return nil; +} + +static void +fsdestroyfid(Fid *fid) +{ + FAux *fa; + + fa = fid->aux; + if(fa == nil) + return; + fid->aux = nil; + + if(fa->handle != nil) + fxpclose(fa->handle); + freefaux(fa); +} + +static void +fsopen(Req *r) +{ + FAux *fa; + FHandle *h; + + fa = r->fid->aux; + if(fa->handle != nil){ + respond(r, Eopen); + return; + } + if(r->fid->qid.type & QTDIR) + h = fxpopendir(fa->path); + else + h = fxpopen(fa->path, r->ifcall.mode); + if(h == nil){ + responderror(r); + return; + } + fa->handle = h; + respond(r, nil); +} + +static void +fscreate(Req *r) +{ + FAux *fa, *dir; + u32int perm; + uchar mode; + char *s; + Dir *d; + + dir = r->fid->aux; + s = estr3dup(dir->path, "/", r->ifcall.name); + fa = newfaux(s); + free(s); + + mode = r->ifcall.mode; + perm = r->ifcall.perm; + if(perm & DMDIR){ + if(fxpmkdir(fa->path, perm) < 0){ + freefaux(fa); + responderror(r); + return; + } + fa->handle = fxpopendir(fa->path); + }else{ + fa->handle = fxpcreate(fa->path, mode, perm); + } + if(fa->handle == nil){ + freefaux(fa); + responderror(r); + return; + } + if((d = fxpstat(fa->path)) == nil){ + freefaux(fa); + responderror(r); + return; + } + r->fid->qid = getqid(fa->path, "", d); + freedir(d); + r->ofcall.qid = r->fid->qid; + r->fid->aux = fa; + respond(r, nil); +} + +static void +fsremove(Req *r) +{ + FAux *fa; + int n; + + fa = r->fid->aux; + if(r->fid->qid.type & QTDIR) + n = fxprmdir(fa->path); + else + n = fxpremove(fa->path); + if(n < 0){ + responderror(r); + return; + } + respond(r, nil); +} + +static int +getdirent(int n, Dir *d, void *aux) +{ + FAux *fa; + Dir *e; + + fa = aux; + if(n >= fa->ndir) + return -1; + e = fa->dir[n]; + d->qid = getqid(fa->path, e->name, e); + d->mode = e->mode; + d->atime = e->atime; + d->mtime = e->mtime; + d->length = e->length; + d->name = estrdup9p(e->name); + d->uid = estrdup9p(e->uid); + d->gid = estrdup9p(e->gid); + d->muid = estrdup9p(e->muid); + + return 0; +} + +static void +fsread(Req *r) +{ + FAux *fa; + uchar *buf; + long msz, nbuf; + int n, nds, i; + vlong off; + Dir **d, **ds, **dp; + + fa = r->fid->aux; + if(fa->handle == nil){ + respond(r, Ehand); + return; + } + if(r->fid->qid.type & QTDIR){ + if(fa->dir == nil){ + ds = nil; + nds = 0; + while((n = fxpreaddir(fa->handle, &d)) > 0){ + ds = erealloc9p(ds, (nds+n)*sizeof(Dir*)); + dp = ds+nds; + for(i = 0; i < n; i++) + dp[i] = d[i]; + free(d); + nds += n; + } + if(n < 0){ + free(ds); + responderror(r); + return; + } + fa->dir = ds; + fa->ndir = nds; + } + dirread9p(r, getdirent, fa); + }else{ + nbuf = r->ifcall.count; + buf = (uchar*)r->ofcall.data; + off = r->ifcall.offset; + if((msz = fxpread(fa->handle, buf, nbuf, off)) < 0){ + free(buf); + responderror(r); + return; + } + r->ofcall.count = msz; + } + respond(r, nil); +} + +static void +fswrite(Req *r) +{ + FAux *fa; + long n; + + fa = r->fid->aux; + if(fa->handle == nil){ + respond(r, Ehand); + return; + } + n = fxpwrite(fa->handle, r->ifcall.data, + r->ifcall.count, r->ifcall.offset); + if(n < 0){ + responderror(r); + return; + } + r->ofcall.count = n; + respond(r, nil); +} + +static void +fsstat(Req *r) +{ + FAux *fa; + Dir *d; + + fa = r->fid->aux; + d = fxpstat(fa->path); + if(d == nil){ + responderror(r); + return; + } + r->d.qid = getqid(fa->path, "", d); + r->d.mode = d->mode; + r->d.atime = d->atime; + r->d.mtime = d->mtime; + r->d.length = d->length; + r->d.name = estrdup9p(d->name); + r->d.uid = estrdup9p(d->uid); + r->d.gid = estrdup9p(d->gid); + r->d.muid = estrdup9p(d->muid); + freedir(d); + respond(r, nil); +} + +static char* +dirname(char *path) +{ + char *d, *s; + + d = estrdup9p(path); + s = strrchr(d, '/'); + if(s != nil) + *s = 0; + return d; +} + +static void +fswstat(Req *r) +{ + FAux *fa; + char *newp, *dir; + + fa = r->fid->aux; + if(r->d.name[0] == 0){ + if(fxpsetstat(fa->path, &r->d) < 0){ + responderror(r); + return; + } + }else{ + dir = dirname(fa->path); + newp = estr3dup(dir, "/", r->d.name); + free(dir); + if(strchr(fa->path, '/') == nil || strchr(newp, '/') == nil){ + respond(r, Epath); + return; + } + if(fxprename(fa->path, newp) < 0){ + free(newp); + responderror(r); + return; + } + free(fa->path); + fa->path = newp; + } + respond(r, nil); +} + +static void +fsend(Srv*) +{ + fxpterm(); +} + +static Srv fs = { + .attach = fsattach, + .walk1 = fswalk1, + .clone = fsclone, + .open = fsopen, + .create = fscreate, + .read = fsread, + .write = fswrite, + .remove = fsremove, + .wstat = fswstat, + .stat = fsstat, + .destroyfid = fsdestroyfid, + .end = fsend, +}; + +void +usage(void) +{ + fprint(2, "usage: sftpfs [-12oDU] [-m mountpoint] [-p serverpath ] [-s srvname] [-u passwd group] [user@]hostname\n"); + threadexitsall("usage"); +} + +static char *pfile; +static char *gfile; +static char *srvname; +static char *host; +static char *mntpt; +static char *serverpath = "/usr/lib/sftp-server"; +static int bigu; +static char sshver = '2'; + +void +threadmain(int argc, char **argv) +{ + char mpath[50], ppath[50], gpath[50]; + + ARGBEGIN{ + default: + usage(); + break; + case '1': + case '2': + case 'o': + sshver = ARGC(); + break; + case 'D': + chatty9p++; + break; + case 'm': + mntpt = EARGF(usage()); + break; + case 'p': + serverpath = EARGF(usage()); + break; + case 's': + srvname = EARGF(usage()); + break; + case 'u': + pfile = EARGF(usage()); + gfile = EARGF(usage()); + break; + case 'U': + bigu = 1; + break; + }ARGEND; + + if(argc < 1) + usage(); + + host = argv[0]; + if(fxpinit(host, sshver, serverpath) < 0) + sysfatal("sftp init: %r"); + + if(mntpt == nil){ + snprint(mpath, sizeof(mpath), "/n/%s", host); + mntpt = mpath; + } + if(bigu){ + snprint(ppath, sizeof(ppath), "%s/etc/passwd", mntpt); + pfile = ppath; + snprint(gpath, sizeof(gpath), "%s/etc/group", mntpt); + gfile = gpath; + } + + /* + * try to ensure two mounts of the same host get the same qids, + * but two mounts of similar hosts get different ones. + */ + Seed = hash(host, nil, 0); + + threadpostmountsrv(&fs, srvname, mntpt, MREPL|MCREATE); + + if(gfile != nil && pfile != nil) + fxpreadmap(pfile, gfile); + threadexits(nil); +} --- /sys/src/cmd/sftpfs/sftpfs.man Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/sftpfs.man Fri Dec 14 01:25:23 2012 @@ -0,0 +1,94 @@ +.TH SFTPFS 4 +.SH NAME +sftpfs \- SSH File Transfer Protocol (SFTP) file system +.SH SYNOPSIS +.B sftpfs +[ +.B -12oDU +] [ +.B -m +.I mountpoint +] [ +.B -p +.I serverpath +] [ +.B -s +.I service +] +[ +.B -u +.I passwd +.I group +] +.RI [ user\fB@ ] host +.SH DESCRIPTION +.I Sftpfs +connects to the SFTP subsystem of the SSH server at +.IR host , +at port 22, and presents at +.I mountpoint +(default +.BI /n/ host \fR) +the root file tree of +.IR host . +.LP +If the +.B -s +option is given, the file system is posted as +.BI /srv/ service \fR. +It will try to login as +.IR user , +if given. Otherwise, login is attempted using the user name from +.BR /dev/user . +Authentication is entirely the reponsibility of the SSH client. +.LP +The +.B -1 -2 -o +flags control whether the Bell Lab's ssh client, +Coraid's ssh client, or the OpenSSH client respectively are +used for the session. +.I Serverpath +(default +.BR /usr/lib/sftp-server ) +specifies the path of the SFTP server used in SSH1. A path without +slash character is looked up in the executable search path. Some +common places for SFTP server to live are: +.LP +.RS +.EX +/usr/lib/sftp-server +/usr/libexec/openssh/sftp-server +/usr/lib/misc/sftp-server +/usr/libexec/sftp-server +/usr/lib/ssh/sftp-server +.EE +.RE +.LP +.I Passwd +and +.I group +specifies path to the Unix password and group files, to be used for +UID/GID to name translation. The paths are interpreted after the file +system is mounted, so paths starting with +.I mountpoint +are acceptable. The +.B -U +option uses +.IB mountpoint /etc/passwd +and +.IB mountpoint /etc/group +as the Unix password and group file. It overrides the +.B -u +flag. +.LP +The +.B -D +flag causes a transcript of the 9P conversation to be written to +standard error. +.SH SOURCE +.B /sys/src/cmd/sftpfs +.SH "SEE ALSO" +.IR ssh (1), +.IR ssh2 (1), +.IR srvssh (4), +.IR openssh (1) --- /sys/src/cmd/sftpfs/util.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/sftpfs/util.c Fri Dec 14 01:25:24 2012 @@ -0,0 +1,67 @@ +/* Copyright © 2008 Fazlul Shahriar*/ + +#include "fxp.h" + +char* +estrstrdup(char *s, char *t) +{ + char *u; + + u = emalloc9p(strlen(s)+strlen(t)+1); + strcpy(u, s); + strcat(u, t); + return u; +} + +char* +estr3dup(char *s, char *t, char *r) +{ + char *u; + + u = emalloc9p(strlen(s)+strlen(t)+strlen(r)+1); + strcpy(u, s); + strcat(u, t); + strcat(u, r); + return u; +} + +char* +eappend(char *s, char *sep, char *t) +{ + char *u; + + if(t == nil) + u = estrstrdup(s, sep); + else{ + u = emalloc9p(strlen(s)+strlen(sep)+strlen(t)+1); + strcpy(u, s); + strcat(u, sep); + strcat(u, t); + } + free(s); + return u; +} + +char* +egrow(char *s, char *sep, char *t) +{ + s = eappend(s, sep, t); + free(t); + return s; +} + +void +hexdump(char *cmt, uchar *buf, int len) +{ + Biobuf *bo; + int i; + + bo = emalloc9p(sizeof *bo); + Binit(bo, 1, OWRITE); + Bprint(bo, "%s", cmt); + for(i = 0; i < len; i++) + Bprint(bo, "%02X ", buf[i]); + Bprint(bo, "\n"); + Bterm(bo); + free(bo); +}