#include #include #include #include #include #include <9p.h> #include "ncp.h" #define min(a,b) (((a)<(b))?(a):(b)) char Host[OBJNAMLEN]; // host connect to int Numvol; // number of volumes int Debug; // Packet hex-dump int Maxio; // Maximum IO chunk size int Slip; // time slip in secs between server and us int Fsver; // server software version * 1000 int Bigfiles; // flag: 64 bit files possible on this server Qid Root; // root of remote system Session *Sess; // netware session ulong Myuid; // my UID enum { RIMstat = RIMname|RIMattr|RIMsize|RIMarch|RIMmodif| RIMcreate|RIMns|RIMdir|RIMrights, DARnewdir = DARreadexisting|DARwriteexisting|DARoldopenexisting| DARcreatenewentry|DARdeleteexisting|DARchangeacl|DARsearch| DARmodify|DARsuporvisor, DARnewfile = DARreadonly|DARwriteonly, RIMqid = RIMattr|RIMmodif|RIMdir, RIMnone = 0, }; typedef struct NWdir NWdir; typedef struct Aux Aux; struct Aux { char *path; // full path fo file long expire; // expiration time of cache long off; // file pos of start of cache long end; // file pos of end of cache char *cache; union { Srch srch; // NCP dir search state Fh fh; // file handle }; }; static void responderrstr(Req *r) { char e[ERRMAX]; rerrstr(e, sizeof e); respond(r, e); } /* * used only for root dir and volumes * to which we have no rights */ static void V2D(Dir *d, ulong vol, char *name) { memset(d, 0, sizeof(Dir)); d->type = 'N'; d->dev = vol; d->name = strlwr(estrdup9p(name)); d->uid = estrdup9p("supervisor"); d->muid = estrdup9p("supervisor"); d->gid = estrdup9p("none"); d->mode = DMDIR; // zero mode, no rights d->qid.vers = Vvol; d->qid.path = vol; d->qid.type = QTDIR; } static Qid I2Qid( FInfo *i) { Qid q; q.path = i->dirent; q.vers = i->modified; q.type = (i->attr & FAdirectory)? QTDIR: QTFILE; return q; } static void I2D(Dir *d, FInfo *i) { char *u, *g; memset(d, 0, sizeof(Dir)); d->type = 'N'; d->dev = i->vol; d->name = estrdup9p(i->name); if (i->creatns == NSdos) strlwr(d->name); uid2names(Sess, i->creator, &u, &g); d->uid = estrdup9p(u); d->gid = estrdup9p(g); uid2names(Sess, i->modifier, &u, &g); d->muid = estrdup9p(u); d->atime = i->accessed; d->mtime = i->modified; d->qid = I2Qid(i); if (i->attr & FAdirectory){ d->length = 0; d->mode = (i->rights & ERall)? (DMDIR|0777): (DMDIR|0555); } else{ d->length = i->size; d->mode = (i->attr & FAreadonly)? 0444: 0666; } } static char * newpath(char *path, char *name) { char *p, *q; assert((p = strrchr(path, '/')) != nil); if (strcmp(name, "..") == 0){ if (p == path) return estrdup9p("/"); q = emalloc9p((p-path)+1); strecpy(q, q+(p-path)+1, path); return q; } if (strcmp(path, "/") == 0) return smprint("/%s", name); return smprint("%s/%s", path, name); } static int dodelete(char *s) { FInfo i; Mfi mfi; char e[ERRMAX]; /* * Netware won't delete readonly files */ memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMattr, s, &i) == -1){ return -1; } memset(&mfi, 0, sizeof mfi); mfi.attr = FAnormal | (i.attr & FAdirectory); ModifyFileOrDirInfo(Sess, NSlong, SAall, MFIattr, &mfi, s); if (DeteleFileOrDir(Sess, NSlong, SAall, s) == -1){ errstr(e, sizeof e); mfi.attr = i.attr; ModifyFileOrDirInfo(Sess, NSlong, SAall, MFIattr, &mfi, s); errstr(e, sizeof e); return -1; } return 0; } static int dorename(char *src, char *dst) { int sa; FInfo i; Mfi mfi; char e[ERRMAX]; /* * Netware won't rename of readonly files */ memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMattr, src, &i) == -1) return -1; memset(&mfi, 0, sizeof mfi); mfi.attr = FAnormal | (i.attr & FAdirectory); if (ModifyFileOrDirInfo(Sess, NSlong, SAall, MFIattr, &mfi, src) == -1) return -1; /* * I don't understand why this is nescessary, but rename fails * on subdirs if we simply pass SAall as the search attribute. */ sa = (i.attr & FAdirectory)? SAsubdironly: SAsubdirfiles; if (RenameOrMoveFileOrDir(Sess, NSlong, 1, sa, src, dst) == -1){ errstr(e, sizeof e); memset(&mfi, 0, sizeof mfi); mfi.attr = i.attr; ModifyFileOrDirInfo(Sess, NSlong, SAall, MFIattr, &mfi, src); errstr(e, sizeof e); return -1; } memset(&mfi, 0, sizeof mfi); mfi.attr = i.attr; ModifyFileOrDirInfo(Sess, NSlong, SAall, MFIattr, &mfi, dst); return 0; } int doreopen(Fid *f) /* as we must close open files during rename */ { FInfo i; int oca; Aux *a = f->aux; int dar = DARcompatibility; switch (f->omode & OMASK){ case OREAD: dar |= DARreadonly; break; case OWRITE: dar |= DARwriteonly; break; case ORDWR: dar |= DARreadonly|DARwriteonly; break; case OEXEC: dar |= DARreadonly; break; } if (OpenCreateFileOrSubdir(Sess, NSlong, OCMopen, SAall, RIMnone, dar, FAnormal, a->path, &oca, a->fh, &i) == -1) return -1; return 0; } int dotruncate(char *path, ulong len) { Fh fh; FInfo i; int err, oca; char buf[1]; if (OpenCreateFileOrSubdir(Sess, NSlong, OCMopen, SAall, RIMnone, DARreadonly|DARwriteonly, FAnormal, path, &oca, fh, &i) == -1){ return -1; } err = WriteToAFile(Sess, fh, buf, 0, len); err += CloseFile(Sess, fh); return (err == 0)? 0: -1; } static int dirgen(int slot, Dir *d, void *aux) { FInfo i; Aux *a = aux; int nent, more; char *npath, volnam[VOLNAMLEN]; long off; int numinf = numinfo(); if (strcmp(a->path, "/") == 0){ if (slot < numinf){ dirgeninfo(slot, d); return 0; } else slot -= numinf; *volnam = 0; if (slot >= Numvol) return -1; if (GetVolumeName(Sess, slot, volnam) == -1) return -1; if (*volnam == 0) return -1; npath = newpath(a->path, volnam); memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMstat, npath, &i) == 0){ /* * ObtainFileOrDirInfo() on volumes returns mostly correct * metadata (NW 5.0) but the filename is missing, so we add * that by hand, and also tweek the permissions. It may fails * on earlier server versions, but we just fall back to V2D(). */ I2D(d, &i); free(d->name); d->name = estrdup9p(strlwr(volnam)); d->mode = 0555|DMDIR; } else V2D(d, slot, volnam); free(npath); return 0; } off = slot * sizeof(FInfo); if (off >= a->off && off < a->end && time(nil) < a->expire){ I2D(d, (FInfo *)(a->cache + (off - a->off))); return 0; } if (off == 0){ if (InitializeSearch(Sess, NSlong, a->path, a->srch) == -1) return -1; a->off = 0; a->end = 0; } /* * we try to read dirs in 64 entry chunks - the * server will round this down to an integer number of * entrys to fit in the negiotated packet size; * * Disappointingly we only seem to get 4 to 6 * directory entries per read. */ more = 1; do { nent = 64; a->off = a->end; if (SearchFileOrSubdirectorySet(Sess, a->srch, NSlong, DSdos, SAall, "\377*", RIMstat, (FInfo *)a->cache, &nent, &more) == -1) return -1; a->end = a->off + nent * sizeof(FInfo); }while (a->off < off && more); a->expire = time(nil) + CACHETIME; if (off >= a->end) return -1; I2D(d, (FInfo *)(a->cache + (off - a->off))); return 0; } static void fsattach(Req *r) { Aux *a; char *spec = r->ifcall.aname; if(spec && spec[0]){ respond(r, "invalid attach specifier"); return; } r->ofcall.qid = (Qid){Proot, Vvol, QTDIR}; r->fid->qid = r->ofcall.qid; a = r->fid->aux = emalloc9p(sizeof(Aux)); memset(a, 0, sizeof(Aux)); a->path = estrdup9p("/"); respond(r, nil); } static char* fsclone(Fid *ofid, Fid *fid) { Aux *a = emalloc9p(sizeof(Aux)); *a = *(Aux*)ofid->aux; if (a->path) a->path = estrdup9p(a->path); fid->aux = a; return nil; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { FInfo i; int n, vol; char *npath; static char e[ERRMAX]; Aux *a = fid->aux; npath = newpath(a->path, name); if (strcmp(npath, "/") == 0) *qid = (Qid){Proot, Vvol, QTDIR}; else if (strrchr(npath, '/') == npath){ if ((n = walkinfo(name)) != -1){ *qid = (Qid){n, Vinfo, 0}; } else { if (GetVolumeNumber(Sess, npath +1, &vol) == -1){ free(npath); rerrstr(e, sizeof(e)); return e; } *qid = (Qid){vol, Vvol, QTDIR}; } } else{ memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMqid, npath, &i) == -1){ free(npath); rerrstr(e, sizeof(e)); if (strcmp(e, "failed") == 0) return "file does not exist"; return e; } *qid = I2Qid(&i); } free(a->path); a->path = npath; fid->qid = *qid; return nil; } static void fsstat(Req *r) { FInfo i; Aux *a = r->fid->aux; if (r->fid->qid.path == Proot) V2D(&r->d, Proot, ""); else if (r->fid->qid.vers == Vinfo) dirgeninfo(r->fid->qid.path, &r->d); else if (r->fid->qid.vers == Vvol){ memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMstat, a->path, &i) == 0) I2D(&r->d, &i); else V2D(&r->d, r->fid->qid.path, a->path +1); } else{ memset(&i, 0, sizeof i); if (ObtainFileOrDirInfo(Sess, NSlong, NSlong, RIMstat, a->path, &i) == -1){ responderrstr(r); return; } I2D(&r->d, &i); } respond(r, nil); } /* * All files are created in compatibility mode because of one marginal case: * If you use mk to build an ANSI C library (using pcc) then the object files * are opened for reading whilst 8c is still writing them. This is illegal in * NCP unless the compatability mode is set. */ static void fscreate(Req *r) { FInfo i; char *npath; int oca, ocm, dar, attr; Aux *a = r->fid->aux; a->cache = emalloc9p(Sess->mtu); npath = smprint("%s/%s", a->path, r->ifcall.name); if (r->ifcall.perm & DMDIR){ attr = FAdirectory; ocm = OCMcreate; dar = DARnewdir; } else { attr = FAnormal; dar = DARnewfile|DARcompatibility; ocm = OCMcreate|OCMopen|OCMtruncate; } memset(&i, 0, sizeof i); if (OpenCreateFileOrSubdir(Sess, NSlong, ocm, SAall, RIMqid, dar, attr, npath, &oca, a->fh, &i) == -1 || oca != OCAcreated){ char e[ERRMAX]; rerrstr(e, sizeof(e)); if (oca != OCAcreated && strcmp(e, "lock fail") != 0){ free(npath); responderrstr(r); return; } } free(a->path); a->path = npath; r->ofcall.qid = I2Qid(&i); r->fid->qid = r->ofcall.qid; respond(r, nil); } static void fsopen(Req *r) { FInfo i; Aux *a = r->fid->aux; int oca, ocm = OCMopen; int dar = DARcompatibility; if (r->fid->qid.vers == Vinfo){ if (makeinfo(r->fid->qid.path) != -1) respond(r, nil); else respond(r, "cannot renerate info"); return; } a->cache = emalloc9p(Sess->mtu); if (r->ofcall.qid.type & QTDIR){ respond(r, nil); return; } if (r->ifcall.mode & OTRUNC) ocm |= OCMtruncate; switch (r->ifcall.mode & OMASK){ case OREAD: dar |= DARreadonly; break; case OWRITE: dar |= DARwriteonly; break; case ORDWR: dar |= DARreadonly|DARwriteonly; break; case OEXEC: dar |= DARreadonly; break; } if (OpenCreateFileOrSubdir(Sess, NSlong, ocm, SAall, RIMnone, dar, FAnormal, a->path, &oca, a->fh, &i) == -1){ responderrstr(r); return; } respond(r, 0); } static void fswrite(Req *r) { long n, m; int err; long got = 0; Aux *a = r->fid->aux; char *buf = r->ifcall.data; long len = r->ifcall.count; long off = r->ifcall.offset; if (r->fid->qid.vers == Vinfo){ respond(r, "illegal operation"); return; } if (r->ifcall.offset > ~0U || r->ifcall.count + r->ifcall.offset > ~0U){ respond(r, "64 bit file offsets not supported"); return; } /* Writes must not cross a 4096 byte boundry */ do { m = n = min(Maxio-(off % IOCHUNK), len-got); err = WriteToAFile(Sess, a->fh, buf, n, off); off += n; got += n; buf += n; } while (got < len && n == m && !err); r->ofcall.count = got; if (err == -1) responderrstr(r); else respond(r, nil); } static void fsread(Req *r) { long n, m; int err; long got = 0; Aux *a = r->fid->aux; char *buf = r->ofcall.data; long len = r->ifcall.count; long off = r->ifcall.offset; if (r->fid->qid.vers == Vinfo){ r->ofcall.count = readinfo(r->fid->qid.path, buf, len, off); respond(r, nil); return; } if (r->ifcall.offset > ~0U || r->ifcall.count + r->ifcall.offset > ~0U){ respond(r, "64 bit file offsets not supported"); return; } if (r->fid->qid.type & QTDIR){ dirread9p(r, dirgen, a); respond(r, nil); return; } if (off >= a->off && (off + len) < a->end && time(nil) < a->expire){ memcpy(buf, a->cache + (off - a->off), len); r->ofcall.count = len; respond(r, nil); return; } a->off = off - (off % IOCHUNK); do { m = n = Maxio; err = ReadFromAFile(Sess, a->fh, a->cache + got, &n, a->off + got); got += n; } while (got < Maxio && n == m && !err); a->end = a->off + got; a->expire = time(nil) + CACHETIME; if ((a->end - a->off) > (off - a->off)) r->ofcall.count = min(len, (a->end - a->off) - (off - a->off)); else r->ofcall.count = 0; if (err == -1){ responderrstr(r); return; } memcpy(buf, a->cache + (off - a->off), r->ofcall.count); respond(r, nil); } static void fsdestroyfid(Fid *f) { Aux *a = f->aux; if (f->qid.vers == Vinfo) freeinfo(f->qid.path); if (f->omode != -1 && (f->qid.type & DMDIR) == 0) CloseFile(Sess, a->fh); if ((f->omode != -1) && (f->omode & ORCLOSE) == ORCLOSE) dodelete(a->path); if (a && a->cache) free(a->cache); if (a && a->path) free(a->path); if (a) free(a); f->omode = -1; } static void fsremove(Req *r) { Aux *a = r->fid->aux; if (r->fid->qid.vers == Vinfo){ respond(r, "illegal operation"); return; } if (r->fid->omode != -1 && (r->fid->qid.type & DMDIR) == 0) CloseFile(Sess, a->fh); r->fid->omode = -1; if (dodelete(a->path) == -1){ responderrstr(r); return; } respond(r, nil); } static void fswstat(Req *r) { Mfi mfi; char *p, *npath; int mask = 0; Aux *a = r->fid->aux; if (r->fid->qid.vers == Vinfo){ respond(r, "illegal operation"); return; } memset(&mfi, 0, sizeof mfi); if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){ respond(r, nil); // this change makes replica happier // respond(r, "cannot change ownership"); return; } if(~r->d.mode){ if(((r->d.mode & ~(0777 | DMDIR)) != 0)){ respond(r, "mode not supported"); return; } mfi.attr = (r->d.mode & 0222)? 0: FAreadonly; mfi.attr |= (r->d.mode & DMDIR)? FAdirectory: FAnormal; mask |= MFIattr; } if(~r->d.length){ if (r->d.length > ~0U){ respond(r, "64 bit file offsets not supported"); return; } if (dotruncate(a->path, (ulong)r->d.length) == -1) responderrstr(r); return; } if(~r->d.mtime){ mfi.modified = r->d.mtime; mfi.modifier = Myuid; mask |= MFImodified; } if(~r->d.atime){ mfi.accessed = r->d.atime; mask |= MFIaccessed; } if(r->d.name && r->d.name[0]){ if ((p = strrchr(a->path, '/')) == nil){ respond(r, "illegal path"); return; } npath = emalloc9p((p-a->path)+strlen(r->d.name)+2); strecpy(npath, npath+(p- a->path)+2, a->path); strcat(npath, r->d.name); if (r->fid->omode != -1 && (r->fid->qid.type & DMDIR) == 0) CloseFile(Sess, a->fh); if (dorename(a->path, npath) == -1){ free(npath); responderrstr(r); return; } if (r->fid->omode != -1 && (r->fid->qid.type & DMDIR) == 0) if (doreopen(r->fid) == -1){ respond(r, "reopen after rename - failed"); return; } free(a->path); a->path = npath; } if(mask && ModifyFileOrDirInfo(Sess, NSlong, SAall, mask, &mfi, a->path)){ responderrstr(r); return; } /* * This only works for open files under Netware, * but it might be usefull... */ if (!mask && (!r->d.name || !r->d.name[0]) && r->fid->omode != -1){ if (CommitFile(Sess, a->fh) == -1){ responderrstr(r); return; } } respond(r, nil); } static void fsend(Srv *srv) { USED(srv); Logout(Sess); Detach(Sess); } Srv fs = { .destroyfid = fsdestroyfid, .attach= fsattach, .open= fsopen, .create= fscreate, .read= fsread, .write= fswrite, .remove= fsremove, .stat= fsstat, .wstat= fswstat, .clone= fsclone, .walk1= fswalk1, .end= fsend, }; void usage(void) { fprint(2, "usage: %s [-dD] [-s service] [-m mtpt] [-k keyparam] host\n", argv0); exits("usage"); } void main(int argc, char **argv) { FSInfo fsi; long fstime; UserPasswd *up; ulong nds_uid; int fd, maxpkt; uchar uid[sizeof(nds_uid)], *p; char *addr, *keyp, defmnt[64], *mtpt, *svs, errbuf[64]; uchar chal[NWKEYLEN], tmp[NWKEYLEN *2], key[NWKEYLEN]; svs = nil; keyp = ""; mtpt = defmnt; ARGBEGIN{ case 'd': Debug = 1; break; case 'D': chatty9p++; break; case 'k': keyp = EARGF(usage()); break; case 's': svs = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; default: usage(); }ARGEND if(argc != 1) usage(); strncpy(Host, *argv, sizeof(Host)); fmtinstall('N', ncpfmt); addr = netmkaddr(Host, "tcp", "ncp"); if ((fd = dial(addr, 0, 0, 0)) == -1) sysfatal("cannot dial %s", addr); if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=ncp %s", keyp)) == nil) { fprint(2, "cannot authenticate\n"); exits("auth"); } if ((Sess = Attach(fd)) == nil){ memset(up->passwd, 0, strlen(up->passwd)); fprint(2, "%s - attach failed, %r\n", Host); exits("attach"); } strupr(up->passwd); if (GetLoginKey(Sess, 1, chal) != 0){ /* * As we cannot get the random challange from the server * then we must use the depricated plain-text login */ if (LoginObject(Sess, up->user, OTuser, up->passwd) == -1){ memset(up->passwd, 0, strlen(up->passwd)); rerrstr(errbuf, sizeof(errbuf)); if (strcmp(errbuf, "no q job rights") == 0) fprint(2, "%s:%s - encrypted login only\n", Host, up->user); else fprint(2, "%s:%s login failed - %r\n", Host, up->user); exits("user"); } memset(up->passwd, 0, strlen(up->passwd)); } else{ /* * NB: We ask for the ObjectID _after_ requesting a * our LoginKey and before doing a KeyedObjectLogin. * This hints to the server to give us the NDS ObjectID * (If it is running NDS), nescessary for authentication. * The server specific ObjectID for the user is returned by * GetBinderyObjectId() called at "any other time". */ if (GetBinderyObjectId(Sess, up->user, OTuser, &nds_uid) != 0){ memset(up->passwd, 0, strlen(up->passwd)); rerrstr(errbuf, sizeof(errbuf)); if (strcmp(errbuf, "no such object") == 0) fprint(2, "%s:%s - unknown username\n", Host, up->user); else fprint(2, "%s:%s get userid failed - %r\n", Host, up->user); exits("user"); } p = uid; *(p++) = (nds_uid >> 24) & 0xff; *(p++) = (nds_uid >> 16) & 0xff; *(p++) = (nds_uid >> 8) & 0xff; *(p++) = nds_uid & 0xff; USED(p); shuffle(uid, (uchar *)up->passwd, strlen(up->passwd), tmp); memset(up->passwd, 0, strlen(up->passwd)); nw_encrypt(chal, tmp, key); /* * the "old password" error looks apropriate, and is taken * from the Linux ncpfs code, however my NW 5.0 server returns * "unable to bind" on password expiration (grace logins still apply) */ if (KeyedObjectLogin(Sess, OTuser, (char *)up->user, key) != 0){ rerrstr(errbuf, sizeof(errbuf)); if (strcmp(errbuf, "old password") == 0 || strcmp(errbuf, "unable to bind") == 0){ fprint(2, "%s: warning %s:%s - password expiration imminent\n", argv0, up->user, Host); } else{ fprint(2, "%s: %s:%s - login failed, %r\n", argv0, Host, up->user); exits("login"); } } } maxpkt = MTU; if (NegotiateBufferSize(Sess, &maxpkt) == -1) sysfatal("negotiate MTU - %r"); free(Sess->buf); Sess->mtu = maxpkt; Sess->buf = emalloc9p(Sess->mtu); Maxio = maxpkt - 32; // leave space for packet headers if (GetFileServerDateAndTime(Sess, &fstime) == -1) sysfatal("get FS time - %r"); Slip = fstime - time(nil); if (GetFileServerInfo(Sess, &fsi) == -1) sysfatal("get FS info - %r"); Fsver = fsi.fsvermaj * 1000 + fsi.fsvermin; Bigfiles = fsi.bigfiles; Numvol = fsi.numvol; /* * by doing this now we get the users * bindery user ID rather than their NDS user ID */ if (GetBinderyObjectId(Sess, up->user, OTuser, &Myuid) != 0){ fprint(2, "%s:%s get userid failed - %r\n", Host, up->user); exits("user"); } strcpy(defmnt, "/n/"); strcat(defmnt, *argv); postmountsrv(&fs, svs, mtpt, MREPL); exits(nil); }