add journal rereading
add DelRequest support add little delete test tool ldapdelete
This commit is contained in:
12
Makefile
12
Makefile
@@ -3,7 +3,7 @@
|
||||
|
||||
all: t1 t2 parse dumpidx idx2ldif addindex bindrequest tinyldap \
|
||||
tinyldap_standalone tinyldap_debug ldapclient ldapclient_str \
|
||||
md5password mysql2ldif acl dumpacls # t6 # t
|
||||
md5password mysql2ldif acl dumpacls ldapdelete # t6 # t
|
||||
|
||||
asn1.a: fmt_asn1intpayload.o fmt_asn1length.o fmt_asn1tag.o \
|
||||
fmt_asn1int.o fmt_asn1string.o fmt_asn1transparent.o scan_asn1tag.o \
|
||||
@@ -23,20 +23,21 @@ matchprefix.o matchcasestring.o matchcaseprefix.o \
|
||||
scan_ldapmodifyrequest.o scan_ldapaddrequest.o bstrlen.o bstrfirst.o \
|
||||
bstrstart.o free_ldapadl.o free_ldappal.o free_ldapsearchfilter.o \
|
||||
scan_ldapsearchfilterstring.o free_ldapsearchresultentry.o \
|
||||
fmt_ldapsearchfilterstring.o ldap_match_sre.o
|
||||
fmt_ldapsearchfilterstring.o ldap_match_sre.o \
|
||||
fmt_ldapdeleterequest.o scan_ldapdeleterequest.o normalize_dn.o
|
||||
|
||||
ldif.a: ldif_parse.o ldap_match_mapped.o
|
||||
|
||||
storage.a: strstorage.o strduptab.o mstorage_add.o mduptab_add.o \
|
||||
bstr_diff.o mduptab_adds.o bstr_diff2.o mstorage_add_bin.o \
|
||||
mstorage_init.o mstorage_init_persistent.o mstorage_unmap.o \
|
||||
mduptab_init.o mduptab_init_reuse.o
|
||||
mduptab_init.o mduptab_init_reuse.o mduptab_reset.o
|
||||
|
||||
auth.a: auth.o
|
||||
|
||||
DIET=/opt/diet/bin/diet -Os
|
||||
CC=gcc
|
||||
CFLAGS=-pipe -I. -Wall -W
|
||||
CFLAGS=-pipe -I. -Wall -W -Wextra
|
||||
ifneq ($(DEBUG),)
|
||||
DIET=/opt/diet/bin/diet
|
||||
CFLAGS=-pipe -I. -Wall -W -g -fstack-protector
|
||||
@@ -66,9 +67,10 @@ t2: ldap.a asn1.a
|
||||
t3 t4 t5 addindex: storage.a
|
||||
t6: storage.a
|
||||
tinyldap tinyldap_standalone tinyldap_debug: ldif.a storage.a auth.a
|
||||
bindrequest tinyldap tinyldap_standalone tinyldap_debug ldapclient ldapclient_str: ldap.a asn1.a
|
||||
bindrequest tinyldap tinyldap_standalone tinyldap_debug ldapclient ldapclient_str ldapdelete: ldap.a asn1.a
|
||||
idx2ldif: ldap.a
|
||||
dumpacls: ldap.a asn1.a
|
||||
parse: normalize_dn.o
|
||||
|
||||
tinyldap_standalone: tinyldap.c
|
||||
$(DIET) $(CC) $(CFLAGS) -DSTANDALONE -o $@ $^ $(LDFLAGS) -lowfat $(LIBS)
|
||||
|
||||
@@ -66,15 +66,6 @@ kaputt:
|
||||
if (!buf) goto kaputt;
|
||||
buf[l2=fmt_ldapsearchfilterstring(buf,f)]=0;
|
||||
buffer_puts(buffer_1,buf);
|
||||
if (l!=l2) {
|
||||
buffer_puts(buffer_1,"\n\n\n");
|
||||
buffer_putulong(buffer_1,l);
|
||||
buffer_puts(buffer_1," != ");
|
||||
buffer_putulong(buffer_1,l2);
|
||||
buffer_puts(buffer_1,"\n");
|
||||
buffer_flush(buffer_1);
|
||||
assert(l==l2);
|
||||
}
|
||||
free_ldapsearchfilter(f);
|
||||
free(f);
|
||||
}
|
||||
|
||||
8
fmt_ldapdeleterequest.c
Normal file
8
fmt_ldapdeleterequest.c
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <string.h>
|
||||
#include "ldap.h"
|
||||
#include "byte.h"
|
||||
|
||||
size_t fmt_ldapdeleterequest(char* dest,struct string* s) {
|
||||
if (dest) byte_copy(dest,s->l,s->s);
|
||||
return s->l;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ static void dumpbstr(const char* c) {
|
||||
l=bstrlen(c);
|
||||
d=bstrfirst(c);
|
||||
up=fmt_ldapescape(0,d,l);
|
||||
assert(up>=l);
|
||||
// assert(up>=l);
|
||||
if (up==l) {
|
||||
buffer_puts(buffer_1," ");
|
||||
if (*c)
|
||||
|
||||
14
ldap.h
14
ldap.h
@@ -14,6 +14,10 @@ int matchcasestring(struct string* s,const char* c);
|
||||
int matchprefix(struct string* s,const char* c);
|
||||
int matchcaseprefix(struct string* s,const char* c);
|
||||
|
||||
/* "ou=fnord; O=fefe; c=de" -> "ou=fnord,o=fefe,c=de" */
|
||||
/* returns the length of the new string */
|
||||
size_t normalize_dn(char* dest,const char* src,int len);
|
||||
|
||||
struct AttributeValueAssertion {
|
||||
struct string desc, value;
|
||||
};
|
||||
@@ -94,6 +98,12 @@ struct AddRequest {
|
||||
struct Addition a;
|
||||
};
|
||||
|
||||
struct ModifyDNRequest {
|
||||
struct string entry, newrdn;
|
||||
int deleteoldrdn;
|
||||
struct string newsuperior;
|
||||
};
|
||||
|
||||
enum ldapops {
|
||||
BindRequest=0, BindResponse=1,
|
||||
UnbindRequest=2,
|
||||
@@ -173,6 +183,8 @@ size_t scan_ldapresult(const char* src,const char* max,unsigned long* result,
|
||||
size_t scan_ldapmodifyrequest(const char* src,const char* max,struct ModifyRequest* m);
|
||||
size_t scan_ldapaddrequest(const char* src, const char * max, struct AddRequest * a);
|
||||
size_t scan_ldapsearchfilterstring(const char* src,struct Filter** f);
|
||||
size_t scan_ldapdeleterequest(const char* src,const char* max,struct string* s);
|
||||
size_t scan_ldapmodifydnrequest(const char* src,const char* max,struct ModifyDNRequest* mdr);
|
||||
|
||||
size_t fmt_ldapstring(char* dest,struct string* s);
|
||||
size_t fmt_ldapmessage(char* dest,long messageid,long op,size_t len);
|
||||
@@ -187,6 +199,8 @@ size_t fmt_ldapadl(char* dest,struct AttributeDescriptionList* adl);
|
||||
size_t fmt_ldapavl(char* dest,struct AttributeDescriptionList* adl);
|
||||
size_t fmt_ldapmodifyrequest(char* dest,struct ModifyRequest* m);
|
||||
size_t fmt_ldapsearchfilterstring(char* dest,struct Filter* f);
|
||||
size_t fmt_ldapdeleterequest(char* dest,struct string* s);
|
||||
size_t fmt_ldapmodifydnrequest(char* dest,struct ModifyDNRequest* mdr);
|
||||
|
||||
#define fmt_ldapbindresponse(a,b,c,d,e) fmt_ldapresult(a,b,c,d,e)
|
||||
#define fmt_ldapsearchresultdone(a,b,c,d,e) fmt_ldapresult(a,b,c,d,e)
|
||||
|
||||
15
ldapclient.c
15
ldapclient.c
@@ -195,6 +195,21 @@ nextmessage:
|
||||
goto copypartialandcontinue;
|
||||
}
|
||||
} else if (op==SearchResultDone) {
|
||||
unsigned long result;
|
||||
struct string matcheddn,errormessage,referral;
|
||||
if (scan_ldapresult(buf+cur+tmp2,max,&result,&matcheddn,&errormessage,&referral)>0) {
|
||||
if (result!=0) {
|
||||
buffer_puts(buffer_2,"fail, code ");
|
||||
buffer_putulong(buffer_2,result);
|
||||
if (errormessage.l) {
|
||||
buffer_puts(buffer_2,", error message \"");
|
||||
buffer_put(buffer_2,errormessage.s,errormessage.l);
|
||||
buffer_puts(buffer_2,"\n");
|
||||
}
|
||||
buffer_putsflush(buffer_2,".\n");
|
||||
}
|
||||
} else
|
||||
buffer_putsflush(buffer_2,"scan_ldapresult failed!\n");
|
||||
if (!matches)
|
||||
buffer_putsflush(buffer_2,"no matches.\n");
|
||||
if (bench && durchlauf!=0)
|
||||
|
||||
127
ldapdelete.c
Normal file
127
ldapdelete.c
Normal file
@@ -0,0 +1,127 @@
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "byte.h"
|
||||
#include "buffer.h"
|
||||
#include "asn1.h"
|
||||
#include "ldap.h"
|
||||
#include "socket.h"
|
||||
#include "ip4.h"
|
||||
#include "str.h"
|
||||
#include "textcode.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define BUFSIZE 8192
|
||||
|
||||
static unsigned long messageid=1;
|
||||
|
||||
static int ldapbind(int sock) {
|
||||
char outbuf[1024];
|
||||
int s=100;
|
||||
size_t len=fmt_ldapbindrequest(outbuf+s,3,"","");
|
||||
size_t hlen=fmt_ldapmessage(0,messageid,BindRequest,len);
|
||||
size_t res,Len;
|
||||
unsigned long op,result;
|
||||
struct string matcheddn,errormessage,referral;
|
||||
fmt_ldapmessage(outbuf+s-hlen,messageid,BindRequest,len);
|
||||
if ((size_t)write(sock,outbuf+s-hlen,len+hlen)!=len+hlen) return 0;;
|
||||
len=read(sock,outbuf,1024);
|
||||
res=scan_ldapmessage(outbuf,outbuf+len,&messageid,&op,&Len);
|
||||
if (!res) return 0;
|
||||
if (op!=BindResponse) return 0;
|
||||
res=scan_ldapbindresponse(outbuf+res,outbuf+res+Len,&result,&matcheddn,&errormessage,&referral);
|
||||
if (!res) return 0;
|
||||
if (result) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc,char* argv[]) {
|
||||
int sock;
|
||||
char buf[BUFSIZE];
|
||||
int len=0;
|
||||
char* me;
|
||||
if ((me=strrchr(argv[0],'/')))
|
||||
++me;
|
||||
else
|
||||
me=argv[0];
|
||||
|
||||
if (argc<2) {
|
||||
usage:
|
||||
buffer_putsflush(buffer_2,"usage: ldapdelete ip dn\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
sock=socket_tcp4b();
|
||||
{
|
||||
char ip[4];
|
||||
if (argv[1][scan_ip4(argv[1],ip)]) goto usage;
|
||||
if (socket_connect4(sock,ip,389)) {
|
||||
buffer_putsflush(buffer_2,"could not connect to ldap server!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (ldapbind(sock)) {
|
||||
struct string s;
|
||||
|
||||
s.l=strlen(argv[2]);
|
||||
s.s=argv[2];
|
||||
|
||||
len=fmt_ldapdeleterequest(buf+100,&s);
|
||||
{
|
||||
int tmp=fmt_ldapmessage(0,++messageid,DelRequest,len);
|
||||
fmt_ldapmessage(buf+100-tmp,messageid,DelRequest,len);
|
||||
write(sock,buf+100-tmp,len+tmp);
|
||||
}
|
||||
shutdown(sock,SHUT_WR);
|
||||
{
|
||||
char buf[32*1024]; /* arbitrary limit, bad! */
|
||||
int len=0,tmp,tmp2;
|
||||
char* max;
|
||||
|
||||
unsigned long mid,op;
|
||||
size_t slen;
|
||||
int cur=0;
|
||||
|
||||
tmp=read(sock,buf+len,sizeof(buf)-len);
|
||||
|
||||
if (tmp<=0) {
|
||||
buffer_putsflush(buffer_2,"read error.\n");
|
||||
return 2;
|
||||
}
|
||||
len+=tmp;
|
||||
if ((tmp2=scan_ldapmessage(buf+cur,buf+len,&mid,&op,&slen))) {
|
||||
max=buf+cur+slen+tmp2;
|
||||
if (op==DelResponse) {
|
||||
unsigned long result;
|
||||
struct string matcheddn, errormessage, referral;
|
||||
if (scan_ldapresult(buf+cur+tmp2,max,&result,&matcheddn,&errormessage,&referral)>0) {
|
||||
if (result==success) {
|
||||
buffer_putsflush(buffer_2,"ok\n");
|
||||
} else {
|
||||
buffer_puts(buffer_2,"fail, code ");
|
||||
buffer_putulong(buffer_2,result);
|
||||
if (errormessage.l) {
|
||||
buffer_puts(buffer_2,", error message \"");
|
||||
buffer_put(buffer_2,errormessage.s,errormessage.l);
|
||||
buffer_puts(buffer_2,"\n");
|
||||
}
|
||||
buffer_putsflush(buffer_2,".\n");
|
||||
}
|
||||
} else
|
||||
buffer_putsflush(buffer_2,"failed to parse result message.\n");
|
||||
} else
|
||||
buffer_putsflush(buffer_2,"unexpected response.\n");
|
||||
} else
|
||||
buffer_putsflush(buffer_2,"failed to parse ldap message.\n");
|
||||
}
|
||||
} else {
|
||||
buffer_putsflush(buffer_2,"ldapbind failed\n");
|
||||
return 2;
|
||||
}
|
||||
close(sock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
4
ldif.h
4
ldif.h
@@ -1,3 +1,5 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#include <sys/stat.h>
|
||||
#include <inttypes.h>
|
||||
#include <ldap.h>
|
||||
|
||||
@@ -19,7 +21,7 @@ extern uint32_t dn, mail, sn, cn, objectClass;
|
||||
extern struct ldaprec *first;
|
||||
extern unsigned long ldifrecords;
|
||||
|
||||
int ldif_parse(const char* filename);
|
||||
int ldif_parse(const char* filename,off_t fromofs,struct stat* ss);
|
||||
|
||||
/* return non-zero if the record matches the search request */
|
||||
int ldap_match(struct ldaprec* r,struct SearchRequest* sr);
|
||||
|
||||
44
ldif_parse.c
44
ldif_parse.c
@@ -1,3 +1,4 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#include <alloca.h>
|
||||
#include <buffer.h>
|
||||
#include <scan.h>
|
||||
@@ -48,34 +49,10 @@ static void addattribute(struct ldaprec** l,uint32_t name,uint32_t val) {
|
||||
}
|
||||
}
|
||||
|
||||
/* "ou=fnord; O=fefe; c=de" -> "ou=fnord,o=fefe,c=de" */
|
||||
/* returns the length of the new string */
|
||||
static int normalize_dn(char* dest,const char* src,int len) {
|
||||
int makelower=1;
|
||||
char* orig=dest;
|
||||
while (len) {
|
||||
if (*src==';' || *src==',') {
|
||||
*dest=',';
|
||||
while (len>1 && src[1]==' ') { ++src; --len; }
|
||||
makelower=1;
|
||||
} else {
|
||||
if (makelower)
|
||||
*dest=tolower(*src);
|
||||
else
|
||||
*dest=*src;
|
||||
if (*dest=='=') makelower=0;
|
||||
}
|
||||
++dest;
|
||||
++src;
|
||||
--len;
|
||||
}
|
||||
return dest-orig;
|
||||
}
|
||||
|
||||
static int unbase64(char* buf) {
|
||||
unsigned long destlen;
|
||||
static size_t unbase64(char* buf) {
|
||||
size_t destlen;
|
||||
char temp[8192];
|
||||
long l=scan_base64(buf,temp,&destlen);
|
||||
size_t l=scan_base64(buf,temp,&destlen);
|
||||
if (buf[l] && buf[l]!='\n') return 0;
|
||||
byte_copy(buf,destlen,temp);
|
||||
return destlen;
|
||||
@@ -302,6 +279,7 @@ lookagain:
|
||||
addattribute(l,tmp,val);
|
||||
#endif
|
||||
} while (!eof);
|
||||
if ((*l)->dn==(uint32_t)-1) return 0;
|
||||
if (ldif_parse_callback && ldif_parse_callback(*l)==-1) return -1;
|
||||
if ((*l)->dn==(uint32_t)-1 && ((*l)->next)) {
|
||||
struct ldaprec* m=(*l)->next;
|
||||
@@ -313,7 +291,7 @@ lookagain:
|
||||
|
||||
struct ldaprec *first=0;
|
||||
|
||||
int ldif_parse(const char* filename) {
|
||||
int ldif_parse(const char* filename,off_t fromofs,struct stat* ss) {
|
||||
char buf[4096];
|
||||
int fd;
|
||||
buffer in;
|
||||
@@ -325,7 +303,8 @@ int ldif_parse(const char* filename) {
|
||||
fd=-1;
|
||||
} else {
|
||||
fd=open_read(filename);
|
||||
if (fd<0) return 1;
|
||||
if (fd<0) return 0; // no journal file is permissible
|
||||
if (fromofs) lseek(fd,fromofs,SEEK_SET);
|
||||
buffer_init(&in,(void*)read,fd,buf,sizeof buf);
|
||||
tmp=∈
|
||||
}
|
||||
@@ -334,6 +313,13 @@ int ldif_parse(const char* filename) {
|
||||
lines=0;
|
||||
{
|
||||
int res=parserec(tmp,&first);
|
||||
if (ss) {
|
||||
fstat(fd,ss);
|
||||
/* the file size may have changed between parserec hitting EOF and
|
||||
* us calling lstat, we we write the current file pointer position
|
||||
* to st_size */
|
||||
ss->st_size=lseek(fd,0,SEEK_CUR);
|
||||
}
|
||||
if (fd!=-1) close(fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -16,3 +16,4 @@ void mduptab_init(mduptab_t* t);
|
||||
void mduptab_init_reuse(mduptab_t* t,mstorage_t* s);
|
||||
long mduptab_add(mduptab_t* t,const char* s,size_t len);
|
||||
long mduptab_adds(mduptab_t* t,const char* s);
|
||||
void mduptab_reset(mduptab_t* t);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/shm.h>
|
||||
#include <stdio.h>
|
||||
#include "byte.h"
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
#include "mstorage.h"
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
void mstorage_unmap(mstorage_t* p) {
|
||||
#ifdef MREMAP_MAYMOVE
|
||||
munmap(p->root,p->mapped);
|
||||
#else
|
||||
free(p->root);
|
||||
#endif
|
||||
if (p->fd!=-1) {
|
||||
ftruncate(p->fd,p->used);
|
||||
close(p->fd);
|
||||
}
|
||||
p->mapped=p->used=0;
|
||||
p->root=0;
|
||||
}
|
||||
|
||||
2
parse.c
2
parse.c
@@ -193,7 +193,7 @@ writeerror:
|
||||
// if ((mduptab_adds(&attributes,"*"))<0)
|
||||
// die(1,"out of memory");
|
||||
|
||||
ldif_parse(argc<2?"exp.ldif":argv[1]);
|
||||
ldif_parse(argc<2?"exp.ldif":argv[1],0,0);
|
||||
if (!first)
|
||||
die(1,"usage: parse [src-ldif-filename] [dest-bin-filename]\n");
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ size_t scan_asn1ENUMERATED(const char* src,const char* max,unsigned long* val) {
|
||||
long ltmp;
|
||||
if ((tmp=scan_asn1int(src,max,&tc,&tt,&tag,<mp)))
|
||||
if (tc==UNIVERSAL && tt==PRIMITIVE && tag==ENUMERATED) {
|
||||
if (ltmp<0 || src+tmp+ltmp>max) return 0;
|
||||
*val=(unsigned long)ltmp;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
10
scan_ldapdeleterequest.c
Normal file
10
scan_ldapdeleterequest.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "asn1.h"
|
||||
#include "ldap.h"
|
||||
|
||||
size_t scan_ldapdeleterequest(const char* src,const char* max,
|
||||
struct string* s) {
|
||||
if (src>=max) return 0;
|
||||
s->l=max-src;
|
||||
s->s=src;
|
||||
return s->l;
|
||||
}
|
||||
324
tinyldap.c
324
tinyldap.c
@@ -1,3 +1,4 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -196,6 +197,8 @@ struct acl {
|
||||
struct acl** Acls;
|
||||
|
||||
static void load_acls() {
|
||||
struct acl** oldAcls=Acls;
|
||||
size_t oldacls=acls;
|
||||
uint32 ofs;
|
||||
uint32 acl_ofs;
|
||||
acl_ofs=0;
|
||||
@@ -281,11 +284,56 @@ kaputt:
|
||||
for (i=0; i<filters; ++i)
|
||||
acl_ec_subjects[i]=(Filters[i]==(struct Filter*)Any);
|
||||
}
|
||||
if (oldAcls) {
|
||||
size_t i;
|
||||
for (i=0; i<oldacls; ++i)
|
||||
free(oldAcls[i]);
|
||||
free(oldAcls);
|
||||
}
|
||||
}
|
||||
|
||||
/* End of ACL code */
|
||||
|
||||
static const char* datafilename;
|
||||
static struct stat ss_data;
|
||||
static struct stat ss_journal;
|
||||
|
||||
void map_datafile(const char* filename) {
|
||||
map=mmap_read(datafilename=filename,&filelen);
|
||||
stat(datafilename,&ss_data);
|
||||
if (!map) {
|
||||
buffer_putsflush(buffer_2,"could not open data!\n");
|
||||
exit(1);
|
||||
}
|
||||
uint32_unpack(map,&magic);
|
||||
uint32_unpack(map+4,&attribute_count);
|
||||
uint32_unpack(map+2*4,&record_count);
|
||||
uint32_unpack(map+3*4,&indices_offset);
|
||||
uint32_unpack(map+4*4,&size_of_string_table);
|
||||
record_set_length=(record_count+sizeof(unsigned long)*8-1) / (sizeof(long)*8);
|
||||
|
||||
/* look up "dn" and "objectClass" */
|
||||
{
|
||||
char* x=map+5*4+size_of_string_table;
|
||||
size_t i;
|
||||
dn_ofs=objectClass_ofs=userPassword_ofs=any_ofs=0;
|
||||
for (i=0; i<attribute_count; ++i) {
|
||||
uint32 j;
|
||||
j=uint32_read(x);
|
||||
if (case_equals("dn",map+j))
|
||||
dn_ofs=j;
|
||||
else if (case_equals("objectClass",map+j))
|
||||
objectClass_ofs=j;
|
||||
else if (case_equals("userPassword",map+j))
|
||||
userPassword_ofs=j;
|
||||
x+=4;
|
||||
}
|
||||
if (!dn_ofs || !objectClass_ofs) {
|
||||
buffer_putsflush(buffer_2,"can't happen error: dn or objectClass not there?!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
_ _ _
|
||||
@@ -652,7 +700,7 @@ static void tagmatches(uint32* index,size_t elements,struct string* s,
|
||||
}
|
||||
}
|
||||
|
||||
uint32 hash(const unsigned char* c,size_t keylen) {
|
||||
static uint32 hash(const unsigned char* c,size_t keylen) {
|
||||
size_t h=0,i;
|
||||
for (i=0; i<keylen; ++i) {
|
||||
/* from djb's cdb */
|
||||
@@ -662,7 +710,7 @@ uint32 hash(const unsigned char* c,size_t keylen) {
|
||||
return (uint32)h;
|
||||
}
|
||||
|
||||
uint32 hash_tolower(const unsigned char* c,size_t keylen) {
|
||||
static uint32 hash_tolower(const unsigned char* c,size_t keylen) {
|
||||
size_t h=0,i;
|
||||
for (i=0; i<keylen; ++i) {
|
||||
/* from djb's cdb */
|
||||
@@ -869,7 +917,6 @@ static int checkacl(uint32 recofs,uint32 attrofs,unsigned long operation,struct
|
||||
}
|
||||
}
|
||||
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]->Attrs[k]==any_ofs || attrofs==Acls[j]->Attrs[k]) {
|
||||
if (Acls[j]->may&operation)
|
||||
return 1;
|
||||
@@ -1087,7 +1134,7 @@ add_attribute:
|
||||
|___/ |_|
|
||||
*/
|
||||
|
||||
int copystring(struct string* dest,struct string* src) {
|
||||
static int copystring(struct string* dest,struct string* src) {
|
||||
dest->s=malloc(src->l+1);
|
||||
if (!dest->s) return -1;
|
||||
byte_copy((char*)dest->s,src->l,src->s);
|
||||
@@ -1340,7 +1387,7 @@ static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) {
|
||||
useindex(&f,&result);
|
||||
if (result.first>result.last)
|
||||
return 0;
|
||||
assert(result.last<=record_count);
|
||||
// assert(result.last<=record_count);
|
||||
for (i=result.first; i<=result.last; ) {
|
||||
if (!result.bits[i/(8*sizeof(long))]) {
|
||||
i+=8*sizeof(long);
|
||||
@@ -1357,6 +1404,17 @@ static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void normalize_string_dn(struct string* s) {
|
||||
/* OK this is a kludge. s->s is supposed to be read-only because it points into the
|
||||
* buffer where we read it into from the network.
|
||||
* Since normalize_dn ends up using less or equal space, and we are not interested in
|
||||
* the non-normalized dn, we do the read-write cast and normalize in-place.
|
||||
* Kids, don't do this at home. */
|
||||
s->l=normalize_dn((char*)s->s,s->s,s->l);
|
||||
}
|
||||
|
||||
static void update();
|
||||
|
||||
/* a standard LDAP session looks like this:
|
||||
* 1. connect to server
|
||||
* 2. send a BindRequest
|
||||
@@ -1368,7 +1426,7 @@ static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) {
|
||||
* 5. close
|
||||
* tinyldap does not complain if you don't unbind before hanging up.
|
||||
*/
|
||||
int handle(int in,int out) {
|
||||
static int handle(int in,int out) {
|
||||
size_t len;
|
||||
char buf[BUFSIZE];
|
||||
for (len=0;;) {
|
||||
@@ -1395,14 +1453,15 @@ int handle(int in,int out) {
|
||||
buffer_putulong(buffer_2,op);
|
||||
buffer_putsflush(buffer_2,".\n");
|
||||
}
|
||||
update();
|
||||
switch (op) {
|
||||
case BindRequest:
|
||||
{
|
||||
unsigned long version,method;
|
||||
struct string name;
|
||||
int tmp;
|
||||
tmp=scan_ldapbindrequest(buf+res,buf+res+len,&version,&name,&method);
|
||||
if (tmp>=0) {
|
||||
size_t tmp;
|
||||
tmp=scan_ldapbindrequest(buf+res,buf+len,&version,&name,&method);
|
||||
if (tmp>0) {
|
||||
if (verbose) {
|
||||
buffer_puts(buffer_2,"bind request: version ");
|
||||
buffer_putulong(buffer_2,version);
|
||||
@@ -1418,8 +1477,9 @@ int handle(int in,int out) {
|
||||
struct hashnode* hn;
|
||||
int err=success;
|
||||
|
||||
scan_ldapstring(buf+res+tmp,buf+res+len,&password);
|
||||
scan_ldapstring(buf+res+tmp,buf+len,&password);
|
||||
|
||||
normalize_string_dn(&name);
|
||||
switch (lookupdn(&name,&idx,&hn)) {
|
||||
case -1: err=operationsError; break;
|
||||
case 1: break;
|
||||
@@ -1430,7 +1490,7 @@ int handle(int in,int out) {
|
||||
goto authfailure;
|
||||
else {
|
||||
char* c=0;
|
||||
uint32 authdn;
|
||||
uint32 authdn=0;
|
||||
char* authdn_str=0;
|
||||
if (idx==(size_t)-1) { // found in journal
|
||||
size_t i;
|
||||
@@ -1485,9 +1545,9 @@ authfailure:
|
||||
}
|
||||
{
|
||||
char outbuf[1024];
|
||||
int s=100;
|
||||
int len=fmt_ldapbindresponse(outbuf+s,0,"","go ahead","");
|
||||
int hlen=fmt_ldapmessage(0,messageid,BindResponse,len);
|
||||
size_t s=100;
|
||||
size_t len=fmt_ldapbindresponse(outbuf+s,0,"","go ahead","");
|
||||
size_t hlen=fmt_ldapmessage(0,messageid,BindResponse,len);
|
||||
fmt_ldapmessage(outbuf+s-hlen,messageid,BindResponse,len);
|
||||
write(out,outbuf+s-hlen,len+hlen);
|
||||
}
|
||||
@@ -1497,7 +1557,7 @@ authfailure:
|
||||
case SearchRequest:
|
||||
{
|
||||
struct SearchRequest sr;
|
||||
int tmp;
|
||||
size_t tmp;
|
||||
#if 0
|
||||
{
|
||||
int fd=open_write("request");
|
||||
@@ -1505,7 +1565,7 @@ authfailure:
|
||||
close(fd);
|
||||
}
|
||||
#endif
|
||||
if ((tmp=scan_ldapsearchrequest(buf+res,buf+res+len,&sr))) {
|
||||
if ((tmp=scan_ldapsearchrequest(buf+res,buf+len,&sr))) {
|
||||
size_t returned=0;
|
||||
|
||||
#if (debug != 0)
|
||||
@@ -1542,7 +1602,7 @@ authfailure:
|
||||
* of the matches in a table. Use findrec to locate
|
||||
* the records that point to the data. */
|
||||
useindex(sr.filter,&result);
|
||||
assert(result.last<=record_count);
|
||||
// assert(result.last<=record_count);
|
||||
for (i=result.first; i<=result.last; ) {
|
||||
size_t ni=i+8*sizeof(long);
|
||||
if (!result.bits[i/(8*sizeof(long))]) {
|
||||
@@ -1589,8 +1649,8 @@ authfailure:
|
||||
}
|
||||
{
|
||||
char buf[1000];
|
||||
long l=fmt_ldapsearchresultdone(buf+100,0,"","","");
|
||||
int hlen=fmt_ldapmessage(0,messageid,SearchResultDone,l);
|
||||
size_t l=fmt_ldapsearchresultdone(buf+100,0,"","","");
|
||||
size_t hlen=fmt_ldapmessage(0,messageid,SearchResultDone,l);
|
||||
fmt_ldapmessage(buf+100-hlen,messageid,SearchResultDone,l);
|
||||
write(out,buf+100-hlen,l+hlen);
|
||||
}
|
||||
@@ -1602,30 +1662,34 @@ authfailure:
|
||||
case ModifyRequest:
|
||||
{
|
||||
struct ModifyRequest mr;
|
||||
int tmp,err=success;
|
||||
size_t tmp,err=success;
|
||||
buffer_putsflush(buffer_2,"modifyrequest!\n");
|
||||
if ((tmp=scan_ldapmodifyrequest(buf+res,buf+res+len,&mr))) {
|
||||
if ((tmp=scan_ldapmodifyrequest(buf+res,buf+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");
|
||||
switch (mr.m.operation) {
|
||||
case 0: buffer_puts(buffer_1,"Add\n"); break;
|
||||
case 1: buffer_puts(buffer_1,"Delete\n"); break;
|
||||
case 2: buffer_puts(buffer_1,"Replace\n"); break;
|
||||
if (verbose) {
|
||||
buffer_puts(buffer_1,"modify request: dn \"");
|
||||
buffer_put(buffer_1,mr.object.s,mr.object.l);
|
||||
buffer_putsflush(buffer_1,"\"\n");
|
||||
switch (mr.m.operation) {
|
||||
case 0: buffer_puts(buffer_1,"Add\n"); break;
|
||||
case 1: buffer_puts(buffer_1,"Delete\n"); break;
|
||||
case 2: buffer_puts(buffer_1,"Replace\n"); break;
|
||||
}
|
||||
buffer_put(buffer_1,mr.m.AttributeDescription.s,mr.m.AttributeDescription.l);
|
||||
buffer_puts(buffer_1,"\n");
|
||||
{
|
||||
struct AttributeDescriptionList* x=mr.m.vals;
|
||||
do {
|
||||
buffer_puts(buffer_1," -> \"");
|
||||
buffer_put(buffer_1,x->a.s,x->a.l);
|
||||
buffer_putsflush(buffer_1,"\"\n");
|
||||
x=x->next;
|
||||
} while (x);
|
||||
}
|
||||
}
|
||||
buffer_put(buffer_1,mr.m.AttributeDescription.s,mr.m.AttributeDescription.l);
|
||||
buffer_puts(buffer_1,"\n");
|
||||
{
|
||||
struct AttributeDescriptionList* x=mr.m.vals;
|
||||
do {
|
||||
buffer_puts(buffer_1," -> \"");
|
||||
buffer_put(buffer_1,x->a.s,x->a.l);
|
||||
buffer_putsflush(buffer_1,"\"\n");
|
||||
x=x->next;
|
||||
} while (x);
|
||||
}
|
||||
/* TODO: do something with the modify request ;-) */
|
||||
|
||||
normalize_string_dn(&mr.object);
|
||||
|
||||
if (acls) {
|
||||
/* convert modifyrequest to searchresultentry */
|
||||
modreq2sre(&sre,&mr);
|
||||
@@ -1648,7 +1712,7 @@ authfailure:
|
||||
#if 1
|
||||
/* 3. apply modifications to record to get new record */
|
||||
if (!applymodreq(hn,&mr,&sre)) {
|
||||
/* 4. write record to "data.upd" */
|
||||
/* 4. write record to journal */
|
||||
int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600);
|
||||
if (fd==-1)
|
||||
err=operationsError;
|
||||
@@ -1665,8 +1729,10 @@ authfailure:
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
buffer_putsflush(buffer_2,"could not parse modifyRequest!\n");
|
||||
err=protocolError;
|
||||
}
|
||||
|
||||
{
|
||||
char outbuf[1024];
|
||||
@@ -1681,7 +1747,7 @@ authfailure:
|
||||
}
|
||||
break;
|
||||
case AbandonRequest:
|
||||
buffer_putsflush(buffer_2,"AbandonRequest!\n");
|
||||
if (verbose) buffer_putsflush(buffer_2,"AbandonRequest!\n");
|
||||
/* do nothing */
|
||||
break;
|
||||
case AddRequest:
|
||||
@@ -1689,11 +1755,12 @@ authfailure:
|
||||
int err=success;
|
||||
struct AddRequest ar;
|
||||
// buffer_putsflush(buffer_2,"AddRequest!\n");
|
||||
if ((tmp=scan_ldapaddrequest(buf+res,buf+res+len,&ar))) {
|
||||
if ((tmp=scan_ldapaddrequest(buf+res,buf+len,&ar))) {
|
||||
struct SearchResultEntry sre;
|
||||
normalize_string_dn(&ar.entry);
|
||||
/* convert addrequest to searchresultentry */
|
||||
addreq2sre(&sre,&ar);
|
||||
/* TODO: do something with the add request ;-) */
|
||||
|
||||
/* 1. check ACLs */
|
||||
if (!acls || checkacl(0,0,acl_add,&sre)==1) {
|
||||
/* 2. check if there already is a record with this dn */
|
||||
@@ -1706,7 +1773,7 @@ authfailure:
|
||||
default: err=operationsError;
|
||||
}
|
||||
if (err==success) {
|
||||
/* 3. write record to "data.upd" */
|
||||
/* 3. write record to journal */
|
||||
int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600);
|
||||
if (fd==-1)
|
||||
err=operationsError;
|
||||
@@ -1740,14 +1807,70 @@ authfailure:
|
||||
|
||||
{
|
||||
char outbuf[1024];
|
||||
int s=100;
|
||||
int len=fmt_ldapresult(outbuf+s,err,"","","");
|
||||
int hlen=fmt_ldapmessage(0,messageid,AddResponse,len);
|
||||
size_t s=100;
|
||||
size_t len=fmt_ldapresult(outbuf+s,err,"","","");
|
||||
size_t hlen=fmt_ldapmessage(0,messageid,AddResponse,len);
|
||||
fmt_ldapmessage(outbuf+s-hlen,messageid,AddResponse,len);
|
||||
write(out,outbuf+s-hlen,len+hlen);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DelRequest:
|
||||
{
|
||||
struct string s;
|
||||
size_t l=scan_ldapdeleterequest(buf+res,buf+len,&s);
|
||||
if (l>0) {
|
||||
struct SearchResultEntry sre;
|
||||
int err=success;
|
||||
if (verbose) {
|
||||
buffer_puts(buffer_2,"Delete Request for DN \"");
|
||||
buffer_put(buffer_2,s.s,s.l);
|
||||
buffer_putsflush(buffer_2,"\".\n");
|
||||
}
|
||||
normalize_string_dn(&s);
|
||||
/* convert modifyrequest to searchresultentry */
|
||||
sre.objectName=s;
|
||||
sre.attributes=0;
|
||||
if (acls) {
|
||||
/* 1. check ACLs */
|
||||
if (checkacl(0,0,acl_delete,&sre)!=1)
|
||||
err=insufficientAccessRights;
|
||||
}
|
||||
if (err==success) {
|
||||
/* 2. check if there already is a record with this dn */
|
||||
struct hashnode* hn;
|
||||
size_t idx;
|
||||
switch (lookupdn(&s,&idx,&hn)) {
|
||||
case -1: err=operationsError; break;
|
||||
case 1: break;
|
||||
case 0: err=noSuchObject; break;
|
||||
default: err=operationsError;
|
||||
}
|
||||
if (err==success) {
|
||||
/* 3. write record to journal */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
char outbuf[1024];
|
||||
size_t s=100;
|
||||
size_t len=fmt_ldapresult(outbuf+s,err,"","","");
|
||||
size_t hlen=fmt_ldapmessage(0,messageid,DelResponse,len);
|
||||
fmt_ldapmessage(outbuf+s-hlen,messageid,DelResponse,len);
|
||||
write(out,outbuf+s-hlen,len+hlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ModifyDNRequest:
|
||||
/* TODO */
|
||||
default:
|
||||
buffer_puts(buffer_2,"unknown request type ");
|
||||
buffer_putulong(buffer_2,op);
|
||||
@@ -1781,7 +1904,7 @@ extern int (*ldif_parse_callback)(struct ldaprec* l);
|
||||
extern mstorage_t stringtable;
|
||||
extern mduptab_t attributes,classes;
|
||||
|
||||
unsigned long hash2(const unsigned char* c) {
|
||||
static unsigned long hash2(const unsigned char* c) {
|
||||
unsigned long h=0;
|
||||
if (*c==0) {
|
||||
uint32 len=uint32_read((char*)c+1);
|
||||
@@ -1798,7 +1921,7 @@ unsigned long hash2(const unsigned char* c) {
|
||||
|
||||
#define HASHTABSIZE 8191
|
||||
|
||||
unsigned char* bstrdup(unsigned char* c) {
|
||||
static unsigned char* bstrdup(unsigned char* c) {
|
||||
size_t len;
|
||||
unsigned char* x;
|
||||
if (*c)
|
||||
@@ -1812,7 +1935,7 @@ unsigned char* bstrdup(unsigned char* c) {
|
||||
return x;
|
||||
}
|
||||
|
||||
unsigned char* bstrdup_attrib(unsigned char* c) {
|
||||
static unsigned char* bstrdup_attrib(unsigned char* c) {
|
||||
char* x=map+5*4+size_of_string_table;
|
||||
size_t i,l;
|
||||
if (*c)
|
||||
@@ -1865,12 +1988,13 @@ static struct hashnode** dn_in_journal2(const char* dn,size_t dnlen) {
|
||||
|
||||
struct hashnode* root;
|
||||
|
||||
int parse_callback(struct ldaprec* l) {
|
||||
static int parse_callback(struct ldaprec* l) {
|
||||
static struct hashnode** nextinlinearlist=&root;
|
||||
size_t i;
|
||||
unsigned long hashval;
|
||||
struct hashnode** hn;
|
||||
if (l->dn==(uint32)-1) return -1;
|
||||
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);
|
||||
@@ -1912,11 +2036,64 @@ int parse_callback(struct ldaprec* l) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readjournal() {
|
||||
static void readjournal() {
|
||||
ldif_parse_callback=parse_callback;
|
||||
mduptab_init(&attributes);
|
||||
mduptab_init(&classes);
|
||||
return ldif_parse("journal");
|
||||
if (ldif_parse("journal",0,&ss_journal)) {
|
||||
buffer_putsflush(buffer_2,"Failed to parse journal!\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void update() {
|
||||
struct stat new_data,new_journal;
|
||||
if (stat(datafilename,&new_data)==-1) {
|
||||
/* no data file?! There is no way to salvage the situation. */
|
||||
buffer_putsflush(buffer_2,"ABEND: data file suddenly gone.\n");
|
||||
exit(1);
|
||||
}
|
||||
/* now see if the data file changed. If it did, map it anew. */
|
||||
if (new_data.st_size!=ss_data.st_size ||
|
||||
new_data.st_mtime!=ss_data.st_mtime ||
|
||||
new_data.st_ino!=ss_data.st_ino) {
|
||||
buffer_putsflush(buffer_2,"Data file changed, reloading.\n");
|
||||
mmap_unmap(map,filelen);
|
||||
/* If the new data file is corrupt, map_datafile calls exit.
|
||||
* I don't believe in limping on. If something is broken on such a fundamental level,
|
||||
* it's better to bail so that the problem does not go unnoticed and things get even
|
||||
* worse. */
|
||||
map_datafile(datafilename);
|
||||
/* OK, now that we have the datafile reloaded, we need to clean our idea of a journal
|
||||
* and reload the journal from scratch. */
|
||||
mduptab_reset(&attributes);
|
||||
mduptab_reset(&classes);
|
||||
readjournal();
|
||||
ss_data=new_data;
|
||||
return;
|
||||
}
|
||||
/* the data file did not change. Maybe the journal did. */
|
||||
if (stat("journal",&new_journal)==-1) {
|
||||
/* no journal; that means:
|
||||
* a) there never was one, totaly read-only data
|
||||
* b) there was one, but it has now been incorporated into the main database
|
||||
* in this case: delete journal data
|
||||
*/
|
||||
mduptab_reset(&attributes);
|
||||
mduptab_reset(&classes);
|
||||
return;
|
||||
}
|
||||
if (new_journal.st_size!=ss_journal.st_size ||
|
||||
new_journal.st_mtime!=ss_journal.st_mtime ||
|
||||
new_journal.st_ino!=ss_journal.st_ino) {
|
||||
/* Journal changed. Since all we ever do is append, we just read the part from how
|
||||
* far we got last time, which happens to be ss_journal.st_size. */
|
||||
if (ldif_parse("journal",ss_journal.st_size,&ss_journal)) {
|
||||
buffer_putsflush(buffer_2,"Failed to parse journal!\n");
|
||||
exit(1);
|
||||
}
|
||||
ss_data=new_data;
|
||||
}
|
||||
}
|
||||
|
||||
static int ldap_matchfilter_hn(struct hashnode* hn,struct Filter* f) {
|
||||
@@ -2010,6 +2187,7 @@ static void answerwith_hn(struct hashnode* hn,struct SearchRequest* sr,long mess
|
||||
struct SearchResultEntry sre;
|
||||
struct PartialAttributeList** pal=&sre.attributes;
|
||||
|
||||
if (!hn->n) return;
|
||||
if (acls)
|
||||
byte_zero(acl_ec_subjects+filters,filters);
|
||||
|
||||
@@ -2134,39 +2312,7 @@ int main(int argc,char* argv[]) {
|
||||
|
||||
signal(SIGPIPE,SIG_IGN);
|
||||
|
||||
map=mmap_read(argc>1?argv[1]:"data",&filelen);
|
||||
if (!map) {
|
||||
buffer_putsflush(buffer_2,"could not open data!\n");
|
||||
return 1;
|
||||
}
|
||||
uint32_unpack(map,&magic);
|
||||
uint32_unpack(map+4,&attribute_count);
|
||||
uint32_unpack(map+2*4,&record_count);
|
||||
uint32_unpack(map+3*4,&indices_offset);
|
||||
uint32_unpack(map+4*4,&size_of_string_table);
|
||||
record_set_length=(record_count+sizeof(unsigned long)*8-1) / (sizeof(long)*8);
|
||||
|
||||
/* look up "dn" and "objectClass" */
|
||||
{
|
||||
char* x=map+5*4+size_of_string_table;
|
||||
size_t i;
|
||||
dn_ofs=objectClass_ofs=userPassword_ofs=any_ofs=0;
|
||||
for (i=0; i<attribute_count; ++i) {
|
||||
uint32 j;
|
||||
j=uint32_read(x);
|
||||
if (case_equals("dn",map+j))
|
||||
dn_ofs=j;
|
||||
else if (case_equals("objectClass",map+j))
|
||||
objectClass_ofs=j;
|
||||
else if (case_equals("userPassword",map+j))
|
||||
userPassword_ofs=j;
|
||||
x+=4;
|
||||
}
|
||||
if (!dn_ofs || !objectClass_ofs) {
|
||||
buffer_putsflush(buffer_2,"can't happen error: dn or objectClass not there?!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
map_datafile(argc>1?argv[1]:"data");
|
||||
|
||||
load_acls();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user