blob: b4496d72c4f7c6a8d640d06c80363c88a3ff4a9d [file] [log] [blame]
#include "stdinc.h"
#include "9.h"
enum {
NUserHash = 1009,
};
typedef struct Ubox Ubox;
typedef struct User User;
struct User {
char* uid;
char* uname;
char* leader;
char** group;
int ngroup;
User* next; /* */
User* ihash; /* lookup by .uid */
User* nhash; /* lookup by .uname */
};
#pragma varargck type "U" User*
struct Ubox {
User* head;
User* tail;
int nuser;
int len;
User* ihash[NUserHash]; /* lookup by .uid */
User* nhash[NUserHash]; /* lookup by .uname */
};
static struct {
RWLock lock;
Ubox* box;
} ubox;
static char usersDefault[] = {
"adm:adm:adm:sys\n"
"none:none::\n"
"noworld:noworld::\n"
"sys:sys::glenda\n"
"glenda:glenda:glenda:\n"
};
static char* usersMandatory[] = {
"adm",
"none",
"noworld",
"sys",
nil,
};
char* uidadm = "adm";
char* unamenone = "none";
char* uidnoworld = "noworld";
static u32int
userHash(char* s)
{
uchar *p;
u32int hash;
hash = 0;
for(p = (uchar*)s; *p != '\0'; p++)
hash = hash*7 + *p;
return hash % NUserHash;
}
static User*
_userByUid(Ubox* box, char* uid)
{
User *u;
if(box != nil){
for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
if(strcmp(u->uid, uid) == 0)
return u;
}
}
werrstr("uname: uid '%s' not found", uid);
return nil;
}
char*
unameByUid(char* uid)
{
User *u;
char *uname;
rlock(&ubox.lock);
if((u = _userByUid(ubox.box, uid)) == nil){
runlock(&ubox.lock);
return nil;
}
uname = vtstrdup(u->uname);
runlock(&ubox.lock);
return uname;
}
static User*
_userByUname(Ubox* box, char* uname)
{
User *u;
if(box != nil){
for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
if(strcmp(u->uname, uname) == 0)
return u;
}
}
werrstr("uname: uname '%s' not found", uname);
return nil;
}
char*
uidByUname(char* uname)
{
User *u;
char *uid;
rlock(&ubox.lock);
if((u = _userByUname(ubox.box, uname)) == nil){
runlock(&ubox.lock);
return nil;
}
uid = vtstrdup(u->uid);
runlock(&ubox.lock);
return uid;
}
static int
_groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
{
int i;
User *g, *m;
/*
* Is 'member' a member of 'group'?
* Note that 'group' is a 'uid' and not a 'uname'.
* A 'member' is automatically in their own group.
*/
if((g = _userByUid(box, group)) == nil)
return whenNoGroup;
if((m = _userByUname(box, member)) == nil)
return 0;
if(m == g)
return 1;
for(i = 0; i < g->ngroup; i++){
if(strcmp(g->group[i], member) == 0)
return 1;
}
return 0;
}
int
groupWriteMember(char* uname)
{
int ret;
/*
* If there is a ``write'' group, then only its members can write
* to the file system, no matter what the permission bits say.
*
* To users not in the ``write'' group, the file system appears
* read only. This is used to serve sources.cs.bell-labs.com
* to the world.
*
* Note that if there is no ``write'' group, then this routine
* makes it look like everyone is a member -- the opposite
* of what groupMember does.
*
* We use this for sources.cs.bell-labs.com.
* If this slows things down too much on systems that don't
* use this functionality, we could cache the write group lookup.
*/
rlock(&ubox.lock);
ret = _groupMember(ubox.box, "write", uname, 1);
runlock(&ubox.lock);
return ret;
}
static int
_groupRemMember(Ubox* box, User* g, char* member)
{
int i;
if(_userByUname(box, member) == nil)
return 0;
for(i = 0; i < g->ngroup; i++){
if(strcmp(g->group[i], member) == 0)
break;
}
if(i >= g->ngroup){
if(strcmp(g->uname, member) == 0)
werrstr("uname: '%s' always in own group", member);
else
werrstr("uname: '%s' not in group '%s'",
member, g->uname);
return 0;
}
vtfree(g->group[i]);
box->len -= strlen(member);
if(g->ngroup > 1)
box->len--;
g->ngroup--;
switch(g->ngroup){
case 0:
vtfree(g->group);
g->group = nil;
break;
default:
for(; i < g->ngroup; i++)
g->group[i] = g->group[i+1];
g->group[i] = nil; /* prevent accidents */
g->group = vtrealloc(g->group, g->ngroup * sizeof(char*));
break;
}
return 1;
}
static int
_groupAddMember(Ubox* box, User* g, char* member)
{
User *u;
if((u = _userByUname(box, member)) == nil)
return 0;
if(_groupMember(box, g->uid, u->uname, 0)){
if(strcmp(g->uname, member) == 0)
werrstr("uname: '%s' always in own group", member);
else
werrstr("uname: '%s' already in group '%s'",
member, g->uname);
return 0;
}
g->group = vtrealloc(g->group, (g->ngroup+1)*sizeof(char*));
g->group[g->ngroup] = vtstrdup(member);
box->len += strlen(member);
g->ngroup++;
if(g->ngroup > 1)
box->len++;
return 1;
}
int
groupMember(char* group, char* member)
{
int r;
if(group == nil)
return 0;
rlock(&ubox.lock);
r = _groupMember(ubox.box, group, member, 0);
runlock(&ubox.lock);
return r;
}
int
groupLeader(char* group, char* member)
{
int r;
User *g;
/*
* Is 'member' the leader of 'group'?
* Note that 'group' is a 'uid' and not a 'uname'.
* Uname 'none' cannot be a group leader.
*/
if(strcmp(member, unamenone) == 0 || group == nil)
return 0;
rlock(&ubox.lock);
if((g = _userByUid(ubox.box, group)) == nil){
runlock(&ubox.lock);
return 0;
}
if(g->leader != nil){
if(strcmp(g->leader, member) == 0){
runlock(&ubox.lock);
return 1;
}
r = 0;
}
else
r = _groupMember(ubox.box, group, member, 0);
runlock(&ubox.lock);
return r;
}
static void
userFree(User* u)
{
int i;
vtfree(u->uid);
vtfree(u->uname);
if(u->leader != nil)
vtfree(u->leader);
if(u->ngroup){
for(i = 0; i < u->ngroup; i++)
vtfree(u->group[i]);
vtfree(u->group);
}
vtfree(u);
}
static User*
userAlloc(char* uid, char* uname)
{
User *u;
u = vtmallocz(sizeof(User));
u->uid = vtstrdup(uid);
u->uname = vtstrdup(uname);
return u;
}
int
validUserName(char* name)
{
Rune *r;
#ifdef PLAN9PORT
static Rune invalid[] = {'#', ':', ',', '(', ')', '\0'};
#else
static Rune invalid[] = L"#:,()";
#endif
for(r = invalid; *r != '\0'; r++){
if(utfrune(name, *r))
return 0;
}
return 1;
}
static int
userFmt(Fmt* fmt)
{
User *u;
int i, r;
u = va_arg(fmt->args, User*);
r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
if(u->leader != nil)
r += fmtprint(fmt, u->leader);
r += fmtprint(fmt, ":");
if(u->ngroup){
r += fmtprint(fmt, u->group[0]);
for(i = 1; i < u->ngroup; i++)
r += fmtprint(fmt, ",%s", u->group[i]);
}
return r;
}
static int
usersFileWrite(Ubox* box)
{
Fs *fs;
User *u;
int i, r;
Fsys *fsys;
char *p, *q, *s;
File *dir, *file;
if((fsys = fsysGet("main")) == nil)
return 0;
fsysFsRlock(fsys);
fs = fsysGetFs(fsys);
/*
* BUG:
* the owner/group/permissions need to be thought out.
*/
r = 0;
if((dir = fileOpen(fs, "/active")) == nil)
goto tidy0;
if((file = fileWalk(dir, uidadm)) == nil)
file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
fileDecRef(dir);
if(file == nil)
goto tidy;
dir = file;
if((file = fileWalk(dir, "users")) == nil)
file = fileCreate(dir, "users", 0664, uidadm);
fileDecRef(dir);
if(file == nil)
goto tidy;
if(!fileTruncate(file, uidadm))
goto tidy;
p = s = vtmalloc(box->len+1);
q = p + box->len+1;
for(u = box->head; u != nil; u = u->next){
p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
if(u->leader != nil)
p+= snprint(p, q-p, u->leader);
p += snprint(p, q-p, ":");
if(u->ngroup){
p += snprint(p, q-p, u->group[0]);
for(i = 1; i < u->ngroup; i++)
p += snprint(p, q-p, ",%s", u->group[i]);
}
p += snprint(p, q-p, "\n");
}
r = fileWrite(file, s, box->len, 0, uidadm);
vtfree(s);
tidy:
if(file != nil)
fileDecRef(file);
tidy0:
fsysFsRUnlock(fsys);
fsysPut(fsys);
return r;
}
static void
uboxRemUser(Ubox* box, User *u)
{
User **h, *up;
h = &box->ihash[userHash(u->uid)];
for(up = *h; up != nil && up != u; up = up->ihash)
h = &up->ihash;
assert(up == u);
*h = up->ihash;
box->len -= strlen(u->uid);
h = &box->nhash[userHash(u->uname)];
for(up = *h; up != nil && up != u; up = up->nhash)
h = &up->nhash;
assert(up == u);
*h = up->nhash;
box->len -= strlen(u->uname);
h = &box->head;
for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
h = &up->next;
assert(up == u);
*h = u->next;
u->next = nil;
box->len -= 4;
box->nuser--;
}
static void
uboxAddUser(Ubox* box, User* u)
{
User **h, *up;
h = &box->ihash[userHash(u->uid)];
u->ihash = *h;
*h = u;
box->len += strlen(u->uid);
h = &box->nhash[userHash(u->uname)];
u->nhash = *h;
*h = u;
box->len += strlen(u->uname);
h = &box->head;
for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
h = &up->next;
u->next = *h;
*h = u;
box->len += 4;
box->nuser++;
}
static void
uboxDump(Ubox* box)
{
User* u;
consPrint("nuser %d len = %d\n", box->nuser, box->len);
for(u = box->head; u != nil; u = u->next)
consPrint("%U\n", u);
}
static void
uboxFree(Ubox* box)
{
User *next, *u;
for(u = box->head; u != nil; u = next){
next = u->next;
userFree(u);
}
vtfree(box);
}
static int
uboxInit(char* users, int len)
{
User *g, *u;
Ubox *box, *obox;
int blank, comment, i, nline, nuser;
char *buf, *f[5], **line, *p, *q, *s;
/*
* Strip out whitespace and comments.
* Note that comments are pointless, they disappear
* when the server writes the database back out.
*/
blank = 1;
comment = nline = 0;
s = p = buf = vtmalloc(len+1);
for(q = users; *q != '\0'; q++){
if(*q == '\r' || *q == '\t' || *q == ' ')
continue;
if(*q == '\n'){
if(!blank){
if(p != s){
*p++ = '\n';
nline++;
s = p;
}
blank = 1;
}
comment = 0;
continue;
}
if(*q == '#')
comment = 1;
blank = 0;
if(!comment)
*p++ = *q;
}
*p = '\0';
line = vtmallocz((nline+2)*sizeof(char*));
if((i = gettokens(buf, line, nline+2, "\n")) != nline){
fprint(2, "nline %d (%d) botch\n", nline, i);
vtfree(line);
vtfree(buf);
return 0;
}
/*
* Everything is updated in a local Ubox until verified.
*/
box = vtmallocz(sizeof(Ubox));
/*
* First pass - check format, check for duplicates
* and enter in hash buckets.
*/
nuser = 0;
for(i = 0; i < nline; i++){
s = vtstrdup(line[i]);
if(getfields(s, f, nelem(f), 0, ":") != 4){
fprint(2, "bad line '%s'\n", line[i]);
vtfree(s);
continue;
}
if(*f[0] == '\0' || *f[1] == '\0'){
fprint(2, "bad line '%s'\n", line[i]);
vtfree(s);
continue;
}
if(!validUserName(f[0])){
fprint(2, "invalid uid '%s'\n", f[0]);
vtfree(s);
continue;
}
if(_userByUid(box, f[0]) != nil){
fprint(2, "duplicate uid '%s'\n", f[0]);
vtfree(s);
continue;
}
if(!validUserName(f[1])){
fprint(2, "invalid uname '%s'\n", f[0]);
vtfree(s);
continue;
}
if(_userByUname(box, f[1]) != nil){
fprint(2, "duplicate uname '%s'\n", f[1]);
vtfree(s);
continue;
}
u = userAlloc(f[0], f[1]);
uboxAddUser(box, u);
line[nuser] = line[i];
nuser++;
vtfree(s);
}
assert(box->nuser == nuser);
/*
* Second pass - fill in leader and group information.
*/
for(i = 0; i < nuser; i++){
s = vtstrdup(line[i]);
getfields(s, f, nelem(f), 0, ":");
assert(g = _userByUname(box, f[1]));
if(*f[2] != '\0'){
if((u = _userByUname(box, f[2])) == nil)
g->leader = vtstrdup(g->uname);
else
g->leader = vtstrdup(u->uname);
box->len += strlen(g->leader);
}
for(p = f[3]; p != nil; p = q){
if((q = utfrune(p, L',')) != nil)
*q++ = '\0';
if(!_groupAddMember(box, g, p)){
// print/log error here
}
}
vtfree(s);
}
vtfree(line);
vtfree(buf);
for(i = 0; usersMandatory[i] != nil; i++){
if((u = _userByUid(box, usersMandatory[i])) == nil){
werrstr("user '%s' is mandatory", usersMandatory[i]);
uboxFree(box);
return 0;
}
if(strcmp(u->uid, u->uname) != 0){
werrstr("uid/uname for user '%s' must match",
usersMandatory[i]);
uboxFree(box);
return 0;
}
}
wlock(&ubox.lock);
obox = ubox.box;
ubox.box = box;
wunlock(&ubox.lock);
if(obox != nil)
uboxFree(obox);
return 1;
}
int
usersFileRead(char* path)
{
char *p;
File *file;
Fsys *fsys;
int len, r;
uvlong size;
if((fsys = fsysGet("main")) == nil)
return 0;
fsysFsRlock(fsys);
if(path == nil)
path = "/active/adm/users";
r = 0;
if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
if(fileGetSize(file, &size)){
len = size;
p = vtmalloc(size+1);
if(fileRead(file, p, len, 0) == len){
p[len] = '\0';
r = uboxInit(p, len);
}
}
fileDecRef(file);
}
fsysFsRUnlock(fsys);
fsysPut(fsys);
return r;
}
static int
cmdUname(int argc, char* argv[])
{
User *u, *up;
int d, dflag, i, r;
char *p, *uid, *uname;
char *createfmt = "fsys main create /active/usr/%s %s %s d775";
char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
dflag = 0;
ARGBEGIN{
default:
return cliError(usage);
case 'd':
dflag = 1;
break;
}ARGEND
if(argc < 1){
if(!dflag)
return cliError(usage);
rlock(&ubox.lock);
uboxDump(ubox.box);
runlock(&ubox.lock);
return 1;
}
uname = argv[0];
argc--; argv++;
if(argc == 0){
rlock(&ubox.lock);
if((u = _userByUname(ubox.box, uname)) == nil){
runlock(&ubox.lock);
return 0;
}
consPrint("\t%U\n", u);
runlock(&ubox.lock);
return 1;
}
wlock(&ubox.lock);
u = _userByUname(ubox.box, uname);
while(argc--){
if(argv[0][0] == '%'){
if(u == nil){
wunlock(&ubox.lock);
return 0;
}
p = &argv[0][1];
if((up = _userByUname(ubox.box, p)) != nil){
werrstr("uname: uname '%s' already exists",
up->uname);
wunlock(&ubox.lock);
return 0;
}
for(i = 0; usersMandatory[i] != nil; i++){
if(strcmp(usersMandatory[i], uname) != 0)
continue;
werrstr("uname: uname '%s' is mandatory",
uname);
wunlock(&ubox.lock);
return 0;
}
d = strlen(p) - strlen(u->uname);
for(up = ubox.box->head; up != nil; up = up->next){
if(up->leader != nil){
if(strcmp(up->leader, u->uname) == 0){
vtfree(up->leader);
up->leader = vtstrdup(p);
ubox.box->len += d;
}
}
for(i = 0; i < up->ngroup; i++){
if(strcmp(up->group[i], u->uname) != 0)
continue;
vtfree(up->group[i]);
up->group[i] = vtstrdup(p);
ubox.box->len += d;
break;
}
}
uboxRemUser(ubox.box, u);
vtfree(u->uname);
u->uname = vtstrdup(p);
uboxAddUser(ubox.box, u);
}
else if(argv[0][0] == '='){
if(u == nil){
wunlock(&ubox.lock);
return 0;
}
if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
if(argv[0][1] != '\0'){
wunlock(&ubox.lock);
return 0;
}
}
if(u->leader != nil){
ubox.box->len -= strlen(u->leader);
vtfree(u->leader);
u->leader = nil;
}
if(up != nil){
u->leader = vtstrdup(up->uname);
ubox.box->len += strlen(u->leader);
}
}
else if(argv[0][0] == '+'){
if(u == nil){
wunlock(&ubox.lock);
return 0;
}
if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
wunlock(&ubox.lock);
return 0;
}
if(!_groupAddMember(ubox.box, u, up->uname)){
wunlock(&ubox.lock);
return 0;
}
}
else if(argv[0][0] == '-'){
if(u == nil){
wunlock(&ubox.lock);
return 0;
}
if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
wunlock(&ubox.lock);
return 0;
}
if(!_groupRemMember(ubox.box, u, up->uname)){
wunlock(&ubox.lock);
return 0;
}
}
else{
if(u != nil){
werrstr("uname: uname '%s' already exists",
u->uname);
wunlock(&ubox.lock);
return 0;
}
uid = argv[0];
if(*uid == ':')
uid++;
if((u = _userByUid(ubox.box, uid)) != nil){
werrstr("uname: uid '%s' already exists",
u->uid);
wunlock(&ubox.lock);
return 0;
}
u = userAlloc(uid, uname);
uboxAddUser(ubox.box, u);
if(argv[0][0] != ':'){
// should have an option for the mode and gid
p = smprint(createfmt, uname, uname, uname);
r = cliExec(p);
vtfree(p);
if(r == 0){
wunlock(&ubox.lock);
return 0;
}
}
}
argv++;
}
if(usersFileWrite(ubox.box) == 0){
wunlock(&ubox.lock);
return 0;
}
if(dflag)
uboxDump(ubox.box);
wunlock(&ubox.lock);
return 1;
}
static int
cmdUsers(int argc, char* argv[])
{
Ubox *box;
int dflag, r, wflag;
char *file;
char *usage = "usage: users [-d | -r file] [-w]";
dflag = wflag = 0;
file = nil;
ARGBEGIN{
default:
return cliError(usage);
case 'd':
dflag = 1;
break;
case 'r':
file = ARGF();
if(file == nil)
return cliError(usage);
break;
case 'w':
wflag = 1;
break;
}ARGEND
if(argc)
return cliError(usage);
if(dflag && file)
return cliError("cannot use -d and -r together");
if(dflag)
uboxInit(usersDefault, sizeof(usersDefault));
else if(file){
if(usersFileRead(file) == 0)
return 0;
}
rlock(&ubox.lock);
box = ubox.box;
consPrint("\tnuser %d len %d\n", box->nuser, box->len);
r = 1;
if(wflag)
r = usersFileWrite(box);
runlock(&ubox.lock);
return r;
}
int
usersInit(void)
{
fmtinstall('U', userFmt);
uboxInit(usersDefault, sizeof(usersDefault));
cliAddCmd("users", cmdUsers);
cliAddCmd("uname", cmdUname);
return 1;
}