/* tinyldap acl syntax: acl login-in-as-dn target-dn attrib e.g. # root@fefe.de can do everything acl dn:cn=root,o=fefe,c=de * +rwdR; # noone can read userPassword acl * * userPassword -r; # but everyone can authenticate using it acl * self +a; # admins at fefe.de can write in their tree acl dn:*ou=admin,o=fefe,c=de dn:*,o=fefe,c=de +rwdR; */ #include #include #include #include #include #include #include #include #include #include const char Any[]="*"; const char Self[]="self"; const char Dn[]="dn"; enum { acl_read = 1, acl_write = 2, acl_auth = 4, acl_delete = 8, acl_rendn = 16, }; struct assertion { const char* attr; uint32 where; const char* what; struct assertion* sameas; }; struct acl { struct assertion login,target; const char* attrib; uint32 anum; unsigned short may,maynot; struct acl* next; }; static unsigned long lines; static stralloc x; void parseerror() { char buf[FMT_ULONG]; buf[fmt_ulong(buf,lines)]=0; die(1,"parse error in line ",buf); } int skipws(buffer* in) { char c; for (;;) { if (in->p < in->n && buffer_feed(in)<1) return 0; c=*buffer_peek(in); if (c=='\n') ++lines; if (c==' ' || c=='\n' || c=='\t') { buffer_getc(in,&c); continue; } else if (c=='#') { for (;;) { int r=buffer_getc(in,&c); if (r!=1) return r; if (c=='\n') { ++lines; break; } } } else return 1; } return 1; } int parseacldn(buffer* in,struct assertion* a) { int r; /* possible forms: * -> "dn", Any dn:*foo -> "dn", "*foo" */ a->sameas=0; if ((r=skipws(in))!=1) return r; stralloc_zero(&x); do { r=buffer_get_token_sa(in,&x," \t",2); if (r!=1) return r; if (x.len>0 && x.s[x.len-1]=='\\') { x.s[--x.len]=' '; continue; } } while (x.len==0); stralloc_chop(&x); if (!stralloc_0(&x)) return -1; r=byte_chr(x.s,x.len,':'); if (x.s[r]==':') { x.s[r]=0; if (str_equal(x.s,"dn")) { a->attr=Dn; a->what=strdup(x.s+r+1); if (!a->what) return -1; } else { a->attr=malloc(x.len); if (!a->attr) return -1; byte_copy((char*)a->attr,x.len,x.s); a->what=a->attr+r+1; } } else { a->attr=Dn; if (str_equal(x.s,"*")) a->what=Any; else if (str_equal(x.s,"self")) a->what=Self; else { a->what=strdup(x.s); if (!a->what) return -1; } } return 1; } int parseaclattrib(buffer* in,struct acl* a) { /* possible forms: cn,sn mail * */ int r; a->attrib=0; if ((r=skipws(in))!=1) return r; if (in->p < in->n && buffer_feed(in)<1) return 0; { char c=*buffer_peek(in); if (c=='+' || c=='-') { a->attrib=Any; return 1; } } r=buffer_get_new_token_sa(in,&x," \t",2); if (r!=1) return r; stralloc_chop(&x); if (!stralloc_0(&x)) return -1; if (str_equal(x.s,"*")) { a->attrib=Any; return 1; } return ((a->attrib=strdup(x.s))?1:-1); } int parseaclpermissions(buffer* in,struct acl* a) { char c; int r; unsigned short* s; a->may=a->maynot=0; s=&a->may; for (;;) { r=buffer_getc(in,&c); if (r<1) return r; switch (c) { case '+': s=&a->may; break; case '-': s=&a->maynot; break; case 'r': *s|=acl_read; break; case 'w': *s|=acl_write; break; case 'a': *s|=acl_auth; break; case 'd': *s|=acl_delete; break; case 'R': *s|=acl_rendn; break; case ' ': case '\t': case '\n': break; case ';': return 1; default: parseerror(); } } } static int parseacl(buffer* in,struct acl* a) { int i,r; char c; if ((r=skipws(in))!=1) return r; for (i=0; i<3; ++i) if ((r=buffer_getc(in,&c))!=1 && c!="acl"[i]) { if (r==0 && i==0) return 0; parseerror(); } if ((r=parseacldn(in,&a->login))!=1) return r; if ((r=parseacldn(in,&a->target))!=1) return r; if ((r=parseaclattrib(in,a))!=1) return r; if ((r=parseaclpermissions(in,a))!=1) return r; a->next=0; return 1; } static void fold(struct assertion* a,struct assertion* b) { if (a->sameas || b->sameas) return; if (a->attr==b->attr || str_equal(a->attr,b->attr)) if (a->what==b->what || str_equal(a->what,b->what)) b->sameas=a; } static void optimize(struct acl* a) { struct acl* b; for (; a; a=a->next) for (b=a; b; b=b->next) { fold(&a->login,&b->login); fold(&a->target,&b->target); fold(&a->login,&a->target); fold(&a->login,&b->target); fold(&b->login,&a->target); fold(&b->login,&b->target); } } static struct acl* root; int readacls(const char* filename) { buffer b; struct acl **next, a; int r; root=0; next=&root; if (buffer_mmapread(&b,filename)==-1) return -1; while ((r=parseacl(&b,&a))!=-1) { *next=malloc(sizeof(struct acl)); if (!*next) diesys(1,"malloc"); **next=a; next=&(*next)->next; if (r==0) break; } if (r==-1) parseerror(); buffer_close(&b); optimize(root); return 0; } /* given a DN a (logged in as DN b), we need to quickly find out what * kind of permissions we have for an attribute c. To make this extra * quick, I'm only comparing DNs if the rule * a) grants or denies the permission I'm interested in (bit test) * b) actually says something about the attribute I'm interested in. * To make b) cheap, I'll not actually compare attribute strings, but * I'll compare the offset of the attribute name in the mmapped file. * The * attribute is represented as 0. An attribute that is not found * in the mmapped file is represented as -1. */ static uint32 acl_map(char* map,char* x,const char* s,unsigned int attribute_count) { unsigned int i; for (i=0; imaplen-2) { carp("invalid offset in attribute table"); return; } /* can't happen */ if (case_equals(map+j,"dn")) dn_ofs=j; else if (case_equals(map+j,"objectClass")) oc_ofs=j; } for (a=root; a; a=a->next) { a->anum=(uint32)-1; if (a->attrib==Any) a->anum=0; else a->anum=acl_map(map,maplen,x,a->attrib,attribute_count); if (a->login.attr==Dn) a->login.where=dn_ofs; else if (case_equals(a->login.attr,"objectClass")) a->login.where=oc_ofs; else a->login.where=acl_map(map,x,a->login.attr,attribute_count); if (a->target.attr==Dn) a->target.where=dn_ofs; else if (case_equals(a->target.attr,"objectClass")) a->target.where=oc_ofs; else a->target.where=acl_map(map,x,a->target.attr,attribute_count); #if 0 if (a->anum==-1) printf("no offset found for %s\n",a->attrib); if (a->login.where==-1) printf("no offset found for %s\n",a->login.attr); if (a->target.where==-1) printf("no offset found for %s\n",a->target.attr); #endif } } extern uint32 dn_ofs,objectClass_ofs; int acl_allowed(char* map,unsigned long maplen,const char* logindn,const char* targetdn,uint32 attr,unsigned short wanted) { struct acl* a; struct assertion* l; for (a=root; a; a=a->next) { if (((a->may|a->maynot)&wanted) && /* acl applies to action we want to do */ (!a->anum || a->anum==attr)) { /* acl applies to this attribute */ l=&a->login; if (l->sameas) l=l->sameas; /* first, see if logindn matches */ if (logindn==0) { /* special case: anonymous bind. * Do not match if login assertion is not "Any" */ if (l->attr != Any) continue; } else if (logindn>=map && logindn<=map+maplen) { uint32 n=uint32_read(logindn); uint32 v=0; if (l->attr != Any) { v=ldap_find_attr_value(logindn,l->where); if (v==0) continue; /* attribute not there, no match */ if (l->what != Any) { if (l->what[0]=='*') { /* suffix match */ } else if (l->what[strlen(l->what)-1]=='*') { /* prefix match */ } else { /* direct match */ /* TODO XXX FIXME */ } } } } else die(1,"unhandled case: logindn outside map"); } #if 0 struct assertion { const char* attr; uint32 where; const char* what; struct assertion* sameas; }; #endif } } #if 0 void dumpacls() { struct acl* a; for (a=root; a; a=a->next) { printf("\n--=[ record at %p ]=--\n",a); printf("%s=%s %s=%s %s\n",a->login.attr,a->login.what,a->target.attr,a->target.what,a->attrib); } } #endif #ifdef MAIN int main() { unsigned long filelen; char* map=mmap_read("data",&filelen); if (readacls("acls")==-1) die(1,"readacls failed"); // dumpacls(); acl_offsets(map,filelen); return 0; } #endif