#include <9pm/windows.h> #include <9pm/u.h> #include <9pm/libc.h> #include "dat.h" #include "fns.h" enum { MAX_SID = sizeof(SID) + SID_MAX_SUB_AUTHORITIES*sizeof(DWORD), BIG_SD = SECURITY_DESCRIPTOR_MIN_LENGTH + sizeof(ACL) + 20*(sizeof(ACCESS_ALLOWED_ACE)+MAX_SID) }; typedef struct User User; typedef struct Gmem Gmem; struct User { QLock lk; SID *sid; Rune *name; Rune *dom; int type; int gotgroup; /* tried to add group */ Gmem *group; User *next; }; struct Gmem { User *user; Gmem *next; }; static struct { NET_API_STATUS (NET_API_FUNCTION *UserGetLocalGroups)( LPWSTR servername, LPWSTR username, DWORD level, DWORD flags, LPBYTE *bufptr, DWORD prefmaxlen, LPDWORD entriesread, LPDWORD totalentries); NET_API_STATUS (NET_API_FUNCTION *UserGetGroups)( LPWSTR servername, LPWSTR username, DWORD level, LPBYTE *bufptr, DWORD prefmaxlen, LPDWORD entriesread, LPDWORD totalentries); NET_API_STATUS (NET_API_FUNCTION *GetAnyDCName)( LPCWSTR ServerName, LPCWSTR DomainName, LPBYTE *Buffer); NET_API_STATUS (NET_API_FUNCTION *ApiBufferFree)(LPVOID Buffer); } net; typedef struct Valmap Valmap; struct Valmap { int val; char *name; }; static SID *dupsid(SID*); static int ismember(Rune*, User*, SID*); static User *sidtouser(Rune*, SID*); static User *domnametouser(Rune*, Rune*, Rune*); static User *nametouser(Rune*, Rune*); static void addgroups(User*); static User *mkuser(SID*, int, Rune*, Rune*); static Rune *filesrv(Rune*, Rune[MAX_PATH]); static Rune *domsrv(Rune *, Rune[MAX_PATH]); static int fsacls(Rune*); static void perminit(int); static SID *creatorowner; static SID *creatorgroup; static SID *everyone; static SID *ntignore; static struct { QLock lk; User *u; }users; int win_usesecurity; void win_secinit(void) { HMODULE lib; SID_IDENTIFIER_AUTHORITY id = SECURITY_CREATOR_SID_AUTHORITY; SID_IDENTIFIER_AUTHORITY wid = SECURITY_WORLD_SID_AUTHORITY; SID_IDENTIFIER_AUTHORITY ntid = SECURITY_NT_AUTHORITY; lib = LoadLibrary(L"netapi32"); if(lib == 0) { win_usesecurity = 0; return; } win_usesecurity = 1; net.UserGetGroups = (void*)GetProcAddress(lib, "NetUserGetGroups"); if(net.UserGetGroups == 0) win_fatal("bad netapi32 library"); net.UserGetLocalGroups = (void*)GetProcAddress(lib, "NetUserGetLocalGroups"); if(net.UserGetLocalGroups == 0) win_fatal("bad netapi32 library"); net.GetAnyDCName = (void*)GetProcAddress(lib, "NetGetAnyDCName"); if(net.GetAnyDCName == 0) win_fatal("bad netapi32 library"); net.ApiBufferFree = (void*)GetProcAddress(lib, "NetApiBufferFree"); if(net.ApiBufferFree == 0) win_fatal("bad netapi32 library"); 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); perminit(0); } static void perminit(int backup) { TOKEN_PRIVILEGES *priv; char privrock[sizeof(TOKEN_PRIVILEGES) + 1*sizeof(LUID_AND_ATTRIBUTES)]; HANDLE token; /* * enabling take ownership allows us to chown to ourself * regargless of permissions on the file. * enabling restore allows chowning to others * if we have permission to write the owner. */ if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) return; priv = (TOKEN_PRIVILEGES*)privrock; priv->PrivilegeCount = 1; priv->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; /* lets be super user */ if(LookupPrivilegeValue(NULL, SE_TAKE_OWNERSHIP_NAME, &priv->Privileges[0].Luid)) if(AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL)) ; if(backup) { /* netapp is broken - until it is fixed these privleges can not be enabled * I do try and enable these when I do a secwperm... */ if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &priv->Privileges[0].Luid)) if(AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL)) ; if(LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &priv->Privileges[0].Luid)) if(AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL)) ; } if(LookupPrivilegeValue(NULL, SE_CHANGE_NOTIFY_NAME, &priv->Privileges[0].Luid)) if(AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL)) ; CloseHandle(token); } int win_secperm(Dir *dir, Rune *file) { SECURITY_INFORMATION si; SECURITY_DESCRIPTOR *sd; ACL_SIZE_INFORMATION size; ACL *acl; ACE_HEADER *aceh; ACCESS_ALLOWED_ACE *ace; SID *sid, *osid, *gsid; BOOL hasacl, b; DWORD need, i; User *owner, *group; int allow, deny, *p, m; Rune *srv, *srvrock; char *sdrock; sdrock = win_malloc(BIG_SD); srvrock = win_malloc(sizeof(Rune)*MAX_PATH); /* secdump(file);*/ osid = NULL; gsid = NULL; si = OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; sd = (SECURITY_DESCRIPTOR*)sdrock; need = 0; if(!GetFileSecurity(file, si, sd, BIG_SD, &need)) { if(GetLastError() == ERROR_ACCESS_DENIED) { dir->uid = strdup("unknown"); dir->gid = strdup("unknown"); dir->mode = 0; goto good; } if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto bad; sd = mallocz(need, 1); if(!GetFileSecurity(file, si, sd, need, &need)) goto bad; } if(!GetSecurityDescriptorOwner(sd, &osid, &b) || !GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b)) goto bad; if(acl == 0) size.AceCount = 0; else if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation)) goto bad; srv = filesrv(file, srvrock); /* * 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 == NULL) gsid = osid; owner = sidtouser(srv, osid); group = sidtouser(srv, gsid); if(owner == 0 || group == 0) goto bad; /* no acl means full access */ if(acl == 0) allow = 0777; else allow = 0; 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(ismember(srv, owner, sid)) *p |= (m << 6) & ~(allow|deny) & 0700; if(ismember(srv, group, sid)) *p |= (m << 3) & ~(allow|deny) & 0070; */ if(EqualSid(osid, sid)) *p |= (m << 6) & ~(allow|deny) & 0700; if(EqualSid(gsid, sid)) *p |= (m << 3) & ~(allow|deny) & 0070; if(EqualSid(everyone, sid)) *p |= m & ~(allow|deny) & 0007; } dir->mode = allow&~deny; dir->uid = win_wstr2utf(owner->name); dir->gid = win_wstr2utf(group->name); good: if((char*)sd != sdrock) free(sd); free(sdrock); free(srvrock); return 0; bad: oserror(); dir->mode = 0; if((char*)sd != sdrock) free(sd); free(sdrock); free(srvrock); return -1; } #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, }; int win_secwperm(Rune *file, Dir *dir, int isdir) { int m; BOOL b; ACL *dacl; SID *osid; ulong mode; DWORD need; User *ou, *gu; ACE_HEADER *aceh; SECURITY_DESCRIPTOR *sd; SECURITY_INFORMATION si; Rune *srv, srvrock[MAX_PATH], name[MAX_PATH]; char sdrock[SECURITY_DESCRIPTOR_MIN_LENGTH + MAX_SID]; char aclrock[sizeof(ACL)+4*(sizeof(ACCESS_ALLOWED_ACE)+2*MAX_SID)]; perminit(1); dacl = 0; if(!fsacls(file)) return 0; srv = filesrv(file, srvrock); win_utf2wstrn(name, nelem(name), dir->uid); ou = nametouser(srv, name); if(ou == 0) return -1; win_utf2wstrn(name, nelem(name), dir->gid); gu = nametouser(srv, name); if(gu == 0) { if (strcmp(dir->gid, "unknown") != 0 && strcmp(dir->gid, "deleted") != 0) return -1; gu = ou; } sd = (SECURITY_DESCRIPTOR*)sdrock; need = 0; osid = 0; if(GetFileSecurity(file, OWNER_SECURITY_INFORMATION, sd, sizeof(sdrock), &need)) GetSecurityDescriptorOwner(sd, &osid, &b); dacl = (ACL*)aclrock; if(!InitializeAcl(dacl, sizeof(aclrock), ACL_REVISION2)){ oserror(); return -1; } mode = dir->mode; if(ou == gu){ mode |= (mode >> 3) & 0070; mode |= (mode << 3) & 0700; } m = modetomask[(mode>>6) & 7]; if(!AddAccessAllowedAce(dacl, ACL_REVISION2, m, ou->sid)){ oserror(); return -1; } if(isdir && !AddAccessAllowedAce(dacl, ACL_REVISION2, m, creatorowner)){ oserror(); return -1; } m = modetomask[(mode>>3) & 7]; if(!AddAccessAllowedAce(dacl, ACL_REVISION2, m, gu->sid)){ oserror(); return -1; } m = modetomask[(mode>>0) & 7]; if(!AddAccessAllowedAce(dacl, ACL_REVISION2, m, everyone)){ oserror(); return -1; } if(isdir) { /* hack to add inherit flags */ if(GetAce(dacl, 1, &aceh)) aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE; if(GetAce(dacl, 2, &aceh)) aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE; if(GetAce(dacl, 3, &aceh)) aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE; } if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION)){ oserror(); return -1; } if(!SetSecurityDescriptorDacl(sd, 1, dacl, 0)){ oserror(); return -1; } si = DACL_SECURITY_INFORMATION; if(osid == NULL || !EqualSid(osid, ou->sid)){ si |= OWNER_SECURITY_INFORMATION; if(!SetSecurityDescriptorOwner(sd, ou->sid, 0)){ oserror(); return -1; } } if(!SetFileSecurity(file, si, sd)){ /* two steps will sometimes work where one will not */ if((si & OWNER_SECURITY_INFORMATION) && !SetFileSecurity(file, OWNER_SECURITY_INFORMATION, sd)) { oserror(); return -1; } if(!SetFileSecurity(file, DACL_SECURITY_INFORMATION, sd)) { oserror(); return -1; } } return 0; } static User* sidtouser(Rune *srv, SID *s) { SID_NAME_USE type; WCHAR 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(!LookupAccountSid(srv, s, aname, &naname, dname, &ndname, &type)) { oserror(); return 0; } return mkuser(s, type, aname, dname); } static User* domnametouser(Rune *srv, Rune *name, Rune *dom) { User *u; qlock(&users.lk); for(u = users.u; u != 0; u = u->next) if(runestrcmp(name, u->name) == 0 && runestrcmp(dom, u->dom) == 0) break; qunlock(&users.lk); if(u == 0) u = nametouser(srv, name); return u; } static User* nametouser(Rune *srv, Rune *name) { char sidrock[MAX_SID]; SID *sid; SID_NAME_USE type; Rune dom[MAX_PATH]; DWORD nsid, ndom; sid = (SID*)sidrock; nsid = sizeof(sidrock); ndom = sizeof(dom); if(!LookupAccountName(srv, name, sid, &nsid, dom, &ndom, &type)){ oserror(); return NULL; } return mkuser(sid, type, name, dom); } static User* mkuser(SID *sid, int type, Rune *name, Rune *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 = mallocz(sizeof(User), 1); if(u == 0){ qunlock(&users.lk); return 0; } u->next = 0; u->group = 0; u->sid = dupsid(sid); u->type = type; u->name = 0; if(name != 0) u->name = runestrdup(name); u->dom = 0; if(dom != 0) u->dom = runestrdup(dom); u->next = users.u; users.u = u; qunlock(&users.lk); return u; } static int ismember(Rune *srv, User *u, SID *gsid) { User *g; Gmem *grps; if(EqualSid(u->sid, gsid)) return 1; if(EqualSid(gsid, everyone)) return 1; g = sidtouser(srv, gsid); if(g == 0) return 0; qlock(&u->lk); addgroups(u); for(grps = u->group; grps != 0; grps = grps->next){ if(EqualSid(grps->user->sid, gsid)){ qunlock(&u->lk); return 1; } } qunlock(&u->lk); return 0; } static void addgroups(User *u) { LOCALGROUP_USERS_INFO_0 *loc; GROUP_USERS_INFO_0 *grp; DWORD i, n, rem; User *gu; Gmem *g; Rune *srv, srvrock[MAX_PATH]; // assert(holdqlock(&u->lk)); if(u->gotgroup) return; u->gotgroup = 1; rem = 1; n = 0; srv = domsrv(u->dom, srvrock); while(rem != n){ i = net.UserGetGroups(srv, u->name, 0, (BYTE**)&grp, 1024, &n, &rem); if(i != NERR_Success && i != ERROR_MORE_DATA) break; for(i = 0; i < n; i++){ gu = domnametouser(srv, grp[i].grui0_name, u->dom); if(gu == 0) continue; g = mallocz(sizeof(Gmem), 1); g->user = gu; g->next = u->group; u->group = g; } net.ApiBufferFree(grp); } rem = 1; n = 0; while(rem != n){ i = net.UserGetLocalGroups(srv, u->name, 0, LG_INCLUDE_INDIRECT, (BYTE**)&loc, 1024, &n, &rem); if(i != NERR_Success && i != ERROR_MORE_DATA) break; for(i = 0; i < n; i++){ gu = domnametouser(srv, loc[i].lgrui0_name, u->dom); if(gu == NULL) continue; g = mallocz(sizeof(Gmem), 1); g->user = gu; g->next = u->group; u->group = g; } net.ApiBufferFree(loc); } } static SID* dupsid(SID *sid) { SID *nsid; int n; n = GetLengthSid(sid); nsid = mallocz(n, 1); if(!CopySid(n, nsid, sid)) oserror(); return nsid; } static Rune* filesrv(Rune *file, Rune srv[MAX_PATH]) { Rune *p; int n; Rune vol[3], uni[MAX_PATH]; /* 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 0; file = ((UNIVERSAL_NAME_INFO*)uni)->lpUniversalName; } p = runestrchr(file+2, '\\'); if(p == 0) n = runestrlen(file+2); else n = p-(file+2); if(n >= MAX_PATH) n = MAX_PATH-1; memcpy(srv, file+2, n*sizeof(Rune)); srv[n] = 0; return srv; } static int fsacls(Rune *file) { Rune *p; DWORD flags; Rune path[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 { runestrcpy(path, file); p = runestrchr(path+2, '\\'); if(p == 0) return 0; p = runestrchr(p+1, '\\'); if(p == 0) runestrcat(path, L"\\"); else p[1] = 0; } if(!GetVolumeInformation(path, NULL, 0, NULL, NULL, &flags, NULL, 0)) return 0; return flags & FS_PERSISTENT_ACLS; } static Rune* domsrv(Rune *dom, Rune srv[MAX_PATH]) { Rune *psrv; int n, r; if(dom[0] == 0) return 0; r = net.GetAnyDCName(NULL, dom, (LPBYTE*)&psrv); if(r == NERR_Success) { n = runestrlen(psrv); if(n >= MAX_PATH) n = MAX_PATH-1; memcpy(srv, psrv, n*sizeof(Rune)); srv[n] = 0; net.ApiBufferFree(psrv); return srv; } return 0; }