diff --git a/.cvsignore b/.cvsignore index 148c0c6..08e4846 100644 --- a/.cvsignore +++ b/.cvsignore @@ -22,3 +22,4 @@ t6 acl acls dumpacls +journal diff --git a/FORMAT b/FORMAT index 25e1c76..841c0cc 100644 --- a/FORMAT +++ b/FORMAT @@ -96,12 +96,3 @@ The syntax of the list should be: uint32_t attributes[]; /* offsets of attribute names in stringtab, terminated by 0. Empty list means: all */ -Typische ACL: - -access to dn="ou=(Fraktion-[^,]+),ou=Fraktionen,o=bundestag,c=de" attr=userPassword - by self write - by anonymous auth - by group="cn=Gruppe A,ou=Administration,o=bundestag,c=de" write - by group="cn=$1,ou=Administration,o=bundestag,c=de" write - by * none - diff --git a/acl.c b/acl.c index 8777b2c..d400ced 100644 --- a/acl.c +++ b/acl.c @@ -143,7 +143,8 @@ int parseaclattrib(buffer* in,struct acl* a) { a->anum=1; return 1; } - if (!(a->attrib=strdup(x.s))) return -1; + if (!(a->attrib=malloc(x.len))) return -1; + memcpy(a->attrib,x.s,x.len); { unsigned int i,j; j=1; @@ -182,7 +183,7 @@ static int parseacl(buffer* in,struct acl* a) { 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=buffer_getc(in,&c))!=1 || c!="acl"[i]) { if (r==0 && i==0) return 0; parseerror(); } @@ -242,7 +243,8 @@ int readacls(const char* filename) { while ((r=parseacl(&b,&a))==1) { *next=malloc(sizeof(struct acl)); if (!*next) diesys(1,"malloc"); - **next=a; + byte_copy(*next,sizeof(a),&a); +// **next=a; next=&(*next)->next; if (r==0) break; } @@ -264,7 +266,7 @@ int marshalfilter(stralloc* x,struct assertion* a) { unsigned long l=fmt_ldapsearchfilter(0,a->f); tmp=alloca(l+10); // you never know if (fmt_ldapsearchfilter(tmp,a->f)!=l) { - buffer_putmflush(buffer_2,"internal error!\n"); + buffer_putsflush(buffer_2,"internal error!\n"); exit(1); } return stralloc_catb(x,tmp,l); @@ -316,7 +318,7 @@ int marshal(char* map,size_t filelen,const char* filename) { ++i; if (!marshalfilter(&x,&a->subject)) { nomem: - buffer_putmflush(buffer_2,"out of memory!\n"); + buffer_putsflush(buffer_2,"out of memory!\n"); exit(1); } // printf("marshalled \"%s\" to %ld\n",a->subject.filterstring,F[i-1]); @@ -342,6 +344,7 @@ nomem: for (a=root; a; a=a->next) { unsigned int l=0; +// printf("a->anum = %lu\nsizeof(*a->attrs) = %lu\n",a->anum,sizeof(*a->attrs)); if (!(a->attrs=malloc(a->anum*sizeof(*a->attrs)))) goto nomem; a->attrs[l]=a->attrib; ++l; @@ -489,7 +492,7 @@ int main(int argc,char* argv[]) { char* map=mmap_read(filename,&filelen); if (filelen<5*4 || uint32_read(map)!=0xfefe1da9) { - buffer_putmflush(buffer_2,"not a valid tinyldap data file!\n"); + buffer_putsflush(buffer_2,"not a valid tinyldap data file!\n"); exit(0); } diff --git a/acls b/acls index 16e646f..f796202 100644 --- a/acls +++ b/acls @@ -5,6 +5,6 @@ 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; +acl (dn=*ou=admin,d=fefe,c=de) (dn=*,d=fefe,c=de) * +rwdR; # everyone can read everything else acl * * * +r; diff --git a/auth.c b/auth.c index 84aaebb..dd4ceee 100644 --- a/auth.c +++ b/auth.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "ldap.h" #include "auth.h" #include "str.h" diff --git a/ldap.h b/ldap.h index eb6cbbd..35d82a8 100644 --- a/ldap.h +++ b/ldap.h @@ -74,7 +74,7 @@ struct SearchResultEntry { struct Modification { enum { Add=0, Delete=1, Replace=2 } operation; struct string AttributeDescription; /* ? */ - struct AttributeDescriptionList vals; + struct AttributeDescriptionList* vals; struct Modification* next; }; diff --git a/parse.c b/parse.c index f59cef3..2da9d50 100644 --- a/parse.c +++ b/parse.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "buffer.h" #include "ldif.h" #include "mduptab.h" diff --git a/scan_ldapmodifyrequest.c b/scan_ldapmodifyrequest.c index 0edb686..04d79f3 100644 --- a/scan_ldapmodifyrequest.c +++ b/scan_ldapmodifyrequest.c @@ -52,19 +52,17 @@ size_t scan_ldapmodifyrequest(const char* src,const char* max,struct ModifyReque { size_t iiislen; /* waah, _four_ levels of indirection! It doesn't get more inefficient than this */ const char* iimax; - struct AttributeDescriptionList* ilast=0; + struct AttributeDescriptionList** ilast=0; if (!(tmp=scan_asn1SET(src+res,max,&iiislen))) goto error; res+=tmp; iimax=src+res+iiislen; if (src+res+iiislen!=imax) goto error; + ilast=&last->vals; while (src+resnext=ilast; ilast=x; - } else - ilast=&last->vals; - if (!(tmp=scan_ldapstring(src+res,imax,&ilast->a))) goto error; + if (!(*ilast=malloc(sizeof(struct AttributeDescriptionList)))) goto error; + if (!(tmp=scan_ldapstring(src+res,imax,&(*ilast)->a))) goto error; + (*ilast)->next=0; + ilast=&(*ilast)->next; res+=tmp; } } @@ -86,6 +84,6 @@ static void free_mod(struct Modification* m) { } void free_ldapmodifyrequest(struct ModifyRequest* m) { - free_ldapadl(m->m.vals.next); + free_ldapadl(m->m.vals); free_mod(m->m.next); } diff --git a/tinyldap.c b/tinyldap.c index f419899..fb241ee 100644 --- a/tinyldap.c +++ b/tinyldap.c @@ -184,6 +184,7 @@ struct Filter** Filters; char Self[]="self"; char Any[]="*"; uint32 authenticated_as; +char* authenticated_as_str; struct acl { uint32 subject,object; /* index of filter for subject,object */ @@ -849,9 +850,12 @@ static int checkacl(uint32 recofs,uint32 attrofs,unsigned long operation,struct int match=0; if (Filters[Acls[j]->object]==(struct Filter*)Any) match=1; - else if (Filters[Acls[j]->object]==(struct Filter*)Self) - match=(recofs==authenticated_as); - else if (recofs) + else if (Filters[Acls[j]->object]==(struct Filter*)Self) { + if (authenticated_as==0 && authenticated_as_str) + match=!strcmp(map+uint32_read(map+recofs+8),authenticated_as_str); + else + match=(recofs==authenticated_as); + } else if (recofs) match=ldap_matchfilter_mapped(recofs,Filters[Acls[j]->object]); else if (sre) match=ldap_matchfilter_sre(sre,Filters[Acls[j]->object]); @@ -895,7 +899,7 @@ static int checkacl_hn(struct hashnode* hn,const unsigned char* attr,unsigned lo if (Filters[Acls[j]->object]==(struct Filter*)Any) match=1; else if (Filters[Acls[j]->object]==(struct Filter*)Self) - match=dn && !strcmp((char*)hn->dn,map+authenticated_as); + match=dn && !strcmp((char*)hn->dn,authenticated_as_str); else if (dn) match=ldap_matchfilter_hn(hn,Filters[Acls[j]->object]); else @@ -1105,6 +1109,22 @@ static int copyadl(struct AttributeDescriptionList** dest,struct AttributeDescri return 0; } +/* semi-deep copy an attribute description list */ +static int copyadl2(struct AttributeDescriptionList** dest,struct AttributeDescriptionList* src) { + *dest=0; + while (src) { + if (!(*dest=malloc(sizeof(*src)))) return -1; + byte_zero(*dest,sizeof(*src)); + (*dest)->a=src->a; + (*dest)->attrofs=src->attrofs; + dest=&(*dest)->next; + src=src->next; + } + return 0; +} + + + #if 0 /* deep copy a partial attribute list */ @@ -1148,6 +1168,97 @@ static int addreq2sre(struct SearchResultEntry* sre,struct AddRequest* ar) { return 0; } +/* small helper for modreq2sre */ +static int mr2sreh1(struct PartialAttributeList** dest,struct Modification* src) { + *dest=0; + while (src) { + if (!(*dest=malloc(sizeof(**dest)))) return -1; + byte_zero(*dest,sizeof(**dest)); + (*dest)->type=src->AttributeDescription; + if (copyadl2(&(*dest)->values,src->vals)) return -1; + dest=&(*dest)->next; + src=src->next; + } + return 0; +} + +/* We need two versions for the modify request. The first one just creates a stupid + * SearchResultEntry out of just the changed attributes, which is then only used for ACL + * matching. The second version merges in the existing record to form the modified + * record. This is the first version for ACL checking. */ +static int modreq2sre(struct SearchResultEntry* sre,struct ModifyRequest* mr) { + byte_zero(sre,sizeof(*sre)); + sre->objectName=mr->object; + if (!(sre->attributes=malloc(sizeof(*sre->attributes))) || + mr2sreh1(&sre->attributes,&mr->m)) { + free_ldapsearchresultentry(sre); + return -1; + } + return 0; +} + +static int applymodreq(struct hashnode* hn,struct ModifyRequest* mr,struct SearchResultEntry* sre) { + struct PartialAttributeList** l; + struct Modification* m; + size_t i; + sre->objectName.l=strlen((char*)hn->dn); + sre->objectName.s=(char*)hn->dn; + sre->attributes=0; + l=&(sre->attributes); + /* go through all the attributes in the hash node and apply the modifications */ + for (i=0; in; ++i) { + enum { Keep, Drop } todo=Keep; + for (m=&mr->m; m; m=m->next) { + if (!matchstring(&m->AttributeDescription,(char*)hn->a[i].a)) { + /* same attribute */ + if (m->operation==Add) + continue; + else if (m->operation==Delete) { + /* if it's delete, we need to check the value list */ + struct AttributeDescriptionList* adl=m->vals; + if (!adl) + todo=Drop; /* if the list is empty, drop all */ + else + for (adl=m->vals; adl; adl=adl->next) { + if (!matchstring(&adl->a,(char*)hn->a[i].v)) { + todo=Drop; + break; + } + } + } else + todo=Drop; + } + if (todo==Drop) break; + } + if (todo==Keep) { + *l=malloc(sizeof(**l)); + if (!*l) return -1; + (*l)->next=0; + (*l)->type.s=bstrfirst((char*)hn->a[i].a); + (*l)->type.l=bstrlen((char*)hn->a[i].a); + if (!((*l)->values=malloc(sizeof(*(*l)->values)))) return -1; + (*l)->values->a.s=bstrfirst((char*)hn->a[i].v); + (*l)->values->a.l=bstrlen((char*)hn->a[i].v); + (*l)->values->attrofs=0; + (*l)->values->next=0; + l=&(*l)->next; + } + } + /* then add all the "replace" or "add" attributes */ + for (m=&mr->m; m; m=m->next) { + if ((m->operation==Add || m->operation==Replace) && m->vals) { + *l=malloc(sizeof(**l)); + if (!*l) return -1; + (*l)->next=0; + (*l)->type.s=m->AttributeDescription.s; + (*l)->type.l=m->AttributeDescription.l; + if (copyadl2(&(*l)->values,m->vals)==-1) return -1; + l=&(*l)->next; + } + } + return 0; +} + /* write a search result entry to a file */ static int writesretofd(int fd,struct SearchResultEntry* sre) { /* we have no locking, but we open using O_APPEND, so the OS synchronizes for us as long @@ -1204,6 +1315,10 @@ static struct hashnode** dn_in_journal2(const char* dn,size_t dnlen); static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) { struct Filter f; struct hashnode** tmphn; + if (dn->l<1 || !dn->s) { + buffer_putsflush(buffer_2,"lookupdn called for NULL dn!\n"); + return -1; + } if ((tmphn=dn_in_journal2(dn->s,dn->l)) && *tmphn) { *hn=*tmphn; *index=-1; @@ -1213,20 +1328,18 @@ static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) { f.type=EQUAL; f.ava.desc.l=2; f.ava.desc.s="dn"; f.ava.value=*dn; - f.next=0; + f.next=f.x=0; fixup(&f); if (!indexable(&f)) { buffer_putsflush(buffer_2,"no index for dn, lookup failed!\n"); return -1; } else { struct bitfield result; - size_t i,done; + size_t i; result.bits=alloca(record_set_length*sizeof(unsigned long)); useindex(&f,&result); - done=0; if (result.first>result.last) return 0; - done=0; assert(result.last<=record_count); for (i=result.first; i<=result.last; ) { if (!result.bits[i/(8*sizeof(long))]) { @@ -1300,17 +1413,64 @@ int handle(int in,int out) { buffer_putsflush(buffer_2,".\n"); } if (name.l) { - struct Filter f; struct string password; - f.type=EQUAL; - scan_ldapstring(buf+res+tmp,buf+res+len,&password); - f.ava.desc.l=2; f.ava.desc.s="dn"; - f.ava.value=name; - f.next=0; - fixup(&f); + size_t idx; + struct hashnode* hn; + int err=success; - if (!indexable(&f)) { - buffer_putsflush(buffer_2,"no index for dn, bind failed!\n"); + scan_ldapstring(buf+res+tmp,buf+res+len,&password); + + switch (lookupdn(&name,&idx,&hn)) { + case -1: err=operationsError; break; + case 1: break; + case 0: err=noSuchObject; break; + default: err=operationsError; + } + if (err!=success) + goto authfailure; + else { + char* c=0; + uint32 authdn; + char* authdn_str=0; + if (idx==(size_t)-1) { // found in journal + size_t i; + for (i=0; in; ++i) + if (!strcmp((char*)hn->a[i].a,"userPassword")) { + c=(char*)hn->a[i].v; + authdn=0; + authdn_str=(char*)hn->dn; + break; + } + } else { // found in db + uint32 j; + uint32_unpack(map+indices_offset+4*idx,&j); + uint32_unpack(map+j+8,&authdn); + authdn_str=map+authdn; + if (!(j=ldap_find_attr_value(j,userPassword_ofs))) { + buffer_putsflush(buffer_2,"no userPassword attribute found, bind failed!\n"); + goto authfailure; + } + c=map+j; + } + + if (check_password(c,&password)) { + authenticated_as=authdn; + authenticated_as_str=authdn_str; + if (acls) { + size_t i; + for (i=0; isubject; + if (!acl_ec_subjects[j]) { + if (authdn==0) // authenticated against hashnode + acl_ec_subjects[j]=ldap_matchfilter_hn(hn,Filters[j]); + else // authenticated against mapped db + acl_ec_subjects[j]=ldap_matchfilter_mapped(authdn,Filters[j]); + } + } + } + } else authfailure: { char outbuf[1024]; @@ -1321,56 +1481,8 @@ authfailure: write(out,outbuf+s-hlen,len+hlen); continue; } - } else { - struct bitfield result; - size_t i,done; - result.bits=alloca(record_set_length*sizeof(unsigned long)); - useindex(&f,&result); - done=0; - if (result.first>result.last) { - buffer_putsflush(buffer_2,"no matching dn found, bind failed!\n"); - goto authfailure; - } - done=0; - assert(result.last<=record_count); - for (i=result.first; i<=result.last; ) { - if (!result.bits[i/(8*sizeof(long))]) { - i+=8*sizeof(long); - continue; - } - for (; i<=result.last; ++i) { - if (isset(&result,i)) { - uint32 j,authdn; - const char* c; - uint32_unpack(map+indices_offset+4*i,&j); - uint32_unpack(map+j+8,&authdn); - if (!(j=ldap_find_attr_value(j,userPassword_ofs))) { - buffer_putsflush(buffer_2,"no userPassword attribute found, bind failed!\n"); - goto authfailure; - } - c=map+j; -#if 0 - buffer_puts(buffer_2,"compare "); - buffer_puts(buffer_2,c); - buffer_puts(buffer_2," with "); - buffer_put(buffer_2,f.ava.value.s,f.ava.value.l); - buffer_putsflush(buffer_2,".\n"); -#endif - if (check_password(c,&password)) { - done=1; - authenticated_as=authdn; - goto found; - } - } - } - } - if (!done) { - buffer_putsflush(buffer_2,"wrong password, bind failed!\n"); - goto authfailure; - } } } -found: { char outbuf[1024]; int s=100; @@ -1490,9 +1602,10 @@ found: case ModifyRequest: { struct ModifyRequest mr; - int tmp; + int tmp,err=success; buffer_putsflush(buffer_2,"modifyrequest!\n"); if ((tmp=scan_ldapmodifyrequest(buf+res,buf+res+len,&mr))) { + struct SearchResultEntry sre; buffer_puts(buffer_1,"modify request: dn \""); buffer_put(buffer_1,mr.object.s,mr.object.l); buffer_putsflush(buffer_1,"\"\n"); @@ -1504,7 +1617,7 @@ found: buffer_put(buffer_1,mr.m.AttributeDescription.s,mr.m.AttributeDescription.l); buffer_puts(buffer_1,"\n"); { - struct AttributeDescriptionList* x=&mr.m.vals; + struct AttributeDescriptionList* x=mr.m.vals; do { buffer_puts(buffer_1," -> \""); buffer_put(buffer_1,x->a.s,x->a.l); @@ -1513,12 +1626,60 @@ found: } while (x); } /* TODO: do something with the modify request ;-) */ - free_ldapmodifyrequest(&mr); - } else { - buffer_putsflush(buffer_2,"couldn't parse modify request!\n"); - exit(1); + if (acls) { + /* convert modifyrequest to searchresultentry */ + modreq2sre(&sre,&mr); + /* 1. check ACLs */ + if (checkacl(0,0,acl_write,&sre)!=1) + err=insufficientAccessRights; + free_ldapsearchresultentry(&sre); + } + if (err==success) { + /* 2. check if there already is a record with this dn */ + struct hashnode* hn; + size_t idx; + switch (lookupdn(&mr.object,&idx,&hn)) { + case -1: err=operationsError; break; + case 1: break; + case 0: err=noSuchObject; break; + default: err=operationsError; + } + if (err==success) { +#if 1 + /* 3. apply modifications to record to get new record */ + if (!applymodreq(hn,&mr,&sre)) { + /* 4. write record to "data.upd" */ + int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600); + if (fd==-1) + err=operationsError; + else { + if (writesretofd(fd,&sre)==-1) + err=operationsError; + close(fd); + } + } else + err=operationsError; + free_ldapsearchresultentry(&sre); +#else + err=operationsError; +#endif + } + } + } else + err=protocolError; + + { + char outbuf[1024]; + int s=100; + int len=fmt_ldapresult(outbuf+s,err,"","",""); + int hlen=fmt_ldapmessage(0,messageid,AddResponse,len); + fmt_ldapmessage(outbuf+s-hlen,messageid,AddResponse,len); + write(out,outbuf+s-hlen,len+hlen); } + + free_ldapmodifyrequest(&mr); } + break; case AbandonRequest: buffer_putsflush(buffer_2,"AbandonRequest!\n"); /* do nothing */ @@ -1632,7 +1793,7 @@ unsigned long hash2(const unsigned char* c) { h ^= *c; ++c; } - return h; + return (uint32)h; } #define HASHTABSIZE 8191 @@ -1689,7 +1850,8 @@ static struct hashnode** dn_in_journal(unsigned char* dn) { static struct hashnode** dn_in_journal2(const char* dn,size_t dnlen) { unsigned long hashval; struct hashnode** hn; - hashval=hash2((const unsigned char*)dn); + hashval=hash((const unsigned char*)dn,dnlen); +// printf("lookup: \"%.*s\" -> %lu\n",dnlen,dn,hashval); hn=hashtab+(hashval%HASHTABSIZE); while (*hn) { if ((*hn)->hashval==hashval) { @@ -1710,6 +1872,7 @@ int parse_callback(struct ldaprec* l) { struct hashnode** hn; if (l->dn==(uint32)-1) return -1; hashval=hash2((unsigned char*)stringtable.root+l->dn); +// printf("journal: \"%s\" -> %lu\n",stringtable.root+l->dn,hashval); hn=hashtab+(hashval%HASHTABSIZE); while (*hn) { if ((*hn)->hashval==hashval) {