diff --git a/GETTING.STARTED b/GETTING.STARTED index b760ddd..dce557a 100644 --- a/GETTING.STARTED +++ b/GETTING.STARTED @@ -68,8 +68,3 @@ it runs, you can use ldapclient to query a specific record: objectName "cn=Felix von Leitner,o=fefe,c=de" mail:felix-tinyldap@fefe.de -ldapclient is quite a stupid client. It can only ask for specific -records, for example. LDAP also defines complex queries like AND and OR -compound queries or substring searches. tinyldap answers those, but to -ask those you should install openldap and use the ldapsearch tool that -comes with it. diff --git a/TODO b/TODO index 638ecfe..b5037d0 100644 --- a/TODO +++ b/TODO @@ -25,3 +25,5 @@ Think about a shared calendar in LDAP. Using ISO date format and ordered matching it can be done. Design tinyldap so this actually scales. How would conflict detection and resolution be done? Think about an iCal frontend. + +Make tinyldap a good back-end for blogs and message boards. diff --git a/acl.c b/acl.c new file mode 100644 index 0000000..d03781e --- /dev/null +++ b/acl.c @@ -0,0 +1,220 @@ +/* + 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 + +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; + 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') 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; + } while (!x.len || x.s[x.len-1]!='\\'); + 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; + 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; + 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 (buffer_getc(in,&c)!=1 && c!="acl"[i]) 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); + } +} + +int main() { + buffer b; + struct acl* root,**next, a; + int r; + root=0; next=&root; + + if (buffer_mmapread(&b,"acls")==-1) diesys(1,"buffer_mmapread"); + + 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; +} diff --git a/acls b/acls new file mode 100644 index 0000000..cfabc35 --- /dev/null +++ b/acls @@ -0,0 +1,8 @@ +# 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;