experimental acl support

This commit is contained in:
leitner
2005-08-27 20:45:44 +00:00
parent d232d9fd65
commit 40271f169a
6 changed files with 413 additions and 111 deletions

44
acl.c
View File

@@ -22,13 +22,7 @@ const char Self[]="self";
const char Dn[]="dn";
uint32 any_ofs;
enum {
acl_read = 1,
acl_write = 2,
acl_auth = 4,
acl_delete = 8,
acl_rendn = 16,
};
#include "acl.h"
struct assertion {
const char* filterstring;
@@ -202,22 +196,39 @@ static int parseacl(buffer* in,struct acl* a) {
static void fold(struct assertion* a,struct assertion* b) {
if (a==b) return;
if (a->sameas || b->sameas) return;
if (!strcmp(a->filterstring,b->filterstring))
b->sameas=a;
#if 0
printf("fold \"%s\" \"%s\"\n",a->filterstring,b->filterstring);
#endif
if (b->sameas || a->sameas) return;
if (!strcmp(a->filterstring,b->filterstring)) {
a->sameas=b;
#if 0
printf(" -> folded!\n");
#endif
}
}
static void optimize(struct acl* a) {
struct acl* origa=a;
struct acl* b;
for (; a; a=a->next)
for (b=a; b; b=b->next) {
for (b=origa; b!=a; b=b->next) {
fold(&a->subject,&b->subject);
fold(&a->object,&b->object);
fold(&a->subject,&a->object);
fold(&a->subject,&b->object);
fold(&b->subject,&a->object);
fold(&b->subject,&b->object);
fold(&a->subject,&a->object);
}
#if 0
for (a=origa; a; a=a->next) {
if (a->subject.sameas && a->subject.sameas->sameas)
puts("ARGH 1!");
if (a->object.sameas && a->object.sameas->sameas)
puts("ARGH 2!");
}
#endif
}
static struct acl* root;
@@ -246,6 +257,8 @@ int readacls(const char* filename) {
int marshalfilter(stralloc* x,struct assertion* a) {
if (a->filterstring==Self)
return stralloc_catb(x,Self,5);
if (a->filterstring==Any)
return stralloc_catb(x,Any,2);
else {
char* tmp;
unsigned long l=fmt_ldapsearchfilter(0,a->f);
@@ -306,11 +319,13 @@ nomem:
buffer_putmflush(buffer_2,"out of memory!\n");
exit(1);
}
// printf("marshalled \"%s\" to %ld\n",a->subject.filterstring,F[i-1]);
}
if (!a->object.sameas) {
F[i]=x.len+filter_offset;
++i;
if (!marshalfilter(&x,&a->object)) goto nomem;
// printf("marshalled \"%s\" to %ld\n",a->object.filterstring,F[i-1]);
}
}
attribute_count=uint32_read(map+4);
@@ -448,7 +463,7 @@ shortwrite:
if (write(fd,tmp,4)!=4) goto shortwrite;
/* uint32 offsets_to_filters_in_scan_ldapsearchfilter_format[filter_count+1]; */
for (i=0; i<filters+1; ++i)
uint32_pack(F+i,F[i]);
uint32_pack((char*)(F+i),F[i]);
if (write(fd,F,(filters+1)*4) != (ssize_t)((filters+1)*4)) goto shortwrite;
/* write marshalled filter data */
if (write(fd,x.s,x.len) != (ssize_t)x.len) goto shortwrite;
@@ -458,7 +473,7 @@ shortwrite:
/* uint32 offsets_to_acls[acl_count] */
fixup=lseek(fd,0,SEEK_CUR)+acls*4;
for (i=0; i<acls; ++i)
uint32_pack(A+i,A[i]+fixup);
uint32_pack((char*)(A+i),A[i]+fixup);
if (write(fd,A,acls*4) != (ssize_t)(acls*4)) goto shortwrite;
/* write marshalled acl data */
if (write(fd,y.s,y.len) != (ssize_t)y.len) goto shortwrite;
@@ -485,3 +500,4 @@ int main(int argc,char* argv[]) {
return 0;
}
#endif

9
acl.h Normal file
View File

@@ -0,0 +1,9 @@
enum {
acl_read = 1,
acl_write = 2,
acl_auth = 4,
acl_delete = 8,
acl_rendn = 16,
};

2
acls
View File

@@ -6,3 +6,5 @@ acl * * userPassword -r;
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;
# everyone can read everything else
acl * * * +r;

View File

@@ -58,7 +58,7 @@ kaputt:
buffer_puts(buffer_1,": ");
if (byte_equal(map+ofs,4,"self"))
buffer_puts(buffer_1,"self");
else if (byte_equal(map+ofs,3,"any"))
else if (byte_equal(map+ofs,2,"*"))
buffer_puts(buffer_1,"any");
else if (scan_ldapsearchfilter(map+ofs,map+filelen,&f)!=0) {
unsigned long l=fmt_ldapsearchfilterstring(0,f);

1
ldif.h
View File

@@ -26,3 +26,4 @@ int ldap_match(struct ldaprec* r,struct SearchRequest* sr);
int ldap_match_mapped(uint32 ofs,struct SearchRequest* sr);
int ldap_match_present(uint32 ofs,uint32 attrofs);
uint32 ldap_find_attr_value(uint32 ofs,uint32 attrofs);
int ldap_matchfilter_mapped(uint32 ofs,struct Filter* f);

View File

@@ -23,6 +23,8 @@
#endif
#include "case.h"
#include <signal.h>
#include "uint16.h"
#include "acl.h"
#ifdef DEBUG
#define verbose 1
@@ -32,9 +34,17 @@
#define debug 0
#endif
char* map;
long filelen;
/* basic operation: the whole data file is mmapped read-only at the beginning and stays there. */
char* map; /* where the file is mapped */
long filelen; /* how many bytes are mapped (the whole file) */
uint32 magic,attribute_count,record_count,indices_offset,size_of_string_table;
/* these are the first values from the file, see the file "FORMAT"
* basic counts and offsets needed to calculate the positions of
* the data structures in the file. */
/* We do queries with indexes by evaluating all the filters (subexpressions) that can be
* answered with an index, and then getting a bit vector, one bit for each record. */
/* how many longs are needed to have one bit for each record? */
uint32 record_set_length;
@@ -42,6 +52,195 @@ uint32 record_set_length;
/* some pre-looked-up attribute offsets to speed up ldap_match_mapped */
uint32 dn_ofs,objectClass_ofs,userPassword_ofs,any_ofs;
/* to avoid string compares, we don't work with char* but with uint32 (offsets within
* the mmapped file) whenever it's about values that will be mentioned in the file, such
* as attribute names. So, for each filter we get sent, we look up the attributes in the
* file, so we have the offsets to save the strcmp later.
* In the same step, we also get the attribute flags. tinyldap does not have LDAP
* schemas, so it does not know which attributes are case sensitive and which aren't. So,
* this is saved in a flag, which is currently set by addindex when a case insensitive
* index is created. */
/* This routine is called when we got a Filter and now want to look up the offsets for
* each attribute mentioned in it */
/* recursively fill in attrofs and attrflag */
static void fixup(struct Filter* f) {
if (!f) return;
switch (f->type) {
case EQUAL:
case SUBSTRING:
case GREATEQUAL:
case LESSEQUAL:
case PRESENT:
case APPROX:
{
char* x=map+5*4+size_of_string_table;
unsigned int i;
f->attrofs=f->attrflag=0;
for (i=0; i<attribute_count; ++i) {
uint32 j=uint32_read(x);
if (!matchcasestring(&f->ava.desc,map+j)) {
f->attrofs=j;
uint32_unpack(x+attribute_count*4,&f->attrflag);
break;
}
x+=4;
}
if (!f->attrofs) {
buffer_puts(buffer_2,"cannot find attribute \"");
buffer_put(buffer_2,f->ava.desc.s,f->ava.desc.l);
buffer_putsflush(buffer_2,"\"!\n");
}
}
case AND:
case OR:
case NOT:
if (f->x) fixup(f->x);
default:
break;
}
if (f->next) fixup(f->next);
}
#if 0
_ _
__ _ ___| | ___ _ _ _ __ _ __ ___ _ __| |_
/ _` |/ __| | / __| | | | '_ \| '_ \ / _ \| '__| __|
| (_| | (__| | \__ \ |_| | |_) | |_) | (_) | | | |_
\__,_|\___|_| |___/\__,_| .__/| .__/ \___/|_| \__|
|_| |_|
#endif
uint32 filters,acls; /* number of filters and acls in the ACL section of the data file */
uint32 filtertab,acltab; /* offsets of the filter and acl table in the data file */
char* acl_ec_subjects; /* if the n'th byte here is nonzero, then the current subject
(the dn the user is logged in as) matches the n'th filter, i.e.
the ACLs with this subject need to be applied. */
struct Filter** Filters;
char Self[]="self";
char Any[]="*";
uint32 authenticated_as;
struct acl {
uint32 subject,object; /* index of filter for subject,object */
uint16 may,maynot;
uint32 attrs;
uint32 Attrs[1];
};
struct acl** Acls;
static void load_acls() {
uint32 ofs;
uint32 acl_ofs;
acl_ofs=0;
for (ofs=indices_offset+record_count*4; ofs<(unsigned long)filelen;) {
uint32 index_type,next;
uint32_unpack(map+ofs,&index_type);
uint32_unpack(map+ofs+4,&next);
if (index_type==2) { acl_ofs=ofs; break; }
if (next<ofs || next>filelen) {
kaputt:
buffer_putsflush(buffer_1,"broken data file!\n");
exit(1);
}
ofs=next;
}
filters=acls=0; acl_ec_subjects=0;
if (acl_ofs) {
uint32 i;
ofs=acl_ofs+8;
filters=uint32_read(map+ofs);
acl_ec_subjects=malloc(2*filters);
filtertab=ofs+4;
ofs=filtertab+filters*4;
if (ofs<filtertab) goto kaputt;
Filters=malloc(sizeof(Filters[0])*filters);
if (!Filters) goto kaputt;
for (i=0; i<filters; ++i) {
struct Filter* f;
ofs=uint32_read(map+filtertab+i*4);
if (ofs<filtertab || ofs>filelen) goto kaputt;
if (byte_equal(map+ofs,4,"self"))
f=(struct Filter*)Self;
else if (byte_equal(map+ofs,2,"*"))
f=(struct Filter*)Any;
else if (scan_ldapsearchfilter(map+ofs,map+filelen,&f)!=0) {
fixup(f);
if (debug) {
unsigned long l=fmt_ldapsearchfilterstring(0,f);
char* buf=malloc(l+23);
if (!buf) goto kaputt;
buf[fmt_ldapsearchfilterstring(buf,f)]=0;
free(buf);
}
} else goto kaputt;
Filters[i]=f;
}
ofs=uint32_read(map+filtertab+filters*4);
if (ofs<filtertab || ofs>filelen-4) goto kaputt;
acls=uint32_read(map+ofs);
acltab=ofs+4;
Acls=malloc(sizeof(Acls[0])*acls);
if (!Acls) goto kaputt;
for (i=0; i<acls; ++i) {
uint32 j;
uint32 tmp,cnt;
ofs=uint32_read(map+acltab+i*4);
if (ofs>filelen-16) goto kaputt;
cnt=0;
for (tmp=ofs+12; tmp<filelen; tmp+=4) {
uint32 j=uint32_read(map+tmp);
if (j>tmp) goto kaputt;
if (!j) break;
++cnt;
}
Acls[i]=malloc(sizeof(struct acl)+cnt*sizeof(uint32));
if (!Acls[i]) goto kaputt;
Acls[i]->subject=uint32_read(map+ofs);
Acls[i]->object=uint32_read(map+ofs+4);
Acls[i]->may=uint16_read(map+ofs+8);
Acls[i]->maynot=uint16_read(map+ofs+10);
Acls[i]->attrs=cnt;
tmp=ofs+12;
for (j=0; j<cnt; ++j) {
uint32 x;
Acls[i]->Attrs[j]=x=uint32_read(map+tmp+4*j);
if (any_ofs==0 && map[x]=='*' && map[x+1]==0) any_ofs=x;
}
}
}
if (acls) {
uint32 i;
for (i=0; i<filters; ++i)
acl_ec_subjects[i]=(Filters[i]==(struct Filter*)Any);
}
}
/* End of ACL code */
#if 0
_ _ _
__| | ___| |__ _ _ __ _ ___ ___ __| | ___
/ _` |/ _ \ '_ \| | | |/ _` | / __/ _ \ / _` |/ _ \
| (_| | __/ |_) | |_| | (_| | | (_| (_) | (_| | __/
\__,_|\___|_.__/ \__,_|\__, | \___\___/ \__,_|\___|
|___/
#endif
#define BUFSIZE 8192
#if (debug != 0)
@@ -126,44 +325,16 @@ mergesub:
}
#endif
/* recursively fill in attrofs and attrflag */
static void fixup(struct Filter* f) {
if (!f) return;
switch (f->type) {
case EQUAL:
case SUBSTRING:
case GREATEQUAL:
case LESSEQUAL:
case PRESENT:
case APPROX:
{
char* x=map+5*4+size_of_string_table;
unsigned int i;
f->attrofs=f->attrflag=0;
for (i=0; i<attribute_count; ++i) {
uint32 j=uint32_read(x);
if (!matchcasestring(&f->ava.desc,map+j)) {
f->attrofs=j;
uint32_unpack(x+attribute_count*4,&f->attrflag);
break;
}
x+=4;
}
if (!f->attrofs) {
buffer_puts(buffer_2,"cannot find attribute \"");
buffer_put(buffer_2,f->ava.desc.s,f->ava.desc.l);
buffer_putsflush(buffer_2,"\"!\n");
}
}
case AND:
case OR:
case NOT:
if (f->x) fixup(f->x);
default:
break;
}
if (f->next) fixup(f->next);
}
#if 0
_ _ _
(_)_ __ __| | _____ __ ___ ___ __| | ___
| | '_ \ / _` |/ _ \ \/ / / __/ _ \ / _` |/ _ \
| | | | | (_| | __/> < | (_| (_) | (_| | __/
|_|_| |_|\__,_|\___/_/\_\ \___\___/ \__,_|\___|
#endif
/* find out whether this filter can be accelerated with the indices */
static int indexable(struct Filter* f) {
@@ -214,21 +385,23 @@ static int indexable(struct Filter* f) {
}
}
/* each record can have more than one attribute with the same name, i.e.
* two email addresses. Thus, the index can't just be a sorted list of
* pointers the records (because a record with two email addresses needs
* to be in the index twice, once for each email address). So our index
* is a sorted list of pointers to the attributes. Thus, a look-up in
* the index does not yield the record but the attribute. We need to be
* able to find the record for a given attribute. To do that, we
* exploit the fact that the strings in the string table are in the same
* order as the records, so we can do a binary search over the record
* table to find the record with the attribute. This does not work for
* objectClass, because the classes are stored in a different string
* table to remove duplicates. */
/* each record can have more than one attribute with the same name, i.e. two email
* addresses. Thus, the index can't just be a sorted list of pointers the records
* (because a record with two email addresses needs to be in the index twice, once for
* each email address). So our index is a sorted list of pointers to the attributes.
* Thus, a look-up in the index does not yield the record but the attribute. We need to
* be able to find the record for a given attribute. To do that, we exploit the fact that
* the strings in the string table are in the same order as the records, so we can do a
* binary search over the record table to find the record with the attribute. This does
* not work for objectClass, because the classes are stored in a different string table to
* remove duplicates. */
/* Yes, this is an evil kludge to keep index size small. However, it turned out that it
* also dominated lookup time for a relatively minor index size reduction. So index type
* 1 was added (flag f to addindex), which does not need this. The benefit is so big that
* tinyldap will drop support for type 0 indices sooner or later. Type 1 indexes are
* twice as large, and save the record number besides each index entry. */
/* this kludge is only necessary for index type 0. Index type 1 also
* saves the record number. */
/* find record given a data pointer */
static long findrec(uint32 dat) {
uint32* records=(uint32*)(map+indices_offset);
@@ -259,13 +432,13 @@ static long findrec(uint32 dat) {
return -1;
}
/* basic bit-set support: set all bits to zero */
/* basic bit-set support: set all bits to 0 */
static inline void emptyset(unsigned long* r) {
unsigned long i;
for (i=0; i<record_set_length; ++i) r[i]=0;
}
/* basic bit-set support: set all bits to zero */
/* basic bit-set support: set all bits to 1 */
static inline void fillset(unsigned long* r) {
unsigned long i;
for (i=0; i<record_set_length; ++i) r[i]=(unsigned long)-1;
@@ -499,6 +672,20 @@ static int useindex(struct Filter* f,unsigned long* bitfield) {
}
}
#if 0
_
__ _ _ _ ___ _ __ _ _ __ _ _ __ _____ _____ _ __(_)_ __ __ _
/ _` | | | |/ _ \ '__| | | | / _` | '_ \/ __\ \ /\ / / _ \ '__| | '_ \ / _` |
| (_| | |_| | __/ | | |_| | | (_| | | | \__ \\ V V / __/ | | | | | | (_| |
\__, |\__,_|\___|_| \__, | \__,_|_| |_|___/ \_/\_/ \___|_| |_|_| |_|\__, |
|_| |___/ |___/
#endif
/* this routine is called for each record matched the query. It basically puts together
* an answer LDAP message from the record and the list of attributes the other side said
* it wanted to have. */
static void answerwith(uint32 ofs,struct SearchRequest* sr,long messageid,int out) {
struct SearchResultEntry sre;
struct PartialAttributeList** pal=&sre.attributes;
@@ -529,6 +716,9 @@ static void answerwith(uint32 ofs,struct SearchRequest* sr,long messageid,int ou
}
#endif
if (acls)
byte_zero(acl_ec_subjects+filters,filters);
sre.objectName.l=bstrlen(sre.objectName.s=map+uint32_read(map+ofs+8));
sre.attributes=0;
/* now go through list of requested attributes */
@@ -555,49 +745,95 @@ static void answerwith(uint32 ofs,struct SearchRequest* sr,long messageid,int ou
while (adl) {
const char* val=0;
uint32 i=2,j;
uint32_unpack(map+ofs,&j);
#if 0
buffer_puts(buffer_2,"looking for attribute \"");
buffer_put(buffer_2,adl->a.s,adl->a.l);
buffer_putsflush(buffer_2,"\"\n");
#endif
if (!matchstring(&adl->a,"dn")) val=sre.objectName.s; else
if (!matchstring(&adl->a,"objectClass"))
val=map+uint32_read(map+ofs+12);
else {
for (; i<j; ++i)
if (!matchstring(&adl->a,map+uint32_read(map+ofs+i*8))) {
val=map+uint32_read(map+ofs+i*8+4);
++i;
break;
int ok=acls?0:1;
for (j=0; j<acls; ++j) {
/* does the ACL subject apply? */
if (!acl_ec_subjects[Acls[j]->subject]) continue;
/* does the ACL even apply to read operations? */
if ((Acls[j]->may | Acls[j]->maynot) & acl_read) {
uint32 k;
if (acl_ec_subjects[filters+Acls[j]->object]==-1) continue;
if (acl_ec_subjects[filters+Acls[j]->object]==0) {
int match=0;
if (Filters[Acls[j]->object]==(struct Filter*)Any)
match=1;
else if (Filters[Acls[j]->object]==(struct Filter*)Self)
match=(ofs==authenticated_as);
else
match=(ldap_matchfilter_mapped(ofs,Filters[Acls[j]->object]));
if (match)
acl_ec_subjects[filters+Acls[j]->object]=1;
else {
acl_ec_subjects[filters+Acls[j]->object]=-1;
continue;
}
}
for (k=0; k<Acls[j]->attrs; ++k) {
if (Acls[j]->Attrs[k]==any_ofs || !matchstring(&adl->a,map+Acls[j]->Attrs[k])) {
if (Acls[j]->may&acl_read) {
#if 0
printf("acl %u allowed serving attribute \"%.*s\"\n",j,(int)adl->a.l,adl->a.s);
#endif
ok=1;
} else {
#if 0
printf("acl %u disallowed serving attribute \"%.*s\"\n",j,(int)adl->a.l,adl->a.s);
#endif
ok=-1;
}
break;
}
}
}
if (val) {
*pal=malloc(sizeof(struct PartialAttributeList));
if (!*pal) {
nomem:
buffer_putsflush(buffer_2,"out of virtual memory!\n");
exit(1);
}
(*pal)->type=adl->a;
{
struct AttributeDescriptionList** a=&(*pal)->values;
add_attribute:
*a=malloc(sizeof(struct AttributeDescriptionList));
if (!*a) goto nomem;
(*a)->a.s=bstrfirst(val);
(*a)->a.l=bstrlen(val);
for (;i<j; ++i)
if (ok) break;
}
if (ok==1) {
uint32_unpack(map+ofs,&j);
#if 0
buffer_puts(buffer_2,"looking for attribute \"");
buffer_put(buffer_2,adl->a.s,adl->a.l);
buffer_putsflush(buffer_2,"\"\n");
#endif
if (!matchstring(&adl->a,"dn")) val=sre.objectName.s; else
if (!matchstring(&adl->a,"objectClass"))
val=map+uint32_read(map+ofs+12);
else {
for (; i<j; ++i)
if (!matchstring(&adl->a,map+uint32_read(map+ofs+i*8))) {
val=map+uint32_read(map+ofs+i*8+4);
++i;
a=&(*a)->next;
goto add_attribute;
break;
}
(*a)->next=0;
}
(*pal)->next=0;
pal=&(*pal)->next;
if (val) {
*pal=malloc(sizeof(struct PartialAttributeList));
if (!*pal) {
nomem:
buffer_putsflush(buffer_2,"out of virtual memory!\n");
exit(1);
}
(*pal)->type=adl->a;
{
struct AttributeDescriptionList** a=&(*pal)->values;
add_attribute:
*a=malloc(sizeof(struct AttributeDescriptionList));
if (!*a) goto nomem;
(*a)->a.s=bstrfirst(val);
(*a)->a.l=bstrlen(val);
for (;i<j; ++i)
if (!matchstring(&adl->a,map+uint32_read(map+ofs+i*8))) {
val=map+uint32_read(map+ofs+i*8+4);
++i;
a=&(*a)->next;
goto add_attribute;
}
(*a)->next=0;
}
(*pal)->next=0;
pal=&(*pal)->next;
}
}
adl=adl->next;
}
@@ -618,6 +854,31 @@ add_attribute:
free_ldappal(sre.attributes);
}
#if 0
_ _ _ _ _ _ _
| |__ (_) __ _| |__ | | _____ _____| | | | __| | __ _ _ __
| '_ \| |/ _` | '_ \ | |/ _ \ \ / / _ \ | | |/ _` |/ _` | '_ \
| | | | | (_| | | | | | | __/\ V / __/ | | | (_| | (_| | |_) |
|_| |_|_|\__, |_| |_| |_|\___| \_/ \___|_| |_|\__,_|\__,_| .__/
|___/ |_|
#endif
/* This is the high level LDAP handling code. It reads queries from the socket at in, and
* then writes the answers to out. Normally in == out, but they are separate here so this
* can also be called with in=stdin and out=stdout. */
/* a standard LDAP session looks like this:
* 1. connect to server
* 2. send a BindRequest
* get back a BindResponse
* 3. send a SearchRequest
* get back n SearchResultEntries
* get back a SearchResultDone
* 4. send an UnbindRequest
* 5. close
* tinyldap does not complain if you don't unbind before hanging up.
*/
int handle(int in,int out) {
int len;
char buf[BUFSIZE];
@@ -626,7 +887,7 @@ int handle(int in,int out) {
int res;
long messageid,op,Len;
if (tmp==0)
if (BUFSIZE-len) { return 0; }
if (BUFSIZE-len) { close(in); if (in!=out) close(out); return 0; }
if (tmp<0) { write(2,"error!\n",7); return 1; }
len+=tmp;
res=scan_ldapmessage(buf,buf+len,&messageid,&op,&Len);
@@ -700,9 +961,10 @@ authfailure:
}
for (; i<record_count; ++i) {
if (isset(result,i)) {
uint32 j;
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;
@@ -717,6 +979,7 @@ authfailure:
#endif
if (check_password(c,&password)) {
done=1;
authenticated_as=authdn;
goto found;
}
}
@@ -937,6 +1200,14 @@ found:
}
}
#if 0
_
_ __ ___ __ _(_)_ __
| '_ ` _ \ / _` | | '_ \
| | | | | | (_| | | | | |
|_| |_| |_|\__,_|_|_| |_|
#endif
int main(int argc,char* argv[]) {
#ifdef STANDALONE
int sock;
@@ -970,8 +1241,6 @@ int main(int argc,char* argv[]) {
objectClass_ofs=j;
else if (case_equals("userPassword",map+j))
userPassword_ofs=j;
else if (case_equals("*",map+j))
any_ofs=j;
x+=4;
}
if (!dn_ofs || !objectClass_ofs) {
@@ -980,6 +1249,8 @@ int main(int argc,char* argv[]) {
}
}
load_acls();
#if 0
ldif_parse("exp.ldif");
if (!first) {
@@ -1037,3 +1308,6 @@ again:
#endif
return 0;
}
/* vim:tw=90:
*/