Browse code

- support for multi-value filtering when generating records - client side filtering via set_dbopt ("client_side_filtering"), to enable resonable filtering of multivalue fields and fields without ordering rules - all comparision operations supported by filter (requires appropriate rules at LDAP server), otherwise client side filtering may be applied - generalized time timezone and localtime, ZULU support - generalized time fraction seconds support - support for alias dereferencing in openldap, option in table definition - support for referral dereferencing in openldap, option in table definition - doc added

Tomas Mandys authored on 29/10/2008 13:43:27
Showing 15 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,222 @@
1
+
2
+1. ldap module
3
+
4
+Jan Janak
5
+
6
+   Iptel.org
7
+
8
+   Copyright © 2008 Iptel.org GmBH
9
+   Revision History
10
+   Revision $Revision$ $Date$
11
+     _________________________________________________________________
12
+
13
+   1.1. Overview
14
+   1.2. Dependencies
15
+   1.3. Parameters
16
+
17
+        1.3.1. config (string)
18
+        1.3.2. reconnect_attempt (integer)
19
+
20
+   1.4. Functions
21
+
22
+1.1. Overview
23
+
24
+   The LDAP module is database driver, i.e. it implements DB API functions. The
25
+   goal is map database query defined by table, matching fields and result
26
+   fields to LDAP search in sub-tree defined by root, object class, attributes
27
+   and pass it to the OpenLDAP which communicates with the LDAP server.
28
+
29
+   This procedure is sometimes tricky because the LDAP does not support all
30
+   database features or supports them in different manner. Here we must express
31
+   especially filtering and multi-values. The multi-value is de facto array of
32
+   single values. If the LDAP module get a multi-value field then generates
33
+   record for every single value, respectively for every combination in case
34
+   the more fields contain multi-value.
35
+
36
+   The LDAP supports natively "AND", "OR", "NOT" logical operators and "equal",
37
+   "non-equal", "less-or-equal" and "greater-or-equal" comparison operators.
38
+   Therefore    "less"    and   "greater"   operators   are   mapped   as
39
+   "less/greater-or-equal-AND-not-equal". It's important realize it when the
40
+   attribute which will be used for filtering may contain multi-value. The LDAP
41
+   server evaluates comparison operator on multi-value so that the result for
42
+   record is true if the condition is satisfied for any single value. The
43
+   single values not satisfying condition are not truncated. It implies two
44
+   cases for positive comparison, e.g. "equal", the result contains values not
45
+   satisfying the condition, the case may be handled by additional filter in
46
+   the LDAP module, the negative comparison, e.g. "non-equal", does not return
47
+   record at all. Because the LDAP module cannot know if the LDAP attribute may
48
+   logically  contain  multi-value  so  there is introduced DB API option
49
+   client_side_filtering which forces filtering such fields in the LDAP module,
50
+   i.e.  the  LDAP server returns larger result set because the filtering
51
+   condition is not passed there.
52
+
53
+   The necessary condition of successful filtering of particular attribute at
54
+   the LDAP server is correct attribute definition. The "equal"/"non-equal"
55
+   operator requires equality matching rule, the "greater"/"less" operator
56
+   requires ordering matching rule. If required matching rule is missing the
57
+   LDAP server silently returns empty result set. In case of double filtering
58
+   both at the LDAP servar and the LDAP module, e.g. multi-value and equal
59
+   comparison, check the LDAP server matching rule satisfies your needs or use
60
+   client_side_filtering feature.
61
+
62
+   The LDAP server may be identified either complete specification of host,
63
+   user, password in URI or is specification reference to connection section of
64
+   config file. Note in the second case there is only one slash.
65
+
66
+   Example 1. URI example
67
+        ...
68
+        modparam("auth", "db_url", "ldap://admin:heslo@127.0.0.1");
69
+
70
+        modparam("auth", "db_url", "ldap:/ldap_server1");
71
+
72
+        ...
73
+
74
+   Features:
75
+     * simple, SASL authentication, TLS
76
+     * server and client side filtering
77
+     * read-only queries
78
+     * optional referral chasing by OpenLDAP
79
+     * optional reference chasing by OpenLDAP
80
+
81
+1.2. Dependencies
82
+
83
+   none
84
+
85
+1.3. Parameters
86
+
87
+1.3.1. config (string)
88
+
89
+   Default value is ldap.cfg.
90
+
91
+   The filename (relatively to ser config file) of mapping database to LDAP
92
+   definition. It is the main configuration file for the LDAP module in SER.
93
+   The  configuration  file maps database table names used in SER to LDAP
94
+   directory sub-trees to be searched. In addition to that the configuration
95
+   file also allows to configure the LDAP search filter and maps database field
96
+   names to LDAP attribute names and vice versa.
97
+
98
+   Example 2. Example config
99
+        ...
100
+        modparam("ldap", "config", "my-ldap.cfg");
101
+        ...
102
+
103
+   Example 3. Configuration file example
104
+# Supported Attribute Type Names:
105
+#  * GeneralizedTime
106
+#  * Integer
107
+#  * BitString
108
+#  * Boolean
109
+#  * String
110
+#  * Binary
111
+#  * Float
112
+#
113
+
114
+[connection:ldap_server1]
115
+host=127.0.0.1
116
+port=389
117
+username=ser
118
+password=heslo
119
+# LDAP or LDAP SASL authentication mechanism.
120
+# Allowed values: none (default), simple, digest-md5, external
121
+authtype=simple
122
+
123
+# tls encryption
124
+tls=off
125
+
126
+# Specifies the file that contains certificates for all of the Certificate
127
+# Authorities the ldap module will recognize.
128
+ca_list=/home/kg/work/openssl/demoCA/cacert.pem
129
+
130
+# Specifies what checks to perform on server certificates in a TLS session
131
+# allowed values are never/allow/try/demand
132
+# see the TLS_REQCERT tls option part of ldap.conf(8) man page for more details
133
+require_certificate=demand
134
+
135
+#
136
+# Table credentials contains SIP digest authentication credentials.
137
+#
138
+[table:credentials]
139
+
140
+# In our LDAP directory we store SIP digest credentials under
141
+# "Digest Credentials" organization unit so this is where searches for digest
142
+# credentials should start.
143
+base = "ou=Digest Credentials,dc=iptel,dc=org"
144
+
145
+# We search the whole subtree.
146
+scope = subtree
147
+
148
+# For digest credentials we are only interested in objects with objectClass
149
+# 'digestAuthCredentials', objects of all other types are ignored.
150
+filter = "(objectClass=digestAuthCredentials)"
151
+
152
+# Mapping of field names to LDAP attribute names and vice versa. Names are
153
+# delimited using ':', the first name is database field name as used in SER
154
+# modules, the second name (after :) is corresponding LDAP attribute name,
155
+# optionally preceeded with LDAP attribute syntax name in parentheses.
156
+field_map = password : (Binary) digestPassword
157
+field_map = realm : digestRealm
158
+field_map = auth_username : digestUsername
159
+field_map = uid : serUID
160
+field_map = flags : (BitString) serFlags
161
+
162
+# retrieve at most sizelimit entries for a search
163
+#sizelimit = 2147483647
164
+
165
+# wait at most timelimit seconds for a search to complete
166
+#timelimit = 120
167
+
168
+# chase references automatically by OpenLDAP. Default is "never"
169
+# chase_references = never | searching | finding | always
170
+
171
+# chase referrals automatically by OpenLDAP. Default is "no"
172
+# chase_referrals = yes | no
173
+
174
+#
175
+# Domain table stores information about virtual domains
176
+#
177
+[table:domain]
178
+
179
+# Objects mapping domain IDs to domain names and vice versa are stored
180
+# in the subtree with the following root:
181
+base = "ou=Domains,dc=iptel,dc=org"
182
+
183
+scope = subtree
184
+
185
+# We are only interested in serDomain objects when looking up information
186
+# about virtual domains.
187
+filter = "(objectClass=serDomain)"
188
+
189
+field_map = did : (String) serDID
190
+field_map = domain : (String) serDomain
191
+field_map = flags : (BitString) serFlags
192
+
193
+#
194
+# Table domain_attrs contains domain attributes, domain attributes store
195
+# extra information about virtual domains.
196
+#
197
+[table:domain_attrs]
198
+base = "ou=Domains, dc=iptel,dc=org"
199
+scope = subtree
200
+
201
+filter = "(objectClass=serDomainAttr)"
202
+
203
+field_map = did : serDID
204
+field_map = name : serAttrName
205
+field_map = type : (Integer) serAttrType
206
+field_map = value : serAttrValue
207
+field_map = flags : (BitString) serFlags
208
+
209
+1.3.2. reconnect_attempt (integer)
210
+
211
+   Default value is 3.
212
+
213
+   Number of reconnect attempts when connection to the LDAP server is lost.
214
+
215
+   Example 4. Example reconnect_attempt
216
+        ...
217
+        modparam("ldap", "reconnect_attempt", "5");
218
+        ...
219
+
220
+1.4. Functions
221
+
222
+   none
0 223
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+#
2
+# The list of documents to build (without extensions)
3
+#
4
+DOCUMENTS = ldap
5
+
6
+#
7
+# The root directory containing Makefile.doc
8
+#
9
+ROOT_DIR=../../..
10
+
11
+#
12
+# Validate docbook documents before generating output
13
+# (may be slow)
14
+#
15
+#VALIDATE=1
16
+
17
+#
18
+# You can override the stylesheet used to generate
19
+# xhtml documents here
20
+#
21
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
22
+
23
+#
24
+# You can override the stylesheet used to generate
25
+# plain text documents here
26
+#
27
+#TXT_XSL=$(XHTML_XSL)
28
+
29
+include $(ROOT_DIR)/Makefile.doc
0 30
new file mode 100644
... ...
@@ -0,0 +1,310 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
3
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
4
+
5
+<section id="ldap" xmlns:xi="http://www.w3.org/2001/XInclude">
6
+    <sectioninfo>
7
+	<authorgroup>
8
+	    <author>
9
+		<firstname>Jan</firstname>
10
+		<surname>Janak</surname>
11
+		<affiliation><orgname>Iptel.org</orgname></affiliation>
12
+		<address>
13
+		    <email>jan  at iptel dot org</email>
14
+		</address>
15
+	    </author>
16
+	</authorgroup>
17
+	<copyright>
18
+	    <year>2008</year>
19
+	    <holder>Iptel.org GmBH</holder>
20
+	</copyright>
21
+	<revhistory>
22
+	    <revision>
23
+		<revnumber>$Revision$</revnumber>
24
+		<date>$Date$</date>
25
+	    </revision>
26
+	</revhistory>
27
+
28
+    </sectioninfo>
29
+
30
+    <title>ldap module</title>
31
+
32
+    <section id="ldap.overview">
33
+		<title>Overview</title>
34
+		<para>
35
+		The LDAP module is database driver, i.e. it implements DB API functions.
36
+		The goal is map database query defined by table, matching fields and result fields
37
+		to LDAP search in sub-tree defined by root, object class, attributes and
38
+		pass it to the <emphasis>OpenLDAP</emphasis> which communicates with the LDAP server.
39
+		</para>
40
+		
41
+		<para>
42
+		This procedure is sometimes tricky because the LDAP does not support
43
+		all database features or supports them in different manner. Here we
44
+		must express especially <emphasis>filtering</emphasis> and <emphasis>
45
+		multi-values</emphasis>. The multi-value is de facto array of single
46
+		values. If the LDAP module get a multi-value field then generates
47
+		record for every single value, respectively for every combination
48
+		in case the more fields contain multi-value.
49
+		</para>
50
+
51
+		<para>		
52
+		The LDAP supports natively "AND", "OR", "NOT" logical operators and "equal", "non-equal",
53
+		"less-or-equal" and "greater-or-equal" comparison operators. Therefore
54
+		"less" and "greater" operators are mapped as "less/greater-or-equal-AND-not-equal".
55
+		It's important realize it when the attribute which will be used for
56
+		filtering may contain multi-value. 
57
+		The LDAP server evaluates comparison operator on multi-value so that
58
+		the result for record is true if the condition is satisfied for any single
59
+		value. The single values not satisfying condition are not truncated. It implies two cases
60
+		for positive comparison, e.g. "equal", the result contains values not satisfying the
61
+		condition, the case may be handled by additional filter in the LDAP module,
62
+		the negative comparison, e.g. "non-equal", does not return record at all.
63
+		Because the LDAP module cannot know if the LDAP attribute may
64
+		logically contain multi-value so there is introduced DB API option <emphasis>client_side_filtering</emphasis>
65
+		which forces filtering such fields in the LDAP module, i.e. the LDAP server returns
66
+		larger result set because the filtering condition is not passed there.
67
+		</para>
68
+
69
+		<para>
70
+		The necessary condition of successful filtering of particular
71
+		attribute at the LDAP server is correct attribute definition.
72
+		The "equal"/"non-equal" operator requires <emphasis>equality matching rule</emphasis>,
73
+		the "greater"/"less" operator requires <emphasis>ordering matching rule</emphasis>.
74
+		If required matching rule is missing the LDAP server silently returns
75
+		empty result set. In case of double filtering both at the LDAP servar and the LDAP
76
+		module, e.g. multi-value and equal comparison, check the LDAP server matching
77
+		rule satisfies your needs or use <emphasis>client_side_filtering</emphasis> feature. 
78
+		</para>
79
+
80
+		<para>
81
+		The LDAP server may be identified either 
82
+		complete specification of host, user, password in URI or
83
+		is specification reference to <varname>connection</varname> section
84
+		of <varname>config</varname> file. Note in the second case there is only
85
+		one slash.
86
+		</para>
87
+
88
+		<example>
89
+			<title>URI example</title>
90
+			<programlisting>
91
+	...
92
+	modparam("auth", "db_url", "ldap://admin:heslo@127.0.0.1");
93
+
94
+	modparam("auth", "db_url", "ldap:/ldap_server1");
95
+
96
+	...
97
+			</programlisting>
98
+		</example>
99
+
100
+		<para>
101
+			Features:
102
+			<itemizedlist>
103
+				<listitem>
104
+					<para>
105
+						simple, SASL authentication, TLS
106
+					</para>
107
+				</listitem>
108
+				<listitem>
109
+					<para>
110
+						server and client side filtering
111
+					</para>
112
+				</listitem>
113
+				<listitem>
114
+					<para>
115
+						read-only queries
116
+					</para>
117
+				</listitem>
118
+				<listitem>
119
+					<para>
120
+						optional referral chasing by OpenLDAP
121
+					</para>
122
+				</listitem>
123
+				<listitem>
124
+					<para>
125
+						optional reference chasing by OpenLDAP
126
+
127
+					</para>
128
+				</listitem>
129
+			</itemizedlist>
130
+		</para>
131
+    </section>
132
+
133
+	<section id="ldap.dep">
134
+		<title>Dependencies</title>
135
+				   
136
+		<para>
137
+		none
138
+		</para>
139
+	</section>
140
+
141
+	<section id="ldap.parameters">
142
+
143
+		<title>Parameters</title>
144
+
145
+		<section id="config">
146
+			<title><varname>config</varname> (string)</title>
147
+			<para>
148
+				Default value is <emphasis>ldap.cfg</emphasis>.
149
+			</para>
150
+			<para>
151
+			The filename (relatively to ser config file) of mapping database to LDAP definition.
152
+
153
+			It is the main configuration file for the LDAP module in SER.
154
+			The configuration file maps database table names used in SER to LDAP directory
155
+			sub-trees to be searched. In addition to that the configuration file also allows to
156
+			configure the LDAP search filter and maps database field names to
157
+			LDAP attribute names and vice versa. 
158
+			</para>
159
+			<example>
160
+				<title>Example <varname>config</varname></title>
161
+				<programlisting>
162
+	...
163
+	modparam("ldap", "config", "my-ldap.cfg");
164
+	...
165
+				</programlisting>
166
+			</example>
167
+
168
+
169
+			<example>
170
+				<title>Configuration file example</title>
171
+				<programlisting>
172
+# Supported Attribute Type Names:
173
+#  * GeneralizedTime
174
+#  * Integer
175
+#  * BitString
176
+#  * Boolean
177
+#  * String
178
+#  * Binary
179
+#  * Float
180
+#
181
+
182
+[connection:ldap_server1]
183
+host=127.0.0.1
184
+port=389
185
+username=ser
186
+password=heslo
187
+# LDAP or LDAP SASL authentication mechanism.
188
+# Allowed values: none (default), simple, digest-md5, external
189
+authtype=simple
190
+
191
+# tls encryption
192
+tls=off
193
+
194
+# Specifies the file that contains certificates for all of the Certificate
195
+# Authorities the ldap module will recognize.
196
+ca_list=/home/kg/work/openssl/demoCA/cacert.pem
197
+
198
+# Specifies what checks to perform on server certificates in a TLS session
199
+# allowed values are never/allow/try/demand
200
+# see the TLS_REQCERT tls option part of ldap.conf(8) man page for more details
201
+require_certificate=demand
202
+
203
+#
204
+# Table credentials contains SIP digest authentication credentials.
205
+#
206
+[table:credentials]
207
+
208
+# In our LDAP directory we store SIP digest credentials under 
209
+# "Digest Credentials" organization unit so this is where searches for digest 
210
+# credentials should start.
211
+base = "ou=Digest Credentials,dc=iptel,dc=org"
212
+
213
+# We search the whole subtree.
214
+scope = subtree
215
+
216
+# For digest credentials we are only interested in objects with objectClass 
217
+# 'digestAuthCredentials', objects of all other types are ignored.
218
+filter = "(objectClass=digestAuthCredentials)"
219
+
220
+# Mapping of field names to LDAP attribute names and vice versa. Names are
221
+# delimited using ':', the first name is database field name as used in SER
222
+# modules, the second name (after :) is corresponding LDAP attribute name,
223
+# optionally preceeded with LDAP attribute syntax name in parentheses.
224
+field_map = password : (Binary) digestPassword
225
+field_map = realm : digestRealm
226
+field_map = auth_username : digestUsername
227
+field_map = uid : serUID
228
+field_map = flags : (BitString) serFlags
229
+
230
+# retrieve at most sizelimit entries for a search
231
+#sizelimit = 2147483647
232
+
233
+# wait at most timelimit seconds for a search to complete
234
+#timelimit = 120
235
+
236
+# chase references automatically by OpenLDAP. Default is "never"
237
+# chase_references = never | searching | finding | always
238
+
239
+# chase referrals automatically by OpenLDAP. Default is "no"
240
+# chase_referrals = yes | no
241
+
242
+#
243
+# Domain table stores information about virtual domains
244
+#
245
+[table:domain]
246
+
247
+# Objects mapping domain IDs to domain names and vice versa are stored
248
+# in the subtree with the following root:
249
+base = "ou=Domains,dc=iptel,dc=org"
250
+
251
+scope = subtree
252
+
253
+# We are only interested in serDomain objects when looking up information
254
+# about virtual domains.
255
+filter = "(objectClass=serDomain)"
256
+
257
+field_map = did : (String) serDID
258
+field_map = domain : (String) serDomain
259
+field_map = flags : (BitString) serFlags
260
+
261
+#
262
+# Table domain_attrs contains domain attributes, domain attributes store
263
+# extra information about virtual domains.
264
+#
265
+[table:domain_attrs]
266
+base = "ou=Domains, dc=iptel,dc=org"
267
+scope = subtree
268
+
269
+filter = "(objectClass=serDomainAttr)"
270
+
271
+field_map = did : serDID
272
+field_map = name : serAttrName
273
+field_map = type : (Integer) serAttrType
274
+field_map = value : serAttrValue
275
+field_map = flags : (BitString) serFlags			
276
+				</programlisting>
277
+			</example>
278
+			
279
+		</section>
280
+
281
+		<section id="reconnect_attempt">
282
+			<title><varname>reconnect_attempt</varname> (integer)</title>
283
+			<para>
284
+			Default value is <emphasis>3</emphasis>.
285
+			</para>
286
+			<para>
287
+			Number of reconnect attempts when connection to the LDAP server is lost.
288
+			</para>
289
+
290
+			<example>
291
+				<title>Example <varname>reconnect_attempt</varname></title>
292
+				<programlisting>
293
+	...
294
+	modparam("ldap", "reconnect_attempt", "5");
295
+	...
296
+				</programlisting>
297
+			</example>
298
+		</section>
299
+
300
+	</section>
301
+
302
+	<section id="ldap.functions">
303
+		<title>Functions</title>
304
+		<para>
305
+		none
306
+		</para>
307
+	</section>
308
+
309
+</section>
310
+
... ...
@@ -249,6 +249,14 @@ static cfg_option_t scope_values[] = {
249 249
 };
