ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /emu/Nt/devfs.c/
#define UNICODE
#define Unknown win_Unknown
#include <windows.h>
#include <winbase.h>
#undef Unknown
#undef Sleep
#include "dat.h"
#include "fns.h"
#include "error.h"
#include "r16.h"
#include <lm.h>
/* TODO: try using / in place of \ in path names */
#ifndef SID_MAX_SUB_AUTHORITIES
#define SID_MAX_SUB_AUTHORITIES 15
#endif
enum
{
MAX_SID = sizeof(SID) + SID_MAX_SUB_AUTHORITIES*sizeof(DWORD),
ACL_ROCK = sizeof(ACL) + 20*(sizeof(ACCESS_ALLOWED_ACE)+MAX_SID),
SD_ROCK = SECURITY_DESCRIPTOR_MIN_LENGTH + MAX_SID + ACL_ROCK,
MAXCOMP = 128,
};
typedef struct User User;
typedef struct Gmem Gmem;
typedef struct Stat Stat;
typedef struct Fsinfo Fsinfo;
typedef WIN32_FIND_DATA Fsdir;
#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif
struct Fsinfo
{
int uid;
int gid;
int mode;
int fd;
vlong offset;
QLock oq;
char* spec;
Rune16* srv;
Cname* name; /* Windows' idea of the file name */
ushort usesec;
ushort checksec;
Fsdir* de; /* non-nil for saved entry from last dirread at offset */
};
#define FS(c) ((Fsinfo*)(c)->aux)
/*
* info about a user or group
* there are two ways to specify a user:
* by sid, a unique identifier
* by user and domain names
* this structure is used to convert between the two,
* as well as figure out which groups a users belongs to.
* the user information never gets thrown away,
* but the group information gets refreshed with each setid.
*/
struct User
{
QLock lk; /* locks the gotgroup and group fields */
SID *sid;
Rune16 *name;
Rune16 *dom;
int type; /* the type of sid, ie SidTypeUser, SidTypeAlias, ... */
int gotgroup; /* tried to add group */
Gmem *group; /* global and local groups to which this user or group belongs. */
User *next;
};
struct Gmem
{
User *user;
Gmem *next;
};
/*
* intermediate stat information
*/
struct Stat
{
User *owner;
User *group;
ulong mode;
};
/*
* some "well-known" sids
*/
static SID *creatorowner;
static SID *creatorgroup;
static SID *everyone;
static SID *ntignore;
static SID *ntroot; /* user who is supposed to run emu as a server */
/*
* all users we ever see end up in this table
* users are never deleted, but we should update
* group information for users sometime
*/
static struct
{
QLock lk;
User *u;
}users;
/*
* conversion from inferno permission modes to nt access masks
* is this good enough? this is what nt sets, except for NOMODE
*/
#define NOMODE (READ_CONTROL|FILE_READ_EA|FILE_READ_ATTRIBUTES)
#define RMODE (READ_CONTROL|SYNCHRONIZE\
|FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES)
#define XMODE (READ_CONTROL|SYNCHRONIZE\
|FILE_EXECUTE|FILE_READ_ATTRIBUTES)
#define WMODE (DELETE|READ_CONTROL|SYNCHRONIZE|WRITE_DAC|WRITE_OWNER\
|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA\
|FILE_DELETE_CHILD|FILE_WRITE_ATTRIBUTES)
static int
modetomask[] =
{
NOMODE,
XMODE,
WMODE,
WMODE|XMODE,
RMODE,
RMODE|XMODE,
RMODE|WMODE,
RMODE|WMODE|XMODE,
};
extern DWORD PlatformId;
char rootdir[MAXROOT] = "\\inferno";
Rune16 rootname[] = L"inferno-server";
static Qid rootqid;
static User *fsnone;
static User *fsuser;
static Rune16 *ntsrv;
static int usesec;
static int checksec;
static int isserver;
static int file_share_delete;
static uchar isntfrog[256];
static void fsremove(Chan*);
wchar_t *widen(char *s);
char *narrowen(wchar_t *ws);
int widebytes(wchar_t *ws);
static char Etoolong[] = "file name too long";
extern int nth2fd(HANDLE);
extern HANDLE ntfd2h(int);
static int cnisroot(Cname*);
static int fsisroot(Chan*);
static int okelem(char*, int);
static int fsexist(char*, Qid*);
static char* fspath(Cname*, char*, char*, char*);
static Cname* fswalkpath(Cname*, char*, int);
static char* fslastelem(Cname*);
static long fsdirread(Chan*, uchar*, int, vlong);
static ulong fsqidpath(char*);
static int fsomode(int);
static int fsdirset(char*, int, WIN32_FIND_DATA*, char*, Chan*, int isdir);
static int fsdirsize(WIN32_FIND_DATA*, char*, Chan*);
static void fssettime(char*, long, long);
static long unixtime(FILETIME);
static FILETIME wintime(ulong);
static void secinit(void);
static int secstat(Dir*, char*, Rune16*);
static int secsize(char*, Rune16*);
static void seccheck(char*, ulong, Rune16*);
static int sechasperm(char*, ulong, Rune16*);
static SECURITY_DESCRIPTOR* secsd(char*, char[SD_ROCK]);
static int secsdhasperm(SECURITY_DESCRIPTOR*, ulong, Rune16*);
static int secsdstat(SECURITY_DESCRIPTOR*, Stat*, Rune16*);
static SECURITY_DESCRIPTOR* secmksd(char[SD_ROCK], Stat*, ACL*, int);
static SID *dupsid(SID*);
static int ismembersid(Rune16*, User*, SID*);
static int ismember(User*, User*);
static User *sidtouser(Rune16*, SID*);
static User *domnametouser(Rune16*, Rune16*, Rune16*);
static User *nametouser(Rune16*, Rune16*);
static User *unametouser(Rune16*, char*);
static void addgroups(User*, int);
static User *mkuser(SID*, int, Rune16*, Rune16*);
static Rune16 *domsrv(Rune16 *, Rune16[MAX_PATH]);
static Rune16 *filesrv(char*);
static int fsacls(char*);
static User *secuser(void);
int
winfilematch(char *path, WIN32_FIND_DATA *data)
{
char *p;
wchar_t *wpath;
int r;
p = path+strlen(path);
while(p > path && p[-1] != '\\')
--p;
wpath = widen(p);
r = (data->cFileName[0] == '.' && runes16len(data->cFileName) == 1)
|| runes16cmp(data->cFileName, wpath) == 0;
free(wpath);
return r;
}
int
winfileclash(char *path)
{
HANDLE h;
WIN32_FIND_DATA data;
wchar_t *wpath;
wpath = widen(path);
h = FindFirstFile(wpath, &data);
free(wpath);
if (h != INVALID_HANDLE_VALUE) {
FindClose(h);
return !winfilematch(path, &data);
}
return 0;
}
/*
* this gets called to set up the environment when we switch users
*/
void
setid(char *name, int owner)
{
User *u;
if(owner && !iseve())
return;
kstrdup(&up->env->user, name);
if(!usesec)
return;
u = unametouser(ntsrv, up->env->user);
if(u == nil)
u = fsnone;
else {
qlock(&u->lk);
addgroups(u, 1);
qunlock(&u->lk);
}
if(u == nil)
panic("setid: user nil\n");
up->env->ui = u;
}
static void
fsfree(Chan *c)
{
cnameclose(FS(c)->name);
if(FS(c)->de != nil)
free(FS(c)->de);
free(FS(c));
}
void
fsinit(void)
{
int n, isvol;
ulong attr;
char *p, tmp[MAXROOT];
wchar_t *wp, *wpath, *last;
wchar_t wrootdir[MAXROOT];
isntfrog['/'] = 1;
isntfrog['\\'] = 1;
isntfrog[':'] = 1;
isntfrog['*'] = 1;
isntfrog['?'] = 1;
isntfrog['"'] = 1;
isntfrog['<'] = 1;
isntfrog['>'] = 1;
/*
* vet the root
*/
strcpy(tmp, rootdir);
for(p = tmp; *p; p++)
if(*p == '/')
*p = '\\';
if(tmp[0] != 0 && tmp[1] == ':') {
if(tmp[2] == 0) {
tmp[2] = '\\';
tmp[3] = 0;
}
else if(tmp[2] != '\\') {
/* don't allow c:foo - only c:\foo */
panic("illegal root pathX");
}
}
wrootdir[0] = '\0';
wpath = widen(tmp);
for(wp = wpath; *wp; wp++) {
if(*wp < 32 || (*wp < 256 && isntfrog[*wp] && *wp != '\\' && *wp != ':'))
panic("illegal root path");
}
n = GetFullPathName(wpath, MAXROOT, wrootdir, &last);
free(wpath);
runes16toutf(rootdir, wrootdir, MAXROOT);
if(n >= MAXROOT || n == 0)
panic("illegal root path");
/* get rid of trailing \ */
while(rootdir[n-1] == '\\') {
if(n <= 2) {
panic("illegal root path");
}
rootdir[--n] = '\0';
}
isvol = 0;
if(rootdir[1] == ':' && rootdir[2] == '\0')
isvol = 1;
else if(rootdir[0] == '\\' && rootdir[1] == '\\') {
p = strchr(&rootdir[2], '\\');
if(p == nil)
panic("inferno root can't be a server");
isvol = strchr(p+1, '\\') == nil;
}
if(strchr(rootdir, '\\') == nil)
strcat(rootdir, "\\.");
attr = GetFileAttributes(wrootdir);
if(attr == 0xFFFFFFFF)
panic("root path '%s' does not exist", narrowen(wrootdir));
rootqid.path = fsqidpath(rootdir);
if(attr & FILE_ATTRIBUTE_DIRECTORY)
rootqid.type |= QTDIR;
rootdir[n] = '\0';
rootqid.vers = time(0);
/*
* set up for nt file security checking
*/
ntsrv = filesrv(rootdir);
usesec = PlatformId == VER_PLATFORM_WIN32_NT; /* true for NT and 2000 */
if(usesec){
file_share_delete = FILE_SHARE_DELETE; /* sensible handling of shared files by delete and rename */
secinit();
if(!fsacls(rootdir))
usesec = 0;
}
checksec = usesec && isserver;
}
Chan*
fsattach(char *spec)
{
Chan *c;
static int devno;
static Lock l;
char *drive = (char *)spec;
if (!emptystr(drive) && (drive[1] != ':' || drive[2] != '\0'))
error(Ebadspec);
c = devattach('U', spec);
lock(&l);
c->dev = devno++;
unlock(&l);
c->qid = rootqid;
c->aux = smalloc(sizeof(Fsinfo));
FS(c)->srv = ntsrv;
if(!emptystr(spec)) {
char *s = smalloc(strlen(spec)+1);
strcpy(s, spec);
FS(c)->spec = s;
FS(c)->srv = filesrv(spec);
if(usesec)
FS(c)->usesec = fsacls(spec);
FS(c)->checksec = FS(c)->usesec && isserver;
c->qid.path = fsqidpath(spec);
c->qid.type = QTDIR;
c->qid.vers = 0;
}else{
FS(c)->usesec = usesec;
FS(c)->checksec = checksec;
}
FS(c)->name = newcname("/");
return c;
}
Walkqid*
fswalk(Chan *c, Chan *nc, char **name, int nname)
{
int j, alloc;
Walkqid *wq;
char path[MAX_PATH], *p;
Cname *ph;
Cname *current, *next;
if(nname > 0)
isdir(c);
alloc = 0;
current = nil;
wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
if(waserror()){
if(alloc && wq->clone != nil)
cclose(wq->clone);
cnameclose(current);
free(wq);
return nil;
}
if(nc == nil){
nc = devclone(c);
nc->type = 0;
alloc = 1;
}
wq->clone = nc;
current = FS(c)->name;
if(current != nil)
incref(¤t->r);
for(j = 0; j < nname; j++){
if(!(nc->qid.type&QTDIR)){
if(j==0)
error(Enotdir);
break;
}
if(!okelem(name[j], 0)){
if(j == 0)
error(Efilename);
break;
}
p = fspath(current, name[j], path, FS(c)->spec);
if(FS(c)->checksec) {
*p = '\0';
if(!sechasperm(path, XMODE, FS(c)->srv)){
if(j == 0)
error(Eperm);
break;
}
*p = '\\';
}
if(strcmp(name[j], "..") == 0) {
if(fsisroot(c))
nc->qid = rootqid;
else{
ph = fswalkpath(current, "..", 1);
if(cnisroot(ph)){
nc->qid = rootqid;
current = ph;
if(current != nil)
incref(¤t->r);
}
else {
fspath(ph, 0, path, FS(c)->spec);
if(!fsexist(path, &nc->qid)){
cnameclose(ph);
if(j == 0)
error(Enonexist);
break;
}
}
next = fswalkpath(current, name[j], 1);
cnameclose(current);
current = next;
cnameclose(ph);
}
}
else{
if(!fsexist(path, &nc->qid)){
if(j == 0)
error(Enonexist);
break;
}
next = fswalkpath(current, name[j], 1);
cnameclose(current);
current = next;
}
wq->qid[wq->nqid++] = nc->qid;
}
poperror();
if(wq->nqid < nname){
cnameclose(current);
if(alloc)
cclose(wq->clone);
wq->clone = nil;
}else if(wq->clone){
nc->aux = smalloc(sizeof(Fsinfo));
nc->type = c->type;
FS(nc)->spec = FS(c)->spec;
FS(nc)->srv = FS(c)->srv;
FS(nc)->name = current;
FS(nc)->usesec = FS(c)->usesec;
FS(nc)->checksec = FS(c)->checksec;
}
return wq;
}
Chan*
fsopen(Chan *c, int mode)
{
HANDLE h;
int m, isdir, aflag, cflag;
char path[MAX_PATH];
wchar_t *wpath;
isdir = c->qid.type & QTDIR;
if(isdir && mode != OREAD)
error(Eperm);
fspath(FS(c)->name, 0, path, FS(c)->spec);
if(FS(c)->checksec) {
switch(mode & (OTRUNC|3)) {
case OREAD:
seccheck(path, RMODE, FS(c)->srv);
break;
case OWRITE:
case OWRITE|OTRUNC:
seccheck(path, WMODE, FS(c)->srv);
break;
case ORDWR:
case ORDWR|OTRUNC:
case OREAD|OTRUNC:
seccheck(path, RMODE|WMODE, FS(c)->srv);
break;
case OEXEC:
seccheck(path, XMODE, FS(c)->srv);
break;
default:
error(Ebadarg);
}
}
c->mode = openmode(mode);
if(isdir)
FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
else {
m = fsomode(mode & 3);
cflag = OPEN_EXISTING;
if(mode & OTRUNC)
cflag = TRUNCATE_EXISTING;
aflag = FILE_FLAG_RANDOM_ACCESS;
if(mode & ORCLOSE)
aflag |= FILE_FLAG_DELETE_ON_CLOSE;
if (winfileclash(path))
error(Eexist);
wpath = widen(path);
h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, 0, cflag, aflag, 0);
free(wpath);
if(h == INVALID_HANDLE_VALUE)
oserror();
FS(c)->fd = nth2fd(h);
}
c->offset = 0;
FS(c)->offset = 0;
c->flag |= COPEN;
return c;
}
void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
Stat st;
HANDLE h;
int m, aflag;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR *sd;
BY_HANDLE_FILE_INFORMATION hi;
char *p, path[MAX_PATH], sdrock[SD_ROCK];
wchar_t *wpath;
ACL *acl;
if(!okelem(name, 1))
error(Efilename);
m = fsomode(mode & 3);
p = fspath(FS(c)->name, name, path, FS(c)->spec);
acl = (ACL*)smalloc(ACL_ROCK);
sd = nil;
if(FS(c)->usesec) {
*p = '\0';
sd = secsd(path, sdrock);
*p = '\\';
if(sd == nil){
free(acl);
oserror();
}
if(FS(c)->checksec && !secsdhasperm(sd, WMODE, FS(c)->srv)
|| !secsdstat(sd, &st, FS(c)->srv)){
if(sd != (void*)sdrock)
free(sd);
free(acl);
error(Eperm);
}
if(sd != (void*)sdrock)
free(sd);
if(perm & DMDIR)
st.mode = (perm & ~0777) | (st.mode & perm & 0777);
else
st.mode = (perm & ~0666) | (st.mode & perm & 0666);
st.owner = up->env->ui;
if(!isserver)
st.owner = fsuser;
sd = secmksd(sdrock, &st, acl, perm & DMDIR);
if(sd == nil){
free(acl);
oserror();
}
}
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = sd;
sa.bInheritHandle = 0;
if(perm & DMDIR) {
if(mode != OREAD) {
free(acl);
error(Eisdir);
}
wpath = widen(path);
if(!CreateDirectory(wpath, &sa) || !fsexist(path, &c->qid)) {
free(wpath);
free(acl);
oserror();
}
free(wpath);
FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
}
else {
aflag = 0;
if(mode & ORCLOSE)
aflag = FILE_FLAG_DELETE_ON_CLOSE;
if (winfileclash(path))
error(Eexist);
wpath = widen(path);
h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, CREATE_ALWAYS, aflag, 0);
free(wpath);
if(h == INVALID_HANDLE_VALUE) {
free(acl);
oserror();
}
FS(c)->fd = nth2fd(h);
c->qid.path = fsqidpath(path);
c->qid.type = 0;
c->qid.vers = 0;
if(GetFileInformationByHandle(h, &hi))
c->qid.vers = unixtime(hi.ftLastWriteTime);
}
c->mode = openmode(mode);
c->offset = 0;
FS(c)->offset = 0;
c->flag |= COPEN;
FS(c)->name = fswalkpath(FS(c)->name, name, 0);
free(acl);
}
void
fsclose(Chan *c)
{
HANDLE h;
if(c->flag & COPEN){
h = ntfd2h(FS(c)->fd);
if(h != INVALID_HANDLE_VALUE){
if(c->qid.type & QTDIR)
FindClose(h);
else
CloseHandle(h);
}
}
if(c->flag & CRCLOSE){
if(!waserror()){
fsremove(c);
poperror();
}
return;
}
fsfree(c);
}
/*
* 64-bit seeks, using SetFilePointer because SetFilePointerEx
* is not supported by NT
*/
static void
fslseek(HANDLE h, vlong offset)
{
LONG hi;
if(offset <= 0x7fffffff){
if(SetFilePointer(h, (LONG)offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
oserror();
}else{
hi = offset>>32;
if(SetFilePointer(h, (LONG)offset, &hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER &&
GetLastError() != NO_ERROR)
oserror();
}
}
long
fsread(Chan *c, void *va, long n, vlong offset)
{
DWORD n2;
HANDLE h;
qlock(&FS(c)->oq);
if(waserror()){
qunlock(&FS(c)->oq);
nexterror();
}
if(c->qid.type & QTDIR) {
n2 = fsdirread(c, va, n, offset);
}
else {
h = ntfd2h(FS(c)->fd);
if(FS(c)->offset != offset){
fslseek(h, offset);
FS(c)->offset = offset;
}
if(!ReadFile(h, va, n, &n2, NULL))
oserror();
FS(c)->offset += n2;
}
qunlock(&FS(c)->oq);
poperror();
return n2;
}
long
fswrite(Chan *c, void *va, long n, vlong offset)
{
DWORD n2;
HANDLE h;
qlock(&FS(c)->oq);
if(waserror()){
qunlock(&FS(c)->oq);
nexterror();
}
h = ntfd2h(FS(c)->fd);
if(FS(c)->offset != offset){
fslseek(h, offset);
FS(c)->offset = offset;
}
if(!WriteFile(h, va, n, &n2, NULL))
oserror();
FS(c)->offset += n2;
qunlock(&FS(c)->oq);
poperror();
return n2;
}
int
fsstat(Chan *c, uchar *buf, int n)
{
WIN32_FIND_DATA data;
char path[MAX_PATH];
wchar_t *wpath;
/*
* have to fake up a data for volumes like
* c: and \\server\share since you can't FindFirstFile them
*/
if(fsisroot(c)){
strcpy(path, rootdir);
if(strchr(path, '\\') == nil)
strcat(path, "\\.");
wpath = widen(path);
data.dwFileAttributes = GetFileAttributes(wpath);
free(wpath);
if(data.dwFileAttributes == 0xffffffff)
oserror();
data.ftCreationTime =
data.ftLastAccessTime =
data.ftLastWriteTime = wintime(time(0));
data.nFileSizeHigh = 0;
data.nFileSizeLow = 0;
utftorunes16(data.cFileName, ".", MAX_PATH);
} else {
HANDLE h = INVALID_HANDLE_VALUE;
fspath(FS(c)->name, 0, path, FS(c)->spec);
if (c->flag & COPEN)
h = ntfd2h(FS(c)->fd);
if (h != INVALID_HANDLE_VALUE) {
BY_HANDLE_FILE_INFORMATION fi;
if (c->mode & OWRITE)
FlushFileBuffers(h);
if (!GetFileInformationByHandle(h, &fi))
oserror();
data.dwFileAttributes = fi.dwFileAttributes;
data.ftCreationTime = fi.ftCreationTime;
data.ftLastAccessTime = fi.ftLastAccessTime;
data.ftLastWriteTime = fi.ftLastWriteTime;;
data.nFileSizeHigh = fi.nFileSizeHigh;
data.nFileSizeLow = fi.nFileSizeLow;
} else {
wpath = widen(path);
h = FindFirstFile(wpath, &data);
free(wpath);
if(h == INVALID_HANDLE_VALUE)
oserror();
if (!winfilematch(path, &data)) {
FindClose(h);
error(Enonexist);
}
FindClose(h);
}
utftorunes16(data.cFileName, fslastelem(FS(c)->name), MAX_PATH);
}
return fsdirset(buf, n, &data, path, c, 0);
}
int
fswstat(Chan *c, uchar *buf, int n)
{
int wsd;
Dir dir;
Stat st;
Cname * volatile ph;
HANDLE h;
ulong attr;
User *ou, *gu;
WIN32_FIND_DATA data;
SECURITY_DESCRIPTOR *sd;
char *last, sdrock[SD_ROCK], path[MAX_PATH], newpath[MAX_PATH], strs[4*256];
wchar_t wspath[MAX_PATH], wsnewpath[MAX_PATH];
wchar_t *wpath;
int nmatch;
n = convM2D(buf, n, &dir, strs);
if(n == 0)
error(Eshortstat);
last = fspath(FS(c)->name, 0, path, FS(c)->spec);
utftorunes16(wspath, path, MAX_PATH);
if(fsisroot(c)){
if(dir.atime != ~0)
data.ftLastAccessTime = wintime(dir.atime);
if(dir.mtime != ~0)
data.ftLastWriteTime = wintime(dir.mtime);
utftorunes16(data.cFileName, ".", MAX_PATH);
}else{
h = FindFirstFile(wspath, &data);
if(h == INVALID_HANDLE_VALUE)
oserror();
if (!winfilematch(path, &data)) {
FindClose(h);
error(Enonexist);
}
FindClose(h);
}
wsd = 0;
ou = nil;
gu = nil;
if(FS(c)->usesec) {
if(FS(c)->checksec && up->env->ui == fsnone)
error(Eperm);
/*
* find new owner and group
*/
if(!emptystr(dir.uid)){
ou = unametouser(FS(c)->srv, dir.uid);
if(ou == nil)
oserror();
}
if(!emptystr(dir.gid)){
gu = unametouser(FS(c)->srv, dir.gid);
if(gu == nil){
if(strcmp(dir.gid, "unknown") != 0
&& strcmp(dir.gid, "deleted") != 0)
oserror();
gu = ou;
}
}
/*
* find old stat info
*/
sd = secsd(path, sdrock);
if(sd == nil || !secsdstat(sd, &st, FS(c)->srv)){
if(sd != nil && sd != (void*)sdrock)
free(sd);
oserror();
}
if(sd != (void*)sdrock)
free(sd);
/*
* permission rules:
* if none, can't do anything
* chown => no way
* chgrp => current owner or group, and in new group
* mode/time => owner or in either group
* rename => write in parent
*/
if(ou == nil)
ou = st.owner;
if(FS(c)->checksec && st.owner != ou)
error(Eperm);
if(gu == nil)
gu = st.group;
if(st.group != gu){
if(FS(c)->checksec
&&(!ismember(up->env->ui, ou) && !ismember(up->env->ui, gu)
|| !ismember(up->env->ui, st.group)))
error(Eperm);
wsd = 1;
}
if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime
|| dir.mode != ~0 && st.mode != dir.mode){
if(FS(c)->checksec
&& !ismember(up->env->ui, ou)
&& !ismember(up->env->ui, gu)
&& !ismember(up->env->ui, st.group))
error(Eperm);
if(dir.mode != ~0 && st.mode != dir.mode)
wsd = 1;
}
}
wpath = widen(dir.name);
nmatch = runes16cmp(wpath, data.cFileName);
free(wpath);
if(!emptystr(dir.name) && nmatch != 0){
if(!okelem(dir.name, 1))
error(Efilename);
ph = fswalkpath(FS(c)->name, "..", 1);
if(waserror()){
cnameclose(ph);
nexterror();
}
ph = fswalkpath(ph, dir.name, 0);
fspath(ph, 0, newpath, FS(c)->spec);
utftorunes16(wsnewpath, newpath, MAX_PATH);
if(GetFileAttributes(wpath) != 0xffffffff && !winfileclash(newpath))
error("file already exists");
if(fsisroot(c))
error(Eperm);
if(FS(c)->checksec){
*last = '\0';
seccheck(path, WMODE, FS(c)->srv);
*last = '\\';
}
poperror();
cnameclose(ph);
}
if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime)
fssettime(path, dir.atime, dir.mtime);
attr = data.dwFileAttributes;
if(dir.mode & 0222)
attr &= ~FILE_ATTRIBUTE_READONLY;
else
attr |= FILE_ATTRIBUTE_READONLY;
if(!fsisroot(c)
&& attr != data.dwFileAttributes
&& (attr & FILE_ATTRIBUTE_READONLY))
SetFileAttributes(wspath, attr);
if(FS(c)->usesec && wsd){
ACL *acl = (ACL *) smalloc(ACL_ROCK);
st.owner = ou;
st.group = gu;
if(dir.mode != ~0)
st.mode = dir.mode;
sd = secmksd(sdrock, &st, acl, data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
if(sd == nil || !SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd)){
free(acl);
oserror();
}
free(acl);
}
if(!fsisroot(c)
&& attr != data.dwFileAttributes
&& !(attr & FILE_ATTRIBUTE_READONLY))
SetFileAttributes(wspath, attr);
/* do last so path is valid throughout */
wpath = widen(dir.name);
nmatch = runes16cmp(wpath, data.cFileName);
free(wpath);
if(!emptystr(dir.name) && nmatch != 0) {
ph = fswalkpath(FS(c)->name, "..", 1);
if(waserror()){
cnameclose(ph);
nexterror();
}
ph = fswalkpath(ph, dir.name, 0);
fspath(ph, 0, newpath, FS(c)->spec);
utftorunes16(wsnewpath, newpath, MAX_PATH);
/*
* can't rename if it is open: if this process has it open, close it temporarily.
*/
if(!file_share_delete && c->flag & COPEN){
h = ntfd2h(FS(c)->fd);
if(h != INVALID_HANDLE_VALUE)
CloseHandle(h); /* woe betide it if ORCLOSE */
FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
}
if(!MoveFile(wspath, wsnewpath)) {
oserror();
} else if(!file_share_delete && c->flag & COPEN) {
int aflag;
SECURITY_ATTRIBUTES sa;
/* The move succeeded, so open new file to maintain handle */
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = sd;
sa.bInheritHandle = 0;
if(c->flag & CRCLOSE)
aflag = FILE_FLAG_DELETE_ON_CLOSE;
h = CreateFile(wsnewpath, fsomode(c->mode & 0x3), FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, OPEN_EXISTING, aflag, 0);
if(h == INVALID_HANDLE_VALUE)
oserror();
FS(c)->fd = nth2fd(h);
}
cnameclose(FS(c)->name);
poperror();
FS(c)->name = ph;
}
return n;
}
static void
fsremove(Chan *c)
{
int n;
char *p, path[MAX_PATH];
wchar_t wspath[MAX_PATH];
if(waserror()){
fsfree(c);
nexterror();
}
if(fsisroot(c))
error(Eperm);
p = fspath(FS(c)->name, 0, path, FS(c)->spec);
utftorunes16(wspath, path, MAX_PATH);
if(FS(c)->checksec){
*p = '\0';
seccheck(path, WMODE, FS(c)->srv);
*p = '\\';
}
if(c->qid.type & QTDIR)
n = RemoveDirectory(wspath);
else
n = DeleteFile(wspath);
if (!n) {
ulong attr, mode;
SECURITY_DESCRIPTOR *sd = nil;
char sdrock[SD_ROCK];
Stat st;
int secok;
attr = GetFileAttributes(wspath);
if(attr != 0xFFFFFFFF) {
if (FS(c)->usesec) {
sd = secsd(path, sdrock);
secok = (sd != nil) && secsdstat(sd, &st, FS(c)->srv);
if (secok) {
ACL *acl = (ACL *) smalloc(ACL_ROCK);
mode = st.mode;
st.mode |= 0660;
sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
if(sd != nil) {
SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
}
free(acl);
if(sd != nil && sd != (void*)sdrock)
free(sd);
sd = nil;
}
}
SetFileAttributes(wspath, FILE_ATTRIBUTE_NORMAL);
if(c->qid.type & QTDIR)
n = RemoveDirectory(wspath);
else
n = DeleteFile(wspath);
if (!n) {
if (FS(c)->usesec && secok) {
ACL *acl = (ACL *) smalloc(ACL_ROCK);
st.mode = mode;
sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
if(sd != nil) {
SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
}
free(acl);
}
SetFileAttributes(wspath, attr);
if(sd != nil && sd != (void*)sdrock)
free(sd);
}
}
}
if(!n)
oserror();
poperror();
fsfree(c);
}
/*
* check elem for illegal characters /\:*?"<>
* ... and relatives are also disallowed,
* since they specify grandparents, which we
* are not prepared to handle
*/
static int
okelem(char *elem, int nodots)
{
int c, dots;
dots = 0;
while((c = *(uchar*)elem) != 0){
if(isntfrog[c])
return 0;
if(c == '.' && dots >= 0)
dots++;
else
dots = -1;
elem++;
}
if(nodots)
return dots <= 0;
return dots <= 2;
}
static int
cnisroot(Cname *c)
{
return strcmp(c->s, "/") == 0;
}
static int
fsisroot(Chan *c)
{
return strcmp(FS(c)->name->s, "/") == 0;
}
static char*
fspath(Cname *c, char *ext, char *path, char *spec)
{
char *p, *last, *rootd;
int extlen = 0;
rootd = spec != nil ? spec : rootdir;
if(ext)
extlen = strlen(ext) + 1;
if(strlen(rootd) + extlen >= MAX_PATH)
error(Etoolong);
strcpy(path, rootd);
if(cnisroot(c)){
if(ext) {
strcat(path, "\\");
strcat(path, ext);
}
}else{
if(*c->s != '/') {
if(strlen(path) + 1 >= MAX_PATH)
error(Etoolong);
strcat(path, "\\");
}
if(strlen(path) + strlen(c->s) + extlen >= MAX_PATH)
error(Etoolong);
strcat(path, c->s);
if(ext){
strcat(path, "\\");
strcat(path, ext);
}
}
last = path;
for(p = path; *p != '\0'; p++){
if(*p == '/' || *p == '\\'){
*p = '\\';
last = p;
}
}
return last;
}
extern void cleancname(Cname*);
static Cname *
fswalkpath(Cname *c, char *name, int dup)
{
if(dup)
c = newcname(c->s);
c = addelem(c, name);
if(isdotdot(name))
cleancname(c);
return c;
}
static char *
fslastelem(Cname *c)
{
char *p;
p = c->s + c->len;
while(p > c->s && p[-1] != '/')
p--;
return p;
}
static int
fsdirbadentry(WIN32_FIND_DATA *data)
{
wchar_t *s;
s = data->cFileName;
if(s[0] == 0)
return 1;
if(s[0] == '.' && (s[1] == 0 || s[1] == '.' && s[2] == 0))
return 1;
return 0;
}
static Fsdir*
fsdirent(Chan *c, char *path, Fsdir *data)
{
wchar_t *wpath;
HANDLE h;
h = ntfd2h(FS(c)->fd);
if(data == nil)
data = smalloc(sizeof(*data));
if(FS(c)->offset == 0){
if(h != INVALID_HANDLE_VALUE)
FindClose(h);
wpath = widen(path);
h = FindFirstFile(wpath, data);
free(wpath);
FS(c)->fd = nth2fd(h);
if(h == INVALID_HANDLE_VALUE){
free(data);
return nil;
}
if(!fsdirbadentry(data))
return data;
}
do{
if(!FindNextFile(h, data)){
free(data);
return nil;
}
}while(fsdirbadentry(data));
return data;
}
static long
fsdirread(Chan *c, uchar *va, int count, vlong offset)
{
int i, r;
char path[MAX_PATH], *p;
Fsdir *de;
vlong o;
if(count == 0 || offset < 0)
return 0;
p = fspath(FS(c)->name, "*.*", path, FS(c)->spec);
p++;
de = nil;
if(FS(c)->offset != offset){
de = FS(c)->de;
if(FS(c)->de != nil){
free(FS(c)->de);
FS(c)->de = nil;
}
FS(c)->offset = 0;
for(o = 0; o < offset;){
de = fsdirent(c, path, de);
if(de == nil){
FS(c)->offset = o;
return 0;
}
runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
path[MAX_PATH-1] = '\0';
o += fsdirsize(de, path, c);
}
FS(c)->offset = offset;
}
for(i = 0; i < count;){
if(FS(c)->de != nil){ /* left over from previous read at offset */
de = FS(c)->de;
FS(c)->de = nil;
}else{
de = fsdirent(c, path, de);
if(de == nil)
break;
}
runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
path[MAX_PATH-1] = '\0';
r = fsdirset(va+i, count-i, de, path, c, 1);
if(r <= 0){
/* won't fit; save for next read at this offset */
FS(c)->de = de;
break;
}
i += r;
FS(c)->offset += r;
}
return i;
}
static ulong
fsqidpath(char *p)
{
ulong h;
int c;
h = 0;
while(*p != '\0'){
/* force case insensitive file names */
c = *p++;
if(c >= 'A' && c <= 'Z')
c += 'a'-'A';
h = h * 19 ^ c;
}
return h;
}
/* TO DO: substitute fixed, made-up (unlikely) names for these */
static char* devf[] = { "aux", "com1", "com2", "lpt1", "nul", "prn", nil };
static int
devfile(char *p)
{
char *s, *t, *u, **ss;
if((u = strrchr(p, '\\')) != nil)
u++;
else if((u = strrchr(p, '/')) != nil)
u++;
else
u = p;
for(ss = devf; *ss != nil; ss++){
for(s = *ss, t = u; *s != '\0' && *t != '\0' && *t != '.'; s++, t++)
if(*s != *t && *s != *t+'a'-'A')
break;
if(*s == '\0' && (*t == '\0' || *t == '.'))
return 1;
}
return 0;
}
/*
* there are other ways to figure out
* the attributes and times for a file.
* perhaps they are faster
*/
static int
fsexist(char *p, Qid *q)
{
HANDLE h;
WIN32_FIND_DATA data;
wchar_t *wpath;
if(devfile(p))
return 0;
wpath = widen(p);
h = FindFirstFile(wpath, &data);
free(wpath);
if(h == INVALID_HANDLE_VALUE)
return 0;
if (!winfilematch(p, &data)) {
FindClose(h);
return 0;
}
FindClose(h);
q->path = fsqidpath(p);
q->type = 0;
if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
q->type |= QTDIR;
q->vers = unixtime(data.ftLastWriteTime);
return 1;
}
static int
fsdirset(char *edir, int n, WIN32_FIND_DATA *data, char *path, Chan *c, int isdir)
{
Dir dir;
static char neveryone[] = "Everyone";
dir.name = narrowen(data->cFileName);
dir.muid = nil;
dir.qid.path = fsqidpath(path);
dir.qid.vers = 0;
dir.qid.type = 0;
dir.mode = 0;
dir.atime = unixtime(data->ftLastAccessTime);
dir.mtime = unixtime(data->ftLastWriteTime);
dir.qid.vers = dir.mtime;
dir.length = ((uvlong)data->nFileSizeHigh<<32) | ((uvlong)data->nFileSizeLow & ~((uvlong)0xFFFFFFFF<<32));
dir.type = 'U';
dir.dev = c->dev;
if(!FS(c)->usesec){
/* no NT security so make something up */
dir.uid = neveryone;
dir.gid = neveryone;
dir.mode = 0777;
}else if(!secstat(&dir, path, FS(c)->srv))
oserror();
if(data->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
dir.mode &= ~0222;
if(data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
dir.qid.type |= QTDIR;
dir.mode |= DMDIR;
dir.length = 0;
}
if(isdir && sizeD2M(&dir) > n)
n = -1;
else
n = convD2M(&dir, edir, n);
if(dir.uid != neveryone)
free(dir.uid);
if(dir.gid != neveryone)
free(dir.gid);
free(dir.name);
return n;
}
static int
fsdirsize(WIN32_FIND_DATA *data, char *path, Chan *c)
{
int i, n;
n = widebytes(data->cFileName);
if(!FS(c)->usesec)
n += 8+8;
else{
i = secsize(path, FS(c)->srv);
if(i < 0)
oserror();
n += i;
}
return STATFIXLEN+n;
}
static void
fssettime(char *path, long at, long mt)
{
HANDLE h;
FILETIME atime, mtime;
wchar_t *wpath;
wpath = widen(path);
h = CreateFile(wpath, GENERIC_WRITE,
0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
free(wpath);
if(h == INVALID_HANDLE_VALUE)
return;
mtime = wintime(mt);
atime = wintime(at);
if(!SetFileTime(h, 0, &atime, &mtime)){
CloseHandle(h);
oserror();
}
CloseHandle(h);
}
static int
fsomode(int m)
{
switch(m & 0x3) {
case OREAD:
case OEXEC:
return GENERIC_READ;
case OWRITE:
return GENERIC_WRITE;
case ORDWR:
return GENERIC_READ|GENERIC_WRITE;
}
error(Ebadarg);
return 0;
}
static long
unixtime(FILETIME ft)
{
vlong t;
t = (vlong)ft.dwLowDateTime + ((vlong)ft.dwHighDateTime<<32);
t -= (vlong)10000000*134774*24*60*60;
return (long)(t/10000000);
}
static FILETIME
wintime(ulong t)
{
FILETIME ft;
vlong vt;
vt = (vlong)t*10000000+(vlong)10000000*134774*24*60*60;
ft.dwLowDateTime = vt;
ft.dwHighDateTime = vt>>32;
return ft;
}
/*
* the sec routines manage file permissions for nt.
* nt files have an associated security descriptor,
* which has in it an owner, a group,
* and a discretionary acces control list, or acl,
* which specifies the permissions for the file.
*
* the strategy for mapping between inferno owner,
* group, other, and mode and nt file security is:
*
* inferno owner == nt file owner
* inferno other == nt Everyone
* inferno group == first non-owner,
* non-Everyone user given in the acl,
* or the owner if there is no such user.
* we examine the entire acl when check for permissions,
* but only report a subset.
*
* when we write an acl, we also give all permissions to
* the special user rootname, who is supposed to run emu in server mode.
*/
static void
secinit(void)
{
HANDLE token;
TOKEN_PRIVILEGES *priv;
char privrock[sizeof(TOKEN_PRIVILEGES) + 1*sizeof(LUID_AND_ATTRIBUTES)];
SID_IDENTIFIER_AUTHORITY id = SECURITY_CREATOR_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY wid = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY ntid = SECURITY_NT_AUTHORITY;
if(!AllocateAndInitializeSid(&id, 1,
SECURITY_CREATOR_OWNER_RID,
1, 2, 3, 4, 5, 6, 7, &creatorowner)
|| !AllocateAndInitializeSid(&id, 1,
SECURITY_CREATOR_GROUP_RID,
1, 2, 3, 4, 5, 6, 7, &creatorgroup)
|| !AllocateAndInitializeSid(&wid, 1,
SECURITY_WORLD_RID,
1, 2, 3, 4, 5, 6, 7, &everyone)
|| !AllocateAndInitializeSid(&ntid, 1,
0,
1, 2, 3, 4, 5, 6, 7, &ntignore))
panic("can't initialize well-known sids");
fsnone = sidtouser(ntsrv, everyone);
if(fsnone == nil)
panic("can't make none user");
/*
* see if we are running as the emu server user
* if so, set up SE_RESTORE_NAME privilege,
* which allows setting the owner field in a security descriptor.
* other interesting privileges are SE_TAKE_OWNERSHIP_NAME,
* which enables changing the ownership of a file to yourself
* regardless of the permissions on the file, SE_BACKUP_NAME,
* which enables reading any files regardless of permission,
* and SE_CHANGE_NOTIFY_NAME, which enables walking through
* directories without X permission.
* SE_RESTORE_NAME and SE_BACKUP_NAME together allow writing
* and reading any file data, regardless of permission,
* if the file is opened with FILE_BACKUP_SEMANTICS.
*/
isserver = 0;
fsuser = secuser();
if(fsuser == nil)
fsuser = fsnone;
else if(runes16cmp(fsuser->name, rootname) == 0
&& OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)){
priv = (TOKEN_PRIVILEGES*)privrock;
priv->PrivilegeCount = 1;
priv->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &priv->Privileges[0].Luid)
&& AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL))
isserver = 1;
CloseHandle(token);
}
}
/*
* get the User for the executing process
*/
static User*
secuser(void)
{
DWORD need;
HANDLE token;
TOKEN_USER *tu;
char turock[sizeof(TOKEN_USER) + MAX_SID];
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
return nil;
tu = (TOKEN_USER*)turock;
if(!GetTokenInformation(token, TokenUser, tu, sizeof(turock), &need)){
CloseHandle(token);
return nil;
}
CloseHandle(token);
return sidtouser(nil, tu->User.Sid);
}
static int
secstat(Dir *dir, char *file, Rune16 *srv)
{
int ok, n;
Stat st;
char sdrock[SD_ROCK];
SECURITY_DESCRIPTOR *sd;
sd = secsd(file, sdrock);
if(sd == nil){
int e = GetLastError();
if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION){
dir->uid = strdup("unknown");
dir->gid = strdup("unknown");
if(dir->uid == nil || dir->gid == nil){
free(dir->uid);
error(Enomem); /* will change to use kstrdup */
}
dir->mode = 0;
return 1;
}
return 0;
}
ok = secsdstat(sd, &st, srv);
if(sd != (void*)sdrock)
free(sd);
if(ok){
dir->mode = st.mode;
n = rune16nlen(st.owner->name, runes16len(st.owner->name));
dir->uid = smalloc(n+1);
runes16toutf(dir->uid, st.owner->name, n+1);
n = rune16nlen(st.group->name, runes16len(st.group->name));
dir->gid = smalloc(n+1);
runes16toutf(dir->gid, st.group->name, n+1);
}
return ok;
}
static int
secsize(char *file, Rune16 *srv)
{
int ok;
Stat st;
char sdrock[SD_ROCK];
SECURITY_DESCRIPTOR *sd;
sd = secsd(file, sdrock);
if(sd == nil){
int e = GetLastError();
if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION)
return 7+7;
return -1;
}
ok = secsdstat(sd, &st, srv);
if(sd != (void*)sdrock)
free(sd);
if(ok)
return rune16nlen(st.owner->name, runes16len(st.owner->name))+rune16nlen(st.group->name, runes16len(st.group->name));
return -1;
}
/*
* verify that u had access to file
*/
static void
seccheck(char *file, ulong access, Rune16 *srv)
{
if(!sechasperm(file, access, srv))
error(Eperm);
}
static int
sechasperm(char *file, ulong access, Rune16 *srv)
{
int ok;
char sdrock[SD_ROCK];
SECURITY_DESCRIPTOR *sd;
/*
* only really needs dacl info
*/
sd = secsd(file, sdrock);
if(sd == nil)
return 0;
ok = secsdhasperm(sd, access, srv);
if(sd != (void*)sdrock)
free(sd);
return ok;
}
static SECURITY_DESCRIPTOR*
secsd(char *file, char sdrock[SD_ROCK])
{
DWORD need;
SECURITY_DESCRIPTOR *sd;
char *path, pathrock[6];
wchar_t *wpath;
path = file;
if(path[0] != '\0' && path[1] == ':' && path[2] == '\0'){
path = pathrock;
strcpy(path, "?:\\.");
path[0] = file[0];
}
sd = (SECURITY_DESCRIPTOR*)sdrock;
need = 0;
wpath = widen(path);
if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, SD_ROCK, &need)) {
free(wpath);
return sd;
}
if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
free(wpath);
return nil;
}
sd = malloc(need);
if(sd == nil) {
free(wpath);
error(Enomem);
}
if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, need, &need)) {
free(wpath);
return sd;
}
free(wpath);
free(sd);
return nil;
}
static int
secsdstat(SECURITY_DESCRIPTOR *sd, Stat *st, Rune16 *srv)
{
ACL *acl;
BOOL hasacl, b;
ACE_HEADER *aceh;
User *owner, *group;
SID *sid, *osid, *gsid;
ACCESS_ALLOWED_ACE *ace;
int i, allow, deny, *p, m;
ACL_SIZE_INFORMATION size;
st->mode = 0;
osid = nil;
gsid = nil;
if(!GetSecurityDescriptorOwner(sd, &osid, &b)
|| !GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
return 0;
if(acl == 0)
size.AceCount = 0;
else if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
return 0;
/*
* first pass through acl finds group
*/
for(i = 0; i < size.AceCount; i++){
if(!GetAce(acl, i, &aceh))
continue;
if(aceh->AceFlags & INHERIT_ONLY_ACE)
continue;
if(aceh->AceType != ACCESS_ALLOWED_ACE_TYPE
&& aceh->AceType != ACCESS_DENIED_ACE_TYPE)
continue;
ace = (ACCESS_ALLOWED_ACE*)aceh;
sid = (SID*)&ace->SidStart;
if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
continue;
if(EqualSid(sid, everyone))
;
else if(EqualSid(sid, osid))
;
else if(EqualPrefixSid(sid, ntignore))
continue; /* boring nt accounts */
else{
gsid = sid;
break;
}
}
if(gsid == nil)
gsid = osid;
owner = sidtouser(srv, osid);
if(owner == nil)
return 0;
group = sidtouser(srv, gsid);
if(group == nil)
return 0;
/* no acl means full access */
allow = 0;
if(acl == 0)
allow = 0777;
deny = 0;
for(i = 0; i < size.AceCount; i++){
if(!GetAce(acl, i, &aceh))
continue;
if(aceh->AceFlags & INHERIT_ONLY_ACE)
continue;
if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
p = &allow;
else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
p = &deny;
else
continue;
ace = (ACCESS_ALLOWED_ACE*)aceh;
sid = (SID*)&ace->SidStart;
if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
continue;
m = 0;
if(ace->Mask & FILE_EXECUTE)
m |= 1;
if(ace->Mask & FILE_WRITE_DATA)
m |= 2;
if(ace->Mask & FILE_READ_DATA)
m |= 4;
if(ismembersid(srv, owner, sid))
*p |= (m << 6) & ~(allow|deny) & 0700;
if(ismembersid(srv, group, sid))
*p |= (m << 3) & ~(allow|deny) & 0070;
if(EqualSid(everyone, sid))
*p |= m & ~(allow|deny) & 0007;
}
st->mode = allow & ~deny;
st->owner = owner;
st->group = group;
return 1;
}
static int
secsdhasperm(SECURITY_DESCRIPTOR *sd, ulong access, Rune16 *srv)
{
User *u;
ACL *acl;
BOOL hasacl, b;
ACE_HEADER *aceh;
SID *sid, *osid, *gsid;
int i, allow, deny, *p, m;
ACCESS_ALLOWED_ACE *ace;
ACL_SIZE_INFORMATION size;
u = up->env->ui;
allow = 0;
deny = 0;
osid = nil;
gsid = nil;
if(!GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
return 0;
/* no acl means full access */
if(acl == 0)
return 1;
if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
return 0;
for(i = 0; i < size.AceCount; i++){
if(!GetAce(acl, i, &aceh))
continue;
if(aceh->AceFlags & INHERIT_ONLY_ACE)
continue;
if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
p = &allow;
else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
p = &deny;
else
continue;
ace = (ACCESS_ALLOWED_ACE*)aceh;
sid = (SID*)&ace->SidStart;
if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
continue;
m = ace->Mask;
if(ismembersid(srv, u, sid))
*p |= m & ~(allow|deny);
}
allow &= ~deny;
return (allow & access) == access;
}
static SECURITY_DESCRIPTOR*
secmksd(char *sdrock, Stat *st, ACL *dacl, int isdir)
{
int m;
ulong mode;
ACE_HEADER *aceh;
SECURITY_DESCRIPTOR *sd;
sd = (SECURITY_DESCRIPTOR*)sdrock;
if(!InitializeAcl(dacl, ACL_ROCK, ACL_REVISION))
return nil;
mode = st->mode;
if(st->owner == st->group){
mode |= (mode >> 3) & 0070;
mode |= (mode << 3) & 0700;
}
m = modetomask[(mode>>6) & 7];
if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->owner->sid))
return nil;
if(isdir && !AddAccessAllowedAce(dacl, ACL_REVISION, m, creatorowner))
return nil;
m = modetomask[(mode>>3) & 7];
if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->group->sid))
return nil;
m = modetomask[(mode>>0) & 7];
if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, everyone))
return nil;
if(isdir){
/* hack to add inherit flags */
if(!GetAce(dacl, 1, &aceh))
return nil;
aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
if(!GetAce(dacl, 2, &aceh))
return nil;
aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
if(!GetAce(dacl, 3, &aceh))
return nil;
aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
}
/*
* allow server user to access any file
*/
if(isserver){
if(!AddAccessAllowedAce(dacl, ACL_REVISION, RMODE|WMODE|XMODE, fsuser->sid))
return nil;
if(isdir){
if(!GetAce(dacl, 4, &aceh))
return nil;
aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
}
}
if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
return nil;
if(!SetSecurityDescriptorDacl(sd, 1, dacl, 0))
return nil;
// if(isserver && !SetSecurityDescriptorOwner(sd, st->owner->sid, 0))
// return nil;
return sd;
}
/*
* the user manipulation routines
* just make it easier to deal with user identities
*/
static User*
sidtouser(Rune16 *srv, SID *s)
{
SID_NAME_USE type;
Rune16 aname[100], dname[100];
DWORD naname, ndname;
User *u;
qlock(&users.lk);
for(u = users.u; u != 0; u = u->next)
if(EqualSid(s, u->sid))
break;
qunlock(&users.lk);
if(u != 0)
return u;
naname = sizeof(aname);
ndname = sizeof(dname);
if(!LookupAccountSidW(srv, s, aname, &naname, dname, &ndname, &type))
return mkuser(s, SidTypeUnknown, L"unknown", L"unknown");
return mkuser(s, type, aname, dname);
}
static User*
domnametouser(Rune16 *srv, Rune16 *name, Rune16 *dom)
{
User *u;
qlock(&users.lk);
for(u = users.u; u != 0; u = u->next)
if(runes16cmp(name, u->name) == 0 && runes16cmp(dom, u->dom) == 0)
break;
qunlock(&users.lk);
if(u == 0)
u = nametouser(srv, name);
return u;
}
static User*
nametouser(Rune16 *srv, Rune16 *name)
{
char sidrock[MAX_SID];
SID *sid;
SID_NAME_USE type;
Rune16 dom[MAX_PATH];
DWORD nsid, ndom;
sid = (SID*)sidrock;
nsid = sizeof(sidrock);
ndom = sizeof(dom);
if(!LookupAccountNameW(srv, name, sid, &nsid, dom, &ndom, &type))
return nil;
return mkuser(sid, type, name, dom);
}
/*
* this mapping could be cached
*/
static User*
unametouser(Rune16 *srv, char *name)
{
Rune16 rname[MAX_PATH];
utftorunes16(rname, name, MAX_PATH);
return nametouser(srv, rname);
}
/*
* make a user structure and add it to the global cache.
*/
static User*
mkuser(SID *sid, int type, Rune16 *name, Rune16 *dom)
{
User *u;
qlock(&users.lk);
for(u = users.u; u != 0; u = u->next){
if(EqualSid(sid, u->sid)){
qunlock(&users.lk);
return u;
}
}
switch(type) {
default:
break;
case SidTypeDeletedAccount:
name = L"deleted";
break;
case SidTypeInvalid:
name = L"invalid";
break;
case SidTypeUnknown:
name = L"unknown";
break;
}
u = malloc(sizeof(User));
if(u == nil){
qunlock(&users.lk);
return 0;
}
u->next = nil;
u->group = nil;
u->sid = dupsid(sid);
u->type = type;
u->name = nil;
if(name != nil)
u->name = runes16dup(name);
u->dom = nil;
if(dom != nil)
u->dom = runes16dup(dom);
u->next = users.u;
users.u = u;
qunlock(&users.lk);
return u;
}
/*
* check if u is a member of gsid,
* which might be a group.
*/
static int
ismembersid(Rune16 *srv, User *u, SID *gsid)
{
User *g;
if(EqualSid(u->sid, gsid))
return 1;
g = sidtouser(srv, gsid);
if(g == 0)
return 0;
return ismember(u, g);
}
static int
ismember(User *u, User *g)
{
Gmem *grps;
if(EqualSid(u->sid, g->sid))
return 1;
if(EqualSid(g->sid, everyone))
return 1;
qlock(&u->lk);
addgroups(u, 0);
for(grps = u->group; grps != 0; grps = grps->next){
if(EqualSid(grps->user->sid, g->sid)){
qunlock(&u->lk);
return 1;
}
}
qunlock(&u->lk);
return 0;
}
/*
* find out what groups a user belongs to.
* if force, throw out the old info and do it again.
*
* note that a global group is also know as a group,
* and a local group is also know as an alias.
* global groups can only contain users.
* local groups can contain global groups or users.
* this code finds all global groups to which a user belongs,
* and all the local groups to which the user or a global group
* containing the user belongs.
*/
static void
addgroups(User *u, int force)
{
LOCALGROUP_USERS_INFO_0 *loc;
GROUP_USERS_INFO_0 *grp;
DWORD i, n, rem;
User *gu;
Gmem *g, *next;
Rune16 *srv, srvrock[MAX_PATH];
if(force){
u->gotgroup = 0;
for(g = u->group; g != nil; g = next){
next = g->next;
free(g);
}
u->group = nil;
}
if(u->gotgroup)
return;
u->gotgroup = 1;
n = 0;
srv = domsrv(u->dom, srvrock);
i = NetUserGetGroups(srv, u->name, 0,
(BYTE**)&grp, MAX_PREFERRED_LENGTH, &n, &rem);
if(i == NERR_Success || i == ERROR_MORE_DATA){
for(i = 0; i < n; i++){
gu = domnametouser(srv, grp[i].grui0_name, u->dom);
if(gu == 0)
continue;
g = malloc(sizeof(Gmem));
if(g == nil)
error(Enomem);
g->user = gu;
g->next = u->group;
u->group = g;
}
NetApiBufferFree(grp);
}
n = 0;
i = NetUserGetLocalGroups(srv, u->name, 0, LG_INCLUDE_INDIRECT,
(BYTE**)&loc, MAX_PREFERRED_LENGTH, &n, &rem);
if(i == NERR_Success || i == ERROR_MORE_DATA){
for(i = 0; i < n; i++){
gu = domnametouser(srv, loc[i].lgrui0_name, u->dom);
if(gu == NULL)
continue;
g = malloc(sizeof(Gmem));
if(g == nil)
error(Enomem);
g->user = gu;
g->next = u->group;
u->group = g;
}
NetApiBufferFree(loc);
}
}
static SID*
dupsid(SID *sid)
{
SID *nsid;
int n;
n = GetLengthSid(sid);
nsid = malloc(n);
if(nsid == nil || !CopySid(n, nsid, sid))
panic("can't copy sid");
return nsid;
}
/*
* return the name of the server machine for file
*/
static Rune16*
filesrv(char *file)
{
int n;
Rune16 *srv;
char *p, uni[MAX_PATH], mfile[MAX_PATH];
wchar_t vol[3];
strcpy(mfile, file);
/* assume file is a fully qualified name - X: or \\server */
if(file[1] == ':') {
vol[0] = file[0];
vol[1] = file[1];
vol[2] = 0;
if(GetDriveType(vol) != DRIVE_REMOTE)
return 0;
n = sizeof(uni);
if(WNetGetUniversalName(vol, UNIVERSAL_NAME_INFO_LEVEL, uni, &n) != NO_ERROR)
return nil;
runes16toutf(mfile, ((UNIVERSAL_NAME_INFO*)uni)->lpUniversalName, MAX_PATH);
file = mfile;
}
file += 2;
p = strchr(file, '\\');
if(p == 0)
n = strlen(file);
else
n = p - file;
if(n >= MAX_PATH)
n = MAX_PATH-1;
memmove(uni, file, n);
uni[n] = '\0';
srv = malloc((n + 1) * sizeof(Rune16));
if(srv == nil)
panic("filesrv: no memory");
utftorunes16(srv, uni, n+1);
return srv;
}
/*
* does the file system support acls?
*/
static int
fsacls(char *file)
{
char *p;
DWORD flags;
char path[MAX_PATH];
wchar_t wpath[MAX_PATH];
/* assume file is a fully qualified name - X: or \\server */
if(file[1] == ':') {
path[0] = file[0];
path[1] = file[1];
path[2] = '\\';
path[3] = 0;
} else {
strcpy(path, file);
p = strchr(path+2, '\\');
if(p == 0)
return 0;
p = strchr(p+1, '\\');
if(p == 0)
strcat(path, "\\");
else
p[1] = 0;
}
utftorunes16(wpath, path, MAX_PATH);
if(!GetVolumeInformation(wpath, NULL, 0, NULL, NULL, &flags, NULL, 0))
return 0;
return flags & FS_PERSISTENT_ACLS;
}
/*
* given a domain, find out the server to ask about its users.
* we just ask the local machine to do the translation,
* so it might fail sometimes. in those cases, we don't
* trust the domain anyway, and vice versa, so it's not
* clear what benifit we would gain by getting the answer "right".
*/
static Rune16*
domsrv(Rune16 *dom, Rune16 srv[MAX_PATH])
{
Rune16 *psrv;
int n, r;
if(dom[0] == 0)
return nil;
r = NetGetAnyDCName(NULL, dom, (LPBYTE*)&psrv);
if(r == NERR_Success) {
n = runes16len(psrv);
if(n >= MAX_PATH)
n = MAX_PATH-1;
memmove(srv, psrv, n*sizeof(Rune16));
srv[n] = 0;
NetApiBufferFree(psrv);
return srv;
}
return nil;
}
Dev fsdevtab = {
'U',
"fs",
fsinit,
fsattach,
fswalk,
fsstat,
fsopen,
fscreate,
fsclose,
fsread,
devbread,
fswrite,
devbwrite,
fsremove,
fswstat
};