#include "u.h" #include #include #include "libc.h" #include "9p.h" #include "stdio.h" #include "setjmp.h" #include "pwd.h" #include "grp.h" #define DBG(f) struct group *getgrent(void); struct passwd *getpwent(void); #ifdef SYSV #include #define DTYPE struct dirent #endif #ifdef V10 #include #define DTYPE struct direct #endif #ifdef BSD #include #define DTYPE struct direct #endif typedef struct File File; typedef struct Rfile Rfile; typedef struct Fd Fd; typedef struct Pass Pass; struct Fd{ int ref; Ulong offset; int fd; DIR *dir; }; struct Rfile{ int busy; int uid; int gid; File *file; Fd *fd; }; struct File{ int ref; char *path; char *name; Qid qid; struct stat stbuf; }; struct Pass{ int id; int gid; char *name; Pass *next; }; char data[2][MAXMSG+MAXFDATA]; char tdata[MAXMSG+MAXFDATA]; char rdata[MAXFDATA]; Fcall rhdr; Fcall thdr; Rfile *rfile; File *file0; int nrfilealloc; jmp_buf loopjmp; Pass* uid[256]; Pass* gid[256]; int devallowed; int connected; void io(void); void error(char*); char *mfmt(Fcall*); void sendmsg(char*); int okfid(int); Rfile* rfilefid(void); File* newfile(void); void* erealloc(void*, unsigned); char* estrdup(char*); char* dostat(File*, char*); char* bldpath(char*, char*, char*); Ulong qid(struct stat*); void errjmp(char*); int omode(int); char* id2name(Pass**, int); Pass* name2pass(Pass**, char*); void getpwdf(void); void getgrpf(void); void perm(Rfile*, int, struct stat*); void parentwrperm(Rfile*); void rauth(void); void rattach(void); void rflush(void); void rclone(void); void rwalk(void); void ropen(void); void rcreate(void); void rread(void); void rwrite(void); void rclunk(int); void rstat(void); void rwstat(void); void rclwalk(void); void rauth(void); char Eauth[] = "authentication failed"; char Eperm[] = "permission denied"; char Ebadfid[] = "fid unknown or out of range"; char Efidactive[] = "fid already in use"; char Eopen[] = "file is open"; char Emode[] = "invalid open mode"; char Especial[] = "no access to special file"; char Especial0[] = "already attached without access to special files"; char Especial1[] = "already attached with access to special files"; char Enotopen[] = "file is not open"; char Etoolarge[] = "i/o count too large"; char Ebaddir[] = "i/o error on directory"; char Eunknown[] = "unknown user or group"; char Euid[] = "can't set uid"; char Egid[] = "can't set gid"; char Eowner[] = "not owner"; int main(int argc, char *argv[]) { freopen(LOG, "a", stderr); setbuf(stderr, (void*)0); DBG(fprintf(stderr, "u9fs\nkill %d\n", getpid())); if(argc > 1) if(chroot(argv[1]) == -1) error("chroot failed"); io(); return 0; } void io(void) { int m; static int toggle, ndata; char *datap; /* * TCP does not preserve record boundaries; this dance works around * the problem. */ setjmp(loopjmp); /* * Invariant: data[toggle] has ndata bytes already */ loop: datap = data[toggle]; toggle ^= 1; for(;;){ if(ndata){ m = convM2S(datap, &rhdr, ndata); /* m is number of bytes more than a full message */ if(m >= 0){ memmove(data[toggle], datap+(ndata-m), m); ndata = m; break; } } m = read(0, datap+ndata, (MAXMSG+MAXFDATA)-ndata); if(m <= 0) error("read"); ndata += m; } thdr.type = rhdr.type+1; thdr.tag = rhdr.tag; thdr.fid = rhdr.fid; DBG(fprintf(stderr, ">> %s\n", mfmt(&rhdr))); switch(rhdr.type){ case Tnop: case Tsession: case Tflush: /* this is a synchronous fs; easy */ break; case Tauth: rauth(); break; case Tattach: rattach(); break; case Tclone: rclone(); break; case Twalk: rwalk(); break; case Tstat: rstat(); break; case Twstat: rwstat(); break; case Topen: ropen(); break; case Tcreate: rcreate(); break; case Tread: rread(); break; case Twrite: rwrite(); break; case Tclunk: rclunk(0); break; case Tremove: rclunk(1); break; default: fprintf(stderr, "unknown message %s\n", mfmt(&rhdr)); error("bad message"); } sendmsg(0); goto loop; } void rauth(void) { sendmsg(Eauth); } void rattach(void) { Rfile *rf; char *err; Pass *p; err = 0; if(file0 == 0){ file0 = newfile(); file0->ref++; /* one extra to hold it up */ file0->path = estrdup("/"); file0->name = estrdup("/"); errjmp(dostat(file0, 0)); } if(!okfid(rhdr.fid)) errjmp(Ebadfid); if(strncmp(rhdr.aname, "device", 6) == 0){ if(connected && !devallowed) errjmp(Especial0); devallowed = 1; }else{ if(connected && devallowed) errjmp(Especial1); } getpwdf(); getgrpf(); rf = &rfile[rhdr.fid]; if(rf->busy) errjmp(Efidactive); rf->busy = 1; rf->file = file0; file0->ref++; p = name2pass(uid, rhdr.uname); if(p == 0) errjmp(Eunknown); rf->uid = p->id; rf->gid = p->gid; thdr.qid = file0->qid; connected = 1; } void rclone(void) { Rfile *rf, *nrf; File *f; rfilefid(); if(!okfid(rhdr.newfid)) errjmp(Ebadfid); rf = &rfile[rhdr.fid]; nrf = &rfile[rhdr.newfid]; f = rf->file; if(nrf->busy) errjmp(Efidactive); nrf->busy = 1; nrf->file = f; f->ref++; nrf->fd = rf->fd; nrf->uid = rf->uid; nrf->gid = rf->gid; if(nrf->fd){ if(nrf->fd->ref == 0) error("clone fd count"); nrf->fd->ref++; } } void rwalk(void) { char *err; Rfile *rf; File *of, *f; rf = rfilefid(); if(rf->fd) errjmp(Eopen); of = rf->file; perm(rf, 1, 0); f = newfile(); f->path = estrdup(of->path); err = dostat(f, rhdr.name); if(err){ f->ref = 0; free(f->path); errjmp(err); } if(of->ref <= 0) error("walk ref count"); if(--of->ref == 0){ free(of->path); free(of->name); free(of); } rf->file = f; thdr.qid = f->qid; } void ropen(void) { Rfile *rf; File *f; int fd; DIR *dir; int m, trunc; rf = rfilefid(); f = rf->file; if(rf->fd) error("open already open"); if(!devallowed && (f->stbuf.st_mode & S_IFCHR)) errjmp(Especial); m = rhdr.mode & (16|3); trunc = m & 16; /* OTRUNC */ switch(m){ case 0: perm(rf, 4, 0); break; case 1: case 1|16: perm(rf, 2, 0); break; case 2: case 0|16: case 2|16: perm(rf, 4, 0); perm(rf, 2, 0); break; case 3: perm(rf, 1, 0); break; default: errjmp(Emode); } m = omode(m & ~16); errno = 0; if(f->qid.path & CHDIR){ if(rhdr.mode != 0) /* OREAD */ errjmp(Eperm); dir = opendir(f->path); if(dir == 0) errjmp(sys_errlist[errno]); fd = 0; }else{ if(trunc){ fd = creat(f->path, 0666); if(fd >= 0) if(m != 1){ close(fd); fd = open(f->path, m); } }else fd = open(f->path, m); if(fd < 0) errjmp(sys_errlist[errno]); dir = 0; } rf->fd = erealloc(0, sizeof(Fd)); rf->fd->ref = 1; rf->fd->fd = fd; rf->fd->dir = dir; rf->fd->offset = 0; thdr.qid = f->qid; } void rcreate(void) { Rfile *rf; File *f, *of; char *path, *err; int fd; int m; char name[NAMELEN]; rf = rfilefid(); if(rf->fd) errjmp(Eopen); perm(rf, 2, 0); path = bldpath(rf->file->path, rhdr.name, name); m = omode(rhdr.mode&~16); errno = 0; if(rhdr.perm & CHDIR){ if(m){ free(path); errjmp(Eperm); } fd = mkdir(path, 0777); if(fd < 0){ free(path); errjmp(sys_errlist[errno]); } fd = open(path, 0); free(path); if(fd >= 0){ fchmod(fd, rhdr.perm&0777); fchown(fd, rf->uid, rf->gid); } }else{ fd = creat(path, 0666); if(fd >= 0){ if(m != 1){ close(fd); fd = open(path, m); } fchmod(fd, rhdr.perm&0777); fchown(fd, rf->uid, rf->gid); } free(path); if(fd < 0) errjmp(sys_errlist[errno]); } f = newfile(); of = rf->file; f->path = estrdup(of->path); err = dostat(f, rhdr.name); if(err){ free(f->path); free(f->name); free(f); errjmp(err); } if(!devallowed && (f->stbuf.st_mode & S_IFCHR)){ free(f->path); free(f->name); free(f); errjmp(Especial); } if(--of->ref == 0){ free(of->path); free(of->name); free(of); } rf->file = f; rf->fd = erealloc(0, sizeof(Fd)); rf->fd->ref = 1; rf->fd->fd = fd; rf->fd->dir = 0; rf->fd->offset = 0; thdr.qid = f->qid; } void rread(void) { Rfile *rf; File *f; long n; DTYPE *de; Dir d; struct stat stbuf; char *path; rf = rfilefid(); if(rf->fd == 0) errjmp(Enotopen); if(rhdr.count > sizeof rdata) errjmp(Etoolarge); f = rf->file; if(rf->fd->dir){ errno = 0; if(rf->fd->offset != rhdr.offset){ seekdir(rf->fd->dir, 0); for(n=0; nfd->dir); if(de == 0) break; if(de->d_ino==0 || de->d_name[0]==0) continue; if(strcmp(de->d_name, ".")==0 || strcmp(de->d_name, "..")==0) continue; n += DIRLEN; } } for(n=0; nfd->dir); if(de == 0) break; if(de->d_ino==0 || de->d_name[0]==0) continue; if(strcmp(de->d_name, ".")==0 || strcmp(de->d_name, "..")==0) continue; strncpy(d.name, de->d_name, NAMELEN-1); d.name[NAMELEN-1] = 0; path = erealloc(0, strlen(f->path)+1+strlen(de->d_name)+1); sprintf(path, "%s/%s", f->path, de->d_name); memset(&stbuf, 0, sizeof stbuf); if(stat(path, &stbuf) < 0){ fprintf(stderr, "dir: bad path %s\n", path); /* but continue... probably a bad symlink */ } free(path); strncpy(d.uid, id2name(uid, stbuf.st_uid), NAMELEN); strncpy(d.gid, id2name(gid, stbuf.st_gid), NAMELEN); d.qid.path = qid(&stbuf); d.qid.vers = 0; d.mode = (d.qid.path&CHDIR)|(stbuf.st_mode&0777); d.atime = stbuf.st_atime; d.mtime = stbuf.st_mtime; d.len.l.hlength = 0; d.len.l.length = stbuf.st_size; convD2M(&d, rdata+n); n += DIRLEN; } }else{ errno = 0; if(rf->fd->offset != rhdr.offset) if(lseek(rf->fd->fd, rhdr.offset, 0) < 0) errjmp(sys_errlist[errno]); n = read(rf->fd->fd, rdata, rhdr.count); if(n < 0) errjmp(sys_errlist[errno]); } rf->fd->offset += n; thdr.count = n; thdr.data = rdata; } void rwrite(void) { Rfile *rf; int n; rf = rfilefid(); if(rf->fd == 0) errjmp(Enotopen); if(rhdr.count > sizeof rdata) errjmp(Etoolarge); errno = 0; if(rf->fd->offset != rhdr.offset) if(lseek(rf->fd->fd, rhdr.offset, 0) < 0) errjmp(sys_errlist[errno]); n = write(rf->fd->fd, rhdr.data, rhdr.count); if(n < 0) errjmp(sys_errlist[errno]); rf->fd->offset += n; thdr.count = n; } void rstat(void) { Rfile *rf; File *f; Dir d; rf = rfilefid(); f = rf->file; errjmp(dostat(f, 0)); strncpy(d.name, f->name, NAMELEN); strncpy(d.uid, id2name(uid, f->stbuf.st_uid), NAMELEN); strncpy(d.gid, id2name(gid, f->stbuf.st_gid), NAMELEN); d.qid = f->qid; d.mode = (f->qid.path&CHDIR)|(f->stbuf.st_mode&0777); d.atime = f->stbuf.st_atime; d.mtime = f->stbuf.st_mtime; d.len.l.hlength = 0; d.len.l.length = f->stbuf.st_size; convD2M(&d, thdr.stat); } void rwstat(void) { Rfile *rf; File *f; Dir d; Pass *p; char *path, *dir, name[NAMELEN]; rf = rfilefid(); f = rf->file; errjmp(dostat(f, 0)); convM2D(rhdr.stat, &d); errno = 0; if(rf->uid != f->stbuf.st_uid) errjmp(Eowner); if(strcmp(d.name, f->name) != 0){ parentwrperm(rf); dir = bldpath(f->path, "..", name); path = erealloc(0, strlen(dir)+1+strlen(d.name)+1); sprintf(path, "%s/%s", dir, d.name); if(link(f->path, path) < 0){ free(path); errjmp(sys_errlist[errno]); } if(unlink(f->path) < 0){ free(path); errjmp(sys_errlist[errno]); } free(f->path); free(f->name); f->path = path; f->name = estrdup(d.name); } if((d.mode&0777) != (f->stbuf.st_mode&0777)){ if(chmod(f->path, d.mode&0777) < 0) errjmp(sys_errlist[errno]); f->stbuf.st_mode &= ~0777; f->stbuf.st_mode |= d.mode&0777; } p = name2pass(gid, d.gid); if(p == 0) errjmp(Eunknown); if(p->id != f->stbuf.st_gid){ if(chown(f->path, f->stbuf.st_uid, p->id) < 0) errjmp(sys_errlist[errno]); f->stbuf.st_gid = p->id; } } void rclunk(int rm) { int ret; char *err; Rfile *rf; File *f; Fd *fd; err = 0; rf = rfilefid(); f = rf->file; if(rm){ parentwrperm(rf); if(f->qid.path & CHDIR) ret = rmdir(f->path); else ret = unlink(f->path); if(ret) err = sys_errlist[errno]; } rf->busy = 0; if(--f->ref == 0){ free(f->path); free(f->name); free(f); } fd = rf->fd; if(fd){ if(fd->ref <= 0) error("clunk fd count"); if(--fd->ref == 0){ if(fd->fd) close(fd->fd); if(fd->dir) closedir(fd->dir); free(fd); } rf->fd = 0; } if(err) errjmp(err); } char* bldpath(char *a, char *elem, char *name) { char *path, *p; if(strcmp(elem, "..") == 0){ if(strcmp(a, "/") == 0){ path = estrdup(a); strcpy(name, a); }else{ p = strrchr(a, '/'); if(p == 0){ fprintf(stderr, "path: '%s'\n", path); error("malformed path 1"); } if(p == a) /* reduced to "/" */ p++; path = erealloc(0, (p-a)+1); memmove(path, a, (p-a)); path[(p-a)] = 0; if(strcmp(path, "/") == 0) p = path; else{ p = strrchr(path, '/'); if(p == 0){ fprintf(stderr, "path: '%s'\n", path); error("malformed path 2"); } p++; } strcpy(name, p); } }else{ if(strcmp(a, "/") == 0) a = ""; path = erealloc(0, strlen(a)+1+strlen(elem)+1); sprintf(path, "%s/%s", a, elem); strcpy(name, elem); } if(strlen(name) >= NAMELEN) error("bldpath: name too long"); return path; } char* dostat(File *f, char *elem) { char *path; struct stat stbuf; char name[NAMELEN]; if(elem) path = bldpath(f->path, elem, name); else path = f->path; errno = 0; if(stat(path, &stbuf) < 0) return sys_errlist[errno]; if(elem){ free(f->path); f->path = path; f->name = estrdup(name); } f->qid.path = qid(&stbuf); f->qid.vers = 0; f->stbuf = stbuf; return 0; } int omode(int m) { switch(m){ case 0: /* OREAD */ case 3: /* OEXEC */ return 0; case 1: /* OWRITE */ return 1; case 2: /* ORDWR */ return 2; } errjmp(Emode); return 0; } void sendmsg(char *err) { int n; if(err){ thdr.type = Rerror; strncpy(thdr.ename, err, ERRLEN); } DBG(fprintf(stderr, "<< %s\n", mfmt(&thdr))); n = convS2M(&thdr, tdata); if(n == 0) error("bad sendmsg format"); if(write(1, tdata, n) != n) error("write error"); } int okfid(int fid) { enum{ Delta=10 }; if(fid < 0){ fprintf(stderr, "u9fs: negative fid %d\n", fid); return 0; } if(fid >= nrfilealloc){ fid += Delta; rfile = erealloc(rfile, fid*sizeof(Rfile)); memset(rfile+nrfilealloc, 0, (fid-nrfilealloc)*sizeof(Rfile)); nrfilealloc = fid; } return 1; } Rfile* rfilefid(void) { Rfile *rf; if(!okfid(rhdr.fid)) errjmp(Ebadfid); rf = &rfile[rhdr.fid]; if(rf->busy == 0) errjmp(Ebadfid); if(rf->file->ref <= 0) error("ref count"); return rf; } void perm(Rfile *rf, int mask, struct stat *st) { if(st == 0) st = &rf->file->stbuf; /* plan 9 access semantics; simpler and more sensible */ if(rf->uid == st->st_uid) if((st->st_mode>>6) & mask) return; if(rf->gid == st->st_gid) if((st->st_mode>>3) & mask) return; if((st->st_mode>>0) & mask) return; errjmp(Eperm); } void parentwrperm(Rfile *rf) { Rfile trf; struct stat st; char *dirp, dir[512]; dirp = bldpath(rf->file->path, "..", dir); if(strlen(dirp) < sizeof dir){ /* ugly: avoid leaving dirp allocated */ strcpy(dir, dirp); free(dirp); dirp = dir; } if(stat(dirp, &st) < 0) errjmp(Eperm); trf.uid = rf->uid; trf.gid = rf->gid; perm(&trf, 2, &st); } File* newfile(void) { File *f; f = erealloc(0, sizeof(File)); memset(f, 0, sizeof(File)); f->ref = 1; return f; } /* * qids: directory bit, seven bits of device, 24 bits of inode */ Ulong qid(struct stat *st) { static int nqdev; static Uchar *qdev; Ulong q; int dev; if(qdev == 0) qdev = erealloc(0, 65536U); q = 0; if(st->st_mode & S_IFDIR) q = CHDIR; dev = st->st_dev & 0xFFFFUL; if(qdev[dev] == 0){ if(++nqdev >= 128) error("too many devices"); qdev[dev] = nqdev; } q |= qdev[dev]<<24; q |= st->st_ino & 0x00FFFFFFUL; return q; } Pass* name2pass(Pass **pw, char *name) { int i; Pass *p; for(i=0; i<256; i++) for(p = pw[i]; p; p = p->next) if(strcmp(name, p->name) == 0) return p; return 0; } char* id2name(Pass **pw, int id) { int i; Pass *p; char *s; static char buf[8]; s = 0; /* use last on list == first in file */ i = (id&0xFF) ^ ((id>>8)&0xFF); for(p = pw[i]; p; p = p->next) if(p->id == id) s = p->name; if(s) return s; sprintf(buf, "%d", id); return buf; } void freepass(Pass **pass) { int i; Pass *p, *np; for(i=0; i<256; i++){ for(p = pass[i]; p; p = np){ np = p->next; free(p); } pass[i] = 0; } } void getpwdf(void) { static mtime; struct stat stbuf; struct passwd *pw; int i; Pass *p; if(stat("/etc/passwd", &stbuf) < 0) error("can't read /etc/passwd"); if(stbuf.st_mtime <= mtime) return; freepass(uid); while(pw = getpwent()){ i = pw->pw_uid; i = (i&0xFF) ^ ((i>>8)&0xFF); p = erealloc(0, sizeof(Pass)); p->next = uid[i]; uid[i] = p; p->id = pw->pw_uid; p->gid = pw->pw_gid; p->name = estrdup(pw->pw_name); } setpwent(); endpwent(); } void getgrpf(void) { static mtime; struct stat stbuf; struct group *pw; int i; Pass *p; if(stat("/etc/group", &stbuf) < 0) error("can't read /etc/group"); if(stbuf.st_mtime <= mtime) return; freepass(gid); while(pw = getgrent()){ i = pw->gr_gid; i = (i&0xFF) ^ ((i>>8)&0xFF); p = erealloc(0, sizeof(Pass)); p->next = gid[i]; gid[i] = p; p->id = pw->gr_gid; p->gid = 0; p->name = estrdup(pw->gr_name); } setgrent(); endgrent(); } void error(char *s) { fprintf(stderr, "u9fs: %s\n", s); perror("unix error"); exit(1); } void errjmp(char *s) { if(s == 0) return; sendmsg(s); longjmp(loopjmp, 1); } void* erealloc(void *p, unsigned n) { if(p == 0) p = malloc(n); else p = realloc(p, n); if(p == 0) error("realloc fail"); return p; } char* estrdup(char *p) { p = strdup(p); if(p == 0) error("strdup fail"); return p; }