250 250
 
251 251
 
252
+static cfg_option_t deref_values[] = {
253
+	{"never",     .val = LDAP_DEREF_NEVER },   /* default, 0x00 */
254
+	{"searching", .val = LDAP_DEREF_SEARCHING},
255
+	{"finding",   .val = LDAP_DEREF_FINDING },
256
+	{"always",    .val = LDAP_DEREF_ALWAYS },
257
+	{0}
258
+};
259
+
252 260
 static cfg_option_t ldap_tab_options[] = {
253 261
 	{"scope",     .param = scope_values, .f = cfg_parse_enum_opt},
254 262
 	{"field_map", .f = parse_field_map},
... ...
@@ -256,6 +264,8 @@ static cfg_option_t ldap_tab_options[] = {
256 264
 	{"base",      .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
257 265
 	{"timelimit", .f = cfg_parse_int_opt},
258 266
 	{"sizelimit", .f = cfg_parse_int_opt},
267
+	{"chase_references",  .param = deref_values, .f = cfg_parse_enum_opt},
268
+	{"chase_referrals",   .f = cfg_parse_bool_opt},
259 269
 	{0}
260 270
 };
261 271
 
... ...
@@ -275,9 +285,9 @@ static cfg_option_t ldap_con_options[] = {
275 285
 	{"username", 		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
276 286
 	{"password", 		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
277 287
 	{"authtype", 		    .param = auth_values, .f = cfg_parse_enum_opt},
278
-	{"tls",			        .f = cfg_parse_bool_opt},
288
+	{"tls",			    .f = cfg_parse_bool_opt},
279 289
 	{"ca_list",  		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
280
-	{"require_certificate", .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
290
+	{"require_certificate",     .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
281 291
 	{0}
282 292
 };
283 293
 
... ...
@@ -332,6 +342,10 @@ static int parse_section(void* param, cfg_parser_t* st, unsigned int flags)
332 342
 		}
333 343
 		ldap_tab_options[4].param = &cfg->timelimit;
334 344
 		ldap_tab_options[5].param = &cfg->sizelimit;
345
+		for(i = 0; deref_values[i].name; i++) {
346
+			deref_values[i].param = &cfg->chase_references;
347
+		}
348
+		ldap_tab_options[7].param = &cfg->chase_referrals;
335 349
 	} else if (type == LDAP_CON_SECTION) {
336 350
 		if ((cinfo = pkg_malloc(sizeof(*cinfo))) == NULL) {
337 351
 			ERR("ldap:%s:%d: Out of memory\n", st->file, st->line);
... ...
@@ -45,6 +45,8 @@ struct ld_cfg {
45 45
 	int n;          /**< Number of fields in the arrays */
46 46
 	int sizelimit; /**< retrieve at most sizelimit entries for a search */
47 47
 	int timelimit; /**< wait at most timelimit seconds for a search to complete */
48
+	int chase_references;  /**< dereference option for LDAP library */
49
+	int chase_referrals;   /**< follow referrals option for LDAP library */
48 50
 	struct ld_cfg* next; /**< The next table in the list */
49 51
 };
50 52
 
... ...
@@ -90,6 +90,8 @@ int ld_cmd(db_cmd_t* cmd)
90 90
 {
91 91
 	struct ld_cmd* lcmd;
92 92
 	struct ld_cfg* cfg;
93
+	struct ld_fld* lfld;
94
+	int i, j;
93 95
 
94 96
 	lcmd = (struct ld_cmd*)pkg_malloc(sizeof(struct ld_cmd));
95 97
 	if (lcmd == NULL) {
... ...
@@ -134,11 +136,37 @@ int ld_cmd(db_cmd_t* cmd)
134 136
 	if (cfg->filter.s) {
135 137
 		lcmd->filter = cfg->filter;
136 138
 	}
139
+	lcmd->chase_references = cfg->chase_references;
140
+	lcmd->chase_referrals = cfg->chase_referrals;
137 141
 
138 142
 	if (ld_resolve_fld(cmd->match, cfg) < 0) goto error;
139 143
 	if (ld_resolve_fld(cmd->result, cfg) < 0) goto error;
140 144
 
145
+	/* prepare filter for each result field */
146
+	for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
147
+		int n;
148
+		lfld = DB_GET_PAYLOAD(cmd->result + i);
149
+		lfld->filter = NULL;
150
+	
151
+		for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
152
+			if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0)
153
+				n++;	
154
+		}
155
+		
156
+		if (n > 0) {
157
+			lfld->filter = pkg_malloc((n+1)*sizeof(*(lfld->filter)));
158
+			if (!lfld->filter) return -1 /* E_OUT_OF_MEM*/;
159
+			for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
160
+				if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0) {
161
+					lfld->filter[n] = cmd->match+j;
162
+					n++;
163
+				}
164
+			}
165
+			lfld->filter[n] = NULL;
166
+		}
167
+	}
141 168
 	if (build_result_array(&lcmd->result, cmd) < 0) goto error;
169
+
142 170
 	DB_SET_PAYLOAD(cmd, lcmd);
143 171
 	return 0;
144 172
 
... ...
@@ -162,13 +190,12 @@ int ld_cmd_exec(db_res_t* res, db_cmd_t* cmd)
162 190
 	char* filter, *err_desc;
163 191
 	int ret, err;
164 192
 	LDAPMessage *msg, *resmsg;
165
-	int reconn_cnt = glb_reconn_cnt;
193
+	int reconn_cnt;
166 194
 	int msgid;
167 195
 	char *oid;
168 196
 	struct berval *data;
169 197
 	struct timeval restimeout;
170 198
 
171
-
172 199
 	filter = NULL;
173 200
 	err_desc = NULL;
174 201
 	resmsg = NULL;
... ...
@@ -179,14 +206,21 @@ int ld_cmd_exec(db_res_t* res, db_cmd_t* cmd)
179 206
 	con = cmd->ctx->con[db_payload_idx];
180 207
 	lcmd = DB_GET_PAYLOAD(cmd);
181 208
 	lcon = DB_GET_PAYLOAD(con);
209
+	
210
+	reconn_cnt = ld_reconnect_attempt;
182 211
 
183
-	if (ld_fld2ldap(&filter, cmd->match, &lcmd->filter) < 0) {
212
+	if (ld_prepare_ldap_filter(&filter, cmd, &lcmd->filter) < 0) {
184 213
 		ERR("ldap: Error while building LDAP search filter\n");
185 214
 		goto error;
186 215
 	}
187 216
 
217
+	DBG("ldap: ldap_search(base:'%s', filter:'%s')\n", lcmd->base, filter);
188 218
 	do {
189 219
 		if (lcon->flags & LD_CONNECTED) {
220
+			ldap_set_option(lcon->con, LDAP_OPT_DEREF, ((void *)&lcmd->chase_references));
221
+			/* there is alternative method using LDAP_CONTROL_REFERRALS per request but is not well documented */
222
+			ldap_set_option(lcon->con, LDAP_OPT_REFERRALS, lcmd->chase_referrals?LDAP_OPT_ON:LDAP_OPT_OFF);
223
+		
190 224
 			ret = ldap_search_ext(lcon->con, lcmd->base, lcmd->scope, filter,
191 225
 								  lcmd->result, 0, NULL, NULL,
192 226
 								  lcmd->timelimit.tv_sec ? &lcmd->timelimit : NULL,
... ...
@@ -232,7 +266,6 @@ int ld_cmd_exec(db_res_t* res, db_cmd_t* cmd)
232 266
 		}
233 267
 	} while (ret <= 0);
234 268
 
235
-
236 269
 	/* looking for unsolicited messages */
237 270
 	for (msg = ldap_first_message(lcon->con, resmsg);
238 271
 		 msg != NULL;
... ...
@@ -302,7 +335,7 @@ static int search_entry(db_res_t* res, int init)
302 335
 	db_con_t* con;
303 336
 	struct ld_res* lres;
304 337
 	struct ld_con* lcon;
305
-
338
+	int r;
306 339
 	lres = DB_GET_PAYLOAD(res);
307 340
 	/* FIXME */
308 341
 	con = res->cmd->ctx->con[db_payload_idx];
... ...
@@ -314,19 +347,24 @@ static int search_entry(db_res_t* res, int init)
314 347
 	    /* there is no more value combination result left */
315 348
 	    || ld_incindex(res->cmd->result)) {
316 349
 
317
-		if (init)
318
-			lres->current = ldap_first_message(lcon->con, lres->msg);
319
-		else
320
-			lres->current = ldap_next_message(lcon->con, lres->current);
321
-
322
-		while(lres->current) {
323
-			if (ldap_msgtype(lres->current) == LDAP_RES_SEARCH_ENTRY) {
324
-				break;
350
+		do {
351
+			if (init) {
352
+				lres->current = ldap_first_message(lcon->con, lres->msg);
353
+				init = 0;
325 354
 			}
326
-			lres->current = ldap_next_message(lcon->con, lres->current);
327
-		}
328
-		if (lres->current == NULL) return 1;
329
-		if (ld_ldap2fldinit(res->cmd->result, lcon->con, lres->current) < 0) return -1;
355
+			else
356
+				lres->current = ldap_next_message(lcon->con, lres->current);
357
+			
358
+			while(lres->current) {
359
+				if (ldap_msgtype(lres->current) == LDAP_RES_SEARCH_ENTRY) {
360
+					break;
361
+				}
362
+				lres->current = ldap_next_message(lcon->con, lres->current);
363
+			}
364
+			if (lres->current == NULL) return 1;
365
+			r = ld_ldap2fldinit(res->cmd->result, lcon->con, lres->current);
366
+		} while (r > 0);
367
+		if (r < 0) return -1;
330 368
 	} else {
331 369
 		if (ld_ldap2fld(res->cmd->result, lcon->con, lres->current) < 0) return -1;
332 370
 	}
... ...
@@ -347,5 +385,35 @@ int ld_cmd_next(db_res_t* res)
347 385
 	return search_entry(res, 0);
348 386
 }
349 387
 
388
+#define is_space(c) ((c)==' '||(c)==','||(c)==';'||(c)=='\t'||(c)=='\n'||(c)=='\r'||(c)=='\0')
389
+
390
+int ld_cmd_setopt(db_cmd_t* cmd, char* optname, va_list ap)
391
+{
392
+	struct ld_fld* lfld;
393
+	char* val, *c;
394
+	int i;
395
+		
396
+	if (!strcasecmp("client_side_filtering", optname)) {
397
+		val = va_arg(ap, char*);
398
+
399
+		for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
400
+			c = val;
401
+			do {
402
+				c = strstr(c, cmd->result[i].name);
403
+				if (c) {
404
+					if ((c == val || is_space(*(c-1))) && is_space(*(c+strlen(cmd->result[i].name)))) {
405
+						lfld = (struct ld_fld*)DB_GET_PAYLOAD(cmd->result + i);
406
+						lfld->client_side_filtering = 1;
407
+						break;
408
+					}
409
+					c += strlen(cmd->result[i].name);
410
+				}
411
+			} while (c != NULL);
412
+		}
413
+	}
414
+	else
415
+		return 1;
416
+        return 0;
417
+}
350 418
 
351 419
 /** @} */
... ...
@@ -55,6 +55,8 @@ struct ld_cmd {
55 55
 	char** result; /**< An array with result attribute names for ldap_search */
56 56
 	int sizelimit; /**< retrieve at most sizelimit entries for a search */
57 57
 	struct timeval timelimit; /**< wait at most timelimit seconds for a search to complete */
58
+	int chase_references;  /**< dereference option for LDAP library */
59
+	int chase_referrals;   /**< follow referrals option for LDAP library */
58 60
 };
59 61
 
60 62
 
... ...
@@ -86,6 +88,8 @@ int ld_cmd_first(db_res_t* res);
86 88
 
87 89
 int ld_cmd_next(db_res_t* res);
88 90
 
91
+int ld_cmd_setopt(db_cmd_t* cmd, char* optname, va_list ap);
92
+
89 93
 /** @} */
90 94
 
91 95
 #endif /* _LD_CMD_H */
... ...
@@ -30,8 +30,6 @@
30 30
  * Functions related to connections to LDAP servers.
31 31
  */
32 32
 
33
-#define LDAP_DEPRECATED 1
34
-
35 33
 #include "ld_con.h"
36 34
 #include "ld_uri.h"
37 35
 
... ...
@@ -30,8 +30,6 @@
30 30
  * Data field conversion and type checking functions.
31 31
  */
32 32
 
33
-#define LDAP_DEPRECATED 1
34
-
35 33
 #define _XOPEN_SOURCE 4     /* bsd */
36 34
 #define _XOPEN_SOURCE_EXTENDED 1    /* solaris */
37 35
 #define _SVID_SOURCE 1 /* timegm */
... ...
@@ -152,8 +150,10 @@ static inline int sb_add_esc(struct sbuf *sb, char* str, int len)
152 150
 static void ld_fld_free(db_fld_t* fld, struct ld_fld* payload)
153 151
 {
154 152
 	db_drv_free(&payload->gen);
155
-	if (payload->values) ldap_value_free_len(payload->values);
153
+	if (payload->values) ldap_value_free_len(payload->values);	
156 154
 	payload->values = NULL;
155
+	if (payload->filter) pkg_free(payload->filter);
156
+	payload->filter = NULL;
157 157
 	pkg_free(payload);
158 158
 }
159 159
 
... ...
@@ -233,19 +233,45 @@ static inline int ldap_gentime2db_datetime(time_t* dst, str* src)
233 233
 
234 234
 	/* It is necessary to zero tm structure first */
235 235
 	memset(&time, '\0', sizeof(struct tm));
236
-	strptime(src->s, "%Y%m%d%H%M%S", &time);
237
-
238
-	/* Daylight saving information got lost in the database
239
-	 * so let timegm to guess it. This eliminates the bug when
240
-	 * contacts reloaded from the database have different time
241
-	 * of expiration by one hour when daylight saving is used
242
-	 */
243
-	time.tm_isdst = -1;
244
-#ifdef HAVE_TIMEGM
245
-    *dst = timegm(&time);
246
-#else
247
-    *dst = _timegm(&time);
248
-#endif /* HAVE_TIMEGM */
236
+	/* YYYYMMDDHHMMSS[.sss][ 'Z' | ( {'+'|'-'} ZZZZ) ] */
237
+	strptime(src->s, "%Y%m%d%H%M%S", &time);  /* Note: frac of seconds are lost in time_t representation */
238
+	
239
+	if (src->s[src->len-1] == 'Z' || src->s[src->len-5] == '-' || src->s[src->len-5] == '+') {
240
+		/* GMT or specified TZ, no daylight saving time */
241
+		#ifdef HAVE_TIMEGM
242
+		*dst = timegm(&time);
243
+		#else
244
+		*dst = _timegm(&time);
245
+		#endif /* HAVE_TIMEGM */
246
+
247
+		if (src->s[src->len-1] != 'Z') {
248
+			/* timezone is specified */
249
+			memset(&time, '\0', sizeof(struct tm));
250
+			strptime(src->s + src->len - 4, "%H%M", &time);
251
+			switch (src->s[src->len-5]) {
252
+				case '-':
253
+					*dst -= time.tm_hour*3600+time.tm_min*60;
254
+					break;
255
+				case '+':
256
+					*dst += time.tm_hour*3600+time.tm_min*60;
257
+					break;
258
+				default:
259
+					;
260
+			}
261
+		}
262
+	}
263
+	else {
264
+		/* it's local time */
265
+
266
+		/* Daylight saving information got lost in the database
267
+		 * so let timegm to guess it. This eliminates the bug when
268
+		 * contacts reloaded from the database have different time
269
+		 * of expiration by one hour when daylight saving is used
270
+		 */
271
+		time.tm_isdst = -1;
272
+		*dst = timelocal(&time);
273
+	}
274
+	
249 275
 	return 0;
250 276
 }
251 277
 
... ...
@@ -263,84 +289,17 @@ static inline int ldap_str2db_float(float* dst, char* src)
263 289
 	return 0;
264 290
 }
265 291
 
292
+static inline int ldap_fld2db_fld(db_fld_t* fld, str v) {
266 293
 
267
-int ld_ldap2fldinit(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg)
268
-{
269
-	return ld_ldap2fldex(fld, ldap, msg, 1);
270
-}
271
-
272
-int ld_ldap2fld(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg)
273
-{
274
-	return ld_ldap2fldex(fld, ldap, msg, 0);
275
-}
276
-
277
-int ld_incindex(db_fld_t* fld) {
278
-	int i;
279
-	struct ld_fld* lfld;
280
-
281
-
282
-	if (fld == NULL) return 0;
283
-
284
-	i = 0;
285
-	while (!DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i])) {
286
-		lfld = DB_GET_PAYLOAD(fld + i);
287
-		lfld->index++;
288
-		/* the index limit has been reached */
289
-		if (lfld->index >= lfld->valuesnum) {
290
-			lfld->index = 0;
291
-		} else {
292
-			return 0;
293
-		}
294
-		i++;
295
-	}
296
-
297
-	/* there is no more value combination left */
298
-	return 1;
299
-}
300
-
301
-int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
302
-{
303
-	int i;
304
-	struct ld_fld* lfld;
305
-	str v;
306
-
307
-	if (fld == NULL || msg == NULL) return 0;
308
-	for(i = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
309
-		lfld = DB_GET_PAYLOAD(fld + i);
310
-
311
-		if (init) {
312
-			/* free the values of the previous object */
313
-			if (lfld->values) ldap_value_free_len(lfld->values);
314
-			lfld->values = ldap_get_values_len(ldap, msg, lfld->attr.s);
315
-
316
-			if (lfld->values == NULL || lfld->values[0] == NULL) {
317
-				fld[i].flags |= DB_NULL;
318
-				/* index == 0 means no value available */
319
-				lfld->valuesnum = 0;
320
-			} else {
321
-				/* init the number of values */
322
-				lfld->valuesnum = ldap_count_values_len(lfld->values);
323
-			}
324
-			/* pointer to the current value */
325
-			lfld->index = 0;
326
-		}
327
-
328
-		/* this is an empty value */
329
-		if (!lfld->valuesnum)
330
-			continue;
331
-
332
-		v.s = lfld->values[lfld->index]->bv_val;
333
-		v.len = lfld->values[lfld->index]->bv_len;
334
-
335
-		switch(fld[i].type) {
294
+	switch(fld->type) {
336 295
 		case DB_CSTR:
337
-			fld[i].v.cstr = v.s;
296
+			fld->v.cstr = v.s;
338 297
 			break;
339 298
 
340 299
 		case DB_STR:
341 300
 		case DB_BLOB:
342
-			fld[i].v.lstr.s = v.s;
343
-			fld[i].v.lstr.len = v.len;
301
+			fld->v.lstr.s = v.s;
302
+			fld->v.lstr.len = v.len;
344 303
 			break;
345 304
 
346 305
 		case DB_INT:
... ...
@@ -349,7 +308,7 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
349 308
 				v.s[v.len - 2] == '\'') {
350 309
 				v.s++;
351 310
 				v.len -= 3;
352
-				if (ldap_bit2db_int(&fld[i].v.int4, &v) != 0) {
311
+				if (ldap_bit2db_int(&fld->v.int4, &v) != 0) {
353 312
 					ERR("ldap: Error while converting bit string '%.*s'\n",
354 313
 						v.len, ZSW(v.s));
355 314
 					return -1;
... ...
@@ -358,16 +317,16 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
358 317
 			}
359 318
 
360 319
 			if (v.len == 4 && !strncasecmp("TRUE", v.s, v.len)) {
361
-				fld[i].v.int4 = 1;
320
+				fld->v.int4 = 1;
362 321
 				break;
363 322
 			}
364 323
 
365 324
 			if (v.len == 5 && !strncasecmp("FALSE", v.s, v.len)) {
366
-				fld[i].v.int4 = 0;
325
+				fld->v.int4 = 0;
367 326
 				break;
368 327
 			}
369 328
 
370
-			if (ldap_int2db_int(&fld[i].v.int4, &v) != 0) {
329
+			if (ldap_int2db_int(&fld->v.int4, &v) != 0) {
371 330
 				ERR("ldap: Error while converting %.*s to integer\n",
372 331
 					v.len, ZSW(v.s));
373 332
 				return -1;
... ...
@@ -375,7 +334,7 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
375 334
 			break;
376 335
 
377 336
 		case DB_DATETIME:
378
-			if (ldap_gentime2db_datetime(&fld[i].v.time, &v) != 0) {
337
+			if (ldap_gentime2db_datetime(&fld->v.time, &v) != 0) {
379 338
 				ERR("ldap: Error while converting LDAP time value '%.*s'\n",
380 339
 					v.len, ZSW(v.s));
381 340
 				return -1;
... ...
@@ -384,7 +343,7 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
384 343
 
385 344
 		case DB_FLOAT:
386 345
 			/* We know that the ldap library zero-terminated v.s */
387
-			if (ldap_str2db_float(&fld[i].v.flt, v.s) != 0) {
346
+			if (ldap_str2db_float(&fld->v.flt, v.s) != 0) {
388 347
 				ERR("ldap: Error while converting '%.*s' to float\n",
389 348
 					v.len, ZSW(v.s));
390 349
 				return -1;
... ...
@@ -393,7 +352,7 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
393 352
 
394 353
 		case DB_DOUBLE:
395 354
 			/* We know that the ldap library zero-terminated v.s */
396
-			if (ldap_str2db_double(&fld[i].v.dbl, v.s) != 0) {
355
+			if (ldap_str2db_double(&fld->v.dbl, v.s) != 0) {
397 356
 				ERR("ldap: Error while converting '%.*s' to double\n",
398 357
 					v.len, ZSW(v.s));
399 358
 				return -1;
... ...
@@ -401,193 +360,452 @@ int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
401 360
 			break;
402 361
 
403 362
 		default:
404
-			ERR("ldap: Unsupported field type: %d\n", fld[i].type);
363
+			ERR("ldap: Unsupported field type: %d\n", fld->type);
405 364
 			return -1;
406
-		}
407 365
 	}
408 366
 	return 0;
409 367
 }
410 368
 
369
+int ld_ldap2fldinit(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg)
370
+{
371
+	return ld_ldap2fldex(fld, ldap, msg, 1);
372
+}
411 373
 
412
-static inline int db_str2ldap_str(struct sbuf* buf, db_fld_t* fld)
374
+int ld_ldap2fld(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg)
413 375
 {
376
+	return ld_ldap2fldex(fld, ldap, msg, 0);
377
+}
378
+
379
+int ld_incindex(db_fld_t* fld) {
380
+	int i;
414 381
 	struct ld_fld* lfld;
415
-	int rv;
416 382
 
417
-	rv = 0;
418
-	if (fld->op != DB_EQ) {
419
-		ERR("ldap: String attributes can only be compared "
420
-			"with '=' operator\n");
421
-		return -1;
383
+
384
+	if (fld == NULL) return 0;
385
+
386
+	i = 0;
387
+	while (!DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i])) {
388
+		lfld = DB_GET_PAYLOAD(fld + i);
389
+		lfld->index++;
390
+		/* the index limit has been reached */
391
+		if (lfld->index >= lfld->valuesnum) {
392
+			lfld->index = 0;
393
+		} else {
394
+			return 0;
395
+		}
396
+		i++;
422 397
 	}
423 398
 
424
-	lfld = DB_GET_PAYLOAD(fld);
425
-	rv |= sb_add(buf, lfld->attr.s, lfld->attr.len);
426
-	rv |= sb_add(buf, "=", 1);
427
-	rv |= sb_add_esc(buf, fld->v.lstr.s, fld->v.lstr.len);
428
-	return rv;
399
+	/* there is no more value combination left */
400
+	return 1;
429 401
 }
430 402
 
431
-
432
-static inline int db_cstr2ldap_str(struct sbuf* buf, db_fld_t* fld)
403
+#define CMP_NUM(fld_v,match_v,fld) \
404
+	if (fld_v.fld == match_v.fld) \
405
+		op = 0x02; \
406
+	else if (fld_v.fld < match_v.fld) \
407
+		op = 0x01; \
408
+	else if (fld_v.fld > match_v.fld) \
409
+		op = 0x04;
410
+		
411
+int ld_ldap2fldex(db_fld_t* fld, LDAP* ldap, LDAPMessage* msg, int init)
433 412
 {
413
+	int i;
434 414
 	struct ld_fld* lfld;
435
-	int rv;
415
+	str v;
436 416
 
437
-	rv = 0;
438
-	if (fld->op != DB_EQ) {
439
-		ERR("ldap: String attributes can only be compared "
440
-			"with '=' operator\n");
441
-		return -1;
442
-	}
417
+	if (fld == NULL || msg == NULL) return 0;
418
+	for(i = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
419
+		lfld = DB_GET_PAYLOAD(fld + i);
420
+		if (init) {
421
+			if (fld[i].type == DB_NONE) {
422
+				switch (lfld->syntax) {
423
+					case LD_SYNTAX_STRING:
424
+						fld[i].type = DB_STR;
425
+						break;
426
+					case LD_SYNTAX_INT:
427
+					case LD_SYNTAX_BOOL:
428
+					case LD_SYNTAX_BIT:
429
+						fld[i].type = DB_INT;
430
+						break;
431
+					case LD_SYNTAX_FLOAT:
432
+						fld[i].type = DB_FLOAT;
433
+						break;
434
+						
435
+					case LD_SYNTAX_GENTIME:
436
+						fld[i].type = DB_DATETIME;
437
+						break;
438
+					case LD_SYNTAX_BIN:
439
+						fld[i].type = DB_BITMAP;
440
+						break;
441
+				}
443 442
 
444
-	lfld = DB_GET_PAYLOAD(fld);
445
-	rv |= sb_add(buf, lfld->attr.s, lfld->attr.len);
446
-	rv |= sb_add(buf, "=", 1);
447
-	rv |= sb_add_esc(buf, fld->v.cstr,
448
-					 fld->v.cstr ? strlen(fld->v.cstr) : 0);
449
-	return rv;
443
+			}
444
+			
445
+			/* free the values of the previous object */
446
+			if (lfld->values) ldap_value_free_len(lfld->values);
447
+			lfld->values = ldap_get_values_len(ldap, msg, lfld->attr.s);
448
+			lfld->index = 0;
449
+			
450
+			if (lfld->values == NULL || lfld->values[0] == NULL) {
451
+				fld[i].flags |= DB_NULL;
452
+				/* index == 0 means no value available */
453
+				lfld->valuesnum = 0;
454
+				if (lfld->client_side_filtering && lfld->filter) {
455
+					int j;
456
+					/* if the all filter conditions requires NULL value then we can accept the record */
457
+					for (j=0; lfld->filter[j]; j++) {
458
+						if (lfld->filter[j]->flags & DB_NULL && lfld->filter[j]->op == DB_EQ) {
459
+							continue;
460
+						}
461
+						return 1; /* get next record */
462
+					}
463
+				}
464
+			} else {
465
+				/* init the number of values */
466
+				fld[i].flags &= ~DB_NULL;
467
+				lfld->valuesnum = ldap_count_values_len(lfld->values);
468
+			
469
+				if ((lfld->valuesnum > 1 || lfld->client_side_filtering) && lfld->filter) {
470
+					
471
+					/* in case of multivalue we must check if value fits in filter criteria.
472
+					   LDAP returns record (having each multivalue) if one particular
473
+					   multivalue fits in filter provided to LDAP search. We need
474
+					   filter out these values manually. It not perfect because 
475
+					   LDAP filtering may be based on different rule/locale than
476
+					   raw (ASCII,...) comparision. 
477
+					
478
+					   We reorder values so we'll have interesting values located from top up to valuesnum at the end.
479
+
480
+					   The same algorithm is applied for client side filtering
481
+					   
482
+					 */
483
+					 
484
+					do {
485
+						int passed, j;
486
+						for (j=0, passed = 1; lfld->filter[j] && passed; j++) {
487
+							int op;  /* b0..less, b1..equal, b2..greater, zero..non equal */ 
488
+							op = 0x00;
489
+							if (lfld->filter[j]->flags & DB_NULL) {
490
+								/* always non equal because field is not NULL */
491
+							}
492
+							else {
493
+								
494
+								v.s = lfld->values[lfld->index]->bv_val;
495
+								v.len = lfld->values[lfld->index]->bv_len;
496
+							
497
+								if (ldap_fld2db_fld(fld + i, v) < 0) {
498
+									passed = 0;
499
+									break; /* for loop */
500
+								}
501
+								else {
502
+									db_fld_val_t v;
503
+									int t;
504
+									static char buf[30];
505
+									t = lfld->filter[j]->type;
506
+									/* we need compare value provided in match condition with value returned by LDAP.
507
+									   The match value should be the same type as LDAP value obtained during
508
+									   db_cmd(). We implement some basic conversions.
509
+									 */
510
+									v = lfld->filter[j]->v;
511
+									if (t == DB_CSTR) {
512
+										v.lstr.s = v.cstr;
513
+										v.lstr.len = strlen(v.lstr.s);
514
+										t = DB_STR;
515
+									} 
516
+									switch (fld[i].type) {
517
+										case DB_CSTR:
518
+											fld[i].v.lstr.s = fld[i].v.cstr;
519
+											fld[i].v.lstr.len = strlen(fld[i].v.lstr.s);
520
+											fld[i].type = DB_STR; 
521
+											/* no break */
522
+										case DB_STR:
523
+											
524
+											switch (t) {
525
+												case DB_INT:
526
+													v.lstr.len = snprintf(buf, sizeof(buf)-1, "%d", v.int4);
527
+													v.lstr.s = buf;
528
+													break;
529
+												/* numeric conversion for double/float not supported because of non unique string representation */
530
+												default:
531
+													goto skip_conv;
532
+											}
533
+											break;
534
+										case DB_INT:
535
+											switch (t) {
536
+												case DB_DOUBLE:
537
+													if ((double)(int)v.dbl != (double)v.dbl) 
538
+														goto skip_conv;
539
+													v.int4 = v.dbl;
540
+													break;
541
+												case DB_FLOAT:
542
+													if ((float)(int)v.flt != (float)v.flt) 
543
+														goto skip_conv;
544
+													v.int4 = v.flt;
545
+													break;
546
+												case DB_STR: 
547
+													if (v.lstr.len > 0) {
548
+														char c, *p;
549
+														int n;
550
+														c = v.lstr.s[v.lstr.len];
551
+														v.lstr.s[v.lstr.len] = '\0';
552
+														n = strtol(v.lstr.s, &p, 10);
553
+														v.lstr.s[v.lstr.len] = c;
554
+														if ((*p) != '\0') {
555
+															goto skip_conv;
556
+														}
557
+														v.int4 = n;
558
+													}
559
+													break;
560
+												default:
561
+													goto skip_conv;
562
+											}
563
+											break;
564
+										case DB_FLOAT:
565
+											switch (t) {
566
+												case DB_DOUBLE:
567
+													v.flt = v.dbl;
568
+													break;
569
+												case DB_INT:
570
+													v.flt = v.int4;
571
+													break;
572
+												#ifdef  __USE_ISOC99
573
+												case DB_STR: 
574
+													if (v.lstr.len > 0) {
575
+														char c, *p;
576
+														float n;
577
+														c = v.lstr.s[v.lstr.len];
578
+														v.lstr.s[v.lstr.len] = '\0';
579
+														n = strtof(v.lstr.s, &p);
580
+														v.lstr.s[v.lstr.len] = c;
581
+														if ((*p) != '\0') {
582
+															goto skip_conv;
583
+														}
584
+														v.flt = n;
585
+													}
586
+													break;
587
+												#endif
588
+												default:
589
+													goto skip_conv;
590
+											}
591
+											break;
592
+										case DB_DOUBLE:
593
+											switch (t) {
594
+												case DB_FLOAT:
595
+													v.dbl = v.flt;
596
+													break;
597
+												case DB_INT:
598
+													v.dbl = v.int4;
599
+													break;
600
+												case DB_STR: 
601
+													if (v.lstr.len > 0) {
602
+														char c, *p;
603
+														double n;
604
+														c = v.lstr.s[v.lstr.len];
605
+														v.lstr.s[v.lstr.len] = '\0';
606
+														n = strtod(v.lstr.s, &p);
607
+														v.lstr.s[v.lstr.len] = c;
608
+														if ((*p) != '\0') {
609
+															goto skip_conv;
610
+														}
611
+														v.dbl = n;
612
+													}
613
+													break;
614
+												default:
615
+													goto skip_conv;
616
+											}
617
+											break;
618
+										case DB_BLOB:
619
+										case DB_BITMAP:
620
+										case DB_DATETIME:
621
+										default:
622
+											goto skip_conv;
623
+										}
624
+									t = fld[i].type;
625
+								skip_conv:
626
+									if (t == fld[i].type) {
627
+									
628
+										switch (fld[i].type) {
629
+											case DB_CSTR: /* impossible, already converted to DB_STR */
630
+											case DB_STR:
631
+												if (fld[i].v.lstr.len == v.lstr.len) {
632
+													op = strncmp(fld[i].v.lstr.s, v.lstr.s, v.lstr.len);
633
+													if (op < 0) 
634
+														op = 0x01;
635
+													else if (op > 0)
636
+														op = 0x04;
637
+													else
638
+														op = 0x02;
639
+												}
640
+												else if (fld[i].v.lstr.len < v.lstr.len) {
641
+													op = strncmp(fld[i].v.lstr.s, v.lstr.s, fld[i].v.lstr.len);
642
+													if (op < 0) 
643
+														op = 0x01;
644
+													else 
645
+														op = 0x04;
646
+												}
647
+												else /* if (fld[i].v.lstr.len > v.lstr.len) */ {
648
+													op = strncmp(fld[i].v.lstr.s, v.lstr.s, v.lstr.len);
649
+													if (op > 0) 
650
+														op = 0x04;
651
+													else 
652
+														op = 0x01;
653
+												}
654
+												break;
655
+											case DB_BLOB:
656
+												if (fld[i].v.blob.len == v.blob.len && memcmp(fld[i].v.blob.s, v.blob.s, v.blob.len) == 0)
657
+													op = 0x02;
658
+												break;											
659
+											case DB_INT:
660
+												CMP_NUM(fld[i].v, v, int4);
661
+												break; 
662
+											case DB_BITMAP:
663
+												CMP_NUM(fld[i].v, v, bitmap);
664
+												break; 
665
+											case DB_DATETIME:
666
+												CMP_NUM(fld[i].v, v, time);
667
+												break; 
668
+											case DB_FLOAT:
669
+												CMP_NUM(fld[i].v, v, flt);
670
+												break; 
671
+											case DB_DOUBLE:
672
+												CMP_NUM(fld[i].v, v, dbl);
673
+												break; 
674
+											default:
675
+												;
676
+										}
677
+									}
678
+									 
679
+								}
680
+							}
681
+							switch (lfld->filter[j]->op) {
682
+								case DB_EQ:
683
+									passed = op == 0x02;
684
+									break;
685
+								case DB_NE:
686
+									passed = (op & 0x02) == 0;
687
+									break;
688
+								case DB_LT:
689
+									passed = op == 0x01;
690
+									break;
691
+								case DB_LEQ:
692
+									passed = op == 0x01 || op == 0x02;