summaryrefslogtreecommitdiffstats
path: root/doc/dev_queue.html
blob: bf2af7f0f5f19236bc5977b42d232a21144b7eb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<html>
<head>
<title>rsyslog queue object</title>
</head>
<body>
<h1>The rsyslog queue object</h1>
<p>This page reflects the status as of 2008-01-17. The documentation is still incomplete.
Target audience is developers and users who would like to get an in-depth understanding of
queues as used in <a href="http://www.rsyslog.com/">rsyslog</a>.</p>
<p><b>Please note that this document is outdated and does not longer reflect the 
specifics of the queue object. However, I have decided to leave it in the doc 
set, as the overall picture provided still is quite OK. I intend to update this 
document somewhat later when I have reached the &quot;store-and-forward&quot; milestone.</b></p>
<h1>Some definitions</h1>
<p>A queue is DA-enabled if it is configured to use disk-assisted mode when 
there is need to. A queue is in DA mode (or DA run mode), when it actually runs 
disk assisted.</p>
<h1>Implementation Details</h1>
<h2>Disk-Assisted Mode</h2>
<p>Memory-Type queues may utilize disk-assisted (DA) mode. DA mode is enabled 
whenever a queue file name prefix is provided. This is called DA-enabled mode. 
If DA-enabled, the queue operates as a regular memory queue until a high water 
mark is reached. If that happens, the queue activates disk assistance (called 
&quot;runs disk assisted&quot; or &quot;runs DA&quot; - you can find that often in source file 
comments). To do so, it creates a helper queue instance (the DA queue). At that 
point, there are two queues running - the primary queue's consumer changes to a 
shuffle-to-DA-queue consumer and the original primary consumer is assigned to 
the DA queue. Existing and new messages are spooled to the disk queue, where the 
DA worker takes them from and passes them for execution to the actual consumer. 
In essence, the primary queue has now become a memory buffer for the DA queue. 
The primary queue will be drained until a low water mark is reached. At that 
point, processing is held. New messages enqueued to the primary queue will not 
be processed but kept in memory. Processing resumes when either the high water 
mark is reached again or the DA queue indicates it is empty. If the DA queue is 
empty, it is shut down and processing of the primary queue continues as a 
regular in-memory queue (aka &quot;DA mode is shut down&quot;). The whole thing iterates 
once the high water mark is hit again.</p>
<p>There is one special case: if the primary queue is shut down and could not 
finish processing all messages within the configured timeout periods, the DA 
queue is instantiated to take up the remaining messages. These will be preserved 
and be processed during the next run. During that period, the DA queue runs in 
&quot;enqueue-only&quot; mode and does not execute any consumer. Draining the primary 
queue is typically very fast. If that behaviour is not desired, it can be turned 
of via parameters. In that case, any remaining in-memory messages are lost.</p>
<p>Due to the fact that when running DA two queues work closely together and 
worker threads (including the DA worker) may shut down at any time (due to 
timeout), processing synchronization and startup and shutdown is somewhat 
complex. I'll outline the exact conditions and steps down here. I also do this 
so that I know clearly what to develop to, so please be patient if the 
information is a bit too in-depth ;)</p>
<h2>DA Run Mode Initialization</h2>
<p>Three cases:</p>
<ol>
	<li>any time during queueEnqObj() when the high water mark is hit</li>
	<li>at queue startup if there is an on-disk queue present (presence of QI 
	file indicates presence of queue data)</li>
	<li>at queue shutdown if remaining in-memory data needs to be persisted to 
	disk</li>
</ol>
<p>In <b>case 1</b>, the worker pool is running. When switching to DA mode, all 
regular workers are sent termination commands. The DA worker is initiated. 
Regular workers may run in parallel to the DA worker until they terminate. 
Regular workers shall terminate as soon as their current consumer has completed. 
They shall not execute the DA consumer.</p>
<p>In <b>case 2</b>, the worker pool is not yet running and is NOT started. The 
DA worker is initiated.</p>
<p>In <b>case 3</b>, the worker pool is already shut down. The DA worker is 
initiated. The DA queue runs in enqueue-only mode.</p>
<p>In all cases, the DA worker starts up and checks if DA mode is already fully 
initialized. If not, it initializes it, what most importantly means construction 
of the queue.</p>
<p>Then, regular worker processing is carried out. That is, the queue worker 
will wait on empty queue and terminate after an timeout. However, If any message 
is received, the DA consumer is executed. That consumer checks the low water 
mark. If the low water mark is reached, it stops processing until either the 
high water mark is reached again or the DA queue indicates it is empty (there is 
a pthread_cond_t for this synchronization).</p>
<p>In theory, a <b>case-2</b> startup could lead to the worker becoming inactive 
and terminating while waiting on the primary queue to fill. In practice, this is 
highly unlikely (but only for the main message queue) because rsyslog issues a 
startup message. HOWEVER, we can not rely on that, it would introduce a race. If 
the primary rsyslog thread (the one that issues the message) is scheduled very 
late and there is a low inactivty timeout for queue workers, the queue worker 
may terminate before the startup message is issued. And if the on-disk queue 
holds only a few messages, it may become empty before the DA worker is 
re-initiated again. So it is possible that the DA run mode termination criteria 
occurs while no DA worker is running on the primary queue.</p>
<p>In cases 1 and 3, the DA worker can never become inactive without hitting the 
DA shutdown criteria. In <b>case 1</b>, it either shuffles messages from the 
primary to the DA queue or it waits because it has the hit low water mark. </p>
<p>In <b>case 3</b>, it always shuffles messages between the queues (because, 
that's the sole purpose of that run). In order for this to happen, the high 
water mark has been set to the value of 1 when DA run mode has been initialized. 
This ensures that the regular logic can be applied to drain the primary queue. 
To prevent a hold due to reaching the low water mark, that mark must be changed 
to 0 before the DA worker starts.</p>
<h2>DA Run Mode Shutdown</h2>
<p>In essence, DA run mode is terminated when the DA queue is empty and the 
primary worker queue size is below the high water mark. It is also terminated 
when the primary queue is shut down. The decision to switch back to regular 
(non-DA) run mode is typically made by the DA worker. If it switches, the DA 
queue is destructed and the regular worker pool is restarted. In some cases, the 
queue shutdown process may initiate the &quot;switch&quot; (in this case more or less a 
clean shutdown of the DA queue).</p>
<p>One might think that it would be more natural for the DA queue to detect 
being idle and shut down itself. However, there are some issues associated with 
that. Most importantly, all queue worker threads need to be shut down during 
queue destruction. Only after that has happend, final destruction steps can 
happen (else we would have a myriad of races). However, it is the DA queues 
worker thread that detects it is empty (empty queue detection always happens at 
the consumer side and must so). That would lead to the DA queue worker thread to 
initiate DA queue destruction which in turn would lead to that very same thread 
being canceled (because workers must shut down before the queue can be 
destructed). Obviously, this does not work out (and I didn't even mention the 
other issues - so let's forget about it). As such, the thread that enqueues 
messages must destruct the queue - and that is the primary queue's DA worker 
thread.</p>
<p>There are some subleties due to thread synchronization and the fact that the 
DA consumer may not be running (in a <b>case-2 startup</b>). So it is not 
trivial to reliably change the queue back from DA run mode to regular run mode. 
The priority is a clean switch. We accept the fact that there may be situations 
where we cleanly shut down DA run mode, just to re-enable it with the very next 
message being enqueued. While unlikely, this will happen from time to time and 
is considered perfectly legal. We can't predict the future and it would 
introduce too great complexity to try to do something against that (that would 
most probably even lead to worse performance under regular conditions).</p>
<p>The primary queue's DA worker thread may wait at two different places:</p>
<ol>
	<li>after reaching the low water mark and waiting for either high water or 
	DA queue empty</li>
	<li>at the regular pthread_cond_wait() on an empty primary queue</li>
</ol>
<p>Case 2 is unlikely, but may happen (see info above on a case 2 startup).</p>
<p><b>The DA worker may also not wait at all,</b> because it is actively 
executing and shuffeling messages between the queues. In that case, however, the 
program flow passes both of the two wait conditions but simply does not wait.</p>
<p><b>Finally, the DA worker may be inactive </b>(again, with a case-2 startup). 
In that case no work(er) at all is executed. Most importantly, without the DA 
worker being active, nobody will ever detect the need to change back to regular 
mode. If we have this situation, the very next message enqueued will cause the 
switch, because then the DA run mode shutdown criteria is met. However, it may 
take close to eternal for this message to arrive. During that time, disk and 
memory resources for the DA queue remain allocated. This also leaves processing 
in a sub-optimal state and it may take longer than necessary to switch back to 
regular queue mode when a message burst happens. In extreme cases, this could 
even lead to shutdown of DA run mode, which takes so long that the high water 
mark is passed and DA run mode is immediately re-initialized - while with an 
immediate switch, the message burst may have been able to be processed by the 
in-memory queue without DA support.</p>
<p>So in short, it is desirable switch to regular run mode as soon as possible. 
To do this, we need an active DA worker. The easy solution is to initiate DA 
worker startup from the DA queue's worker once it detects empty condition. To do 
so, the DA queue's worker must call into a &quot;<i>DA worker startup initiation</i>&quot; 
routine inside the main queue. As a reminder, the DA worker will most probably 
not receive the &quot;DA queue empty&quot; signal in that case, because it will be long 
sent (in most cases) before the DA worker even waits for it. So <b>it is vital 
that DA run mode termination checks be done in the DA worker before it goes into 
any wait condition</b>.</p>
<p>Please note that the &quot;<i>DA worker startup initiation</i>&quot; routine may be 
called concurrently from multiple initiators. <b>To prevent a race, it must be 
guarded by the queue mutex </b>and return without any action (and no error 
code!) if the DA worker is already initiated.</p>
<p>All other cases can be handled by checking the termination criteria 
immediately at the start of the worker and then once again for each run. The 
logic follows this simplified flow diagram:</p>
<p align="center"><a href="queueWorkerLogic.jpg">
<img border="0" src="queueWorkerLogic_small.jpg" width="431" height="605"></a></p>
<p>Some of the more subtle aspects of worker processing (e.g. enqueue thread 
signaling and other fine things) have been left out in order to get the big 
picture. What is called &quot;check DA mode switchback...&quot; right after &quot;worker init&quot; 
is actually a check for the worker's termination criteria. Typically, <b>the 
worker termination criteria is a shutdown request</b>. However, <b>for a DA 
worker, termination is also requested if the queue size is below the high water 
mark AND the DA queue is empty</b>. There is also a third termination criteria 
and it is not even on the chart: that is the inactivity timeout, which exists in 
all modes. Note that while the inactivity timeout shuts down a thread, it 
logically does not terminate the worker pool (or DA worker): workers are 
restarted on an as-needed basis. However, inactivity timeouts are very important 
because they require us to restart workers in some situations where we may 
expect a running one. So always keep them on your mind.</p>
<h2>Queue Destruction</h2>
<p>Now let's consider <b>the case of destruction of the primary queue. </b>During 
destruction, our focus is on loosing as few messages as possible. If the 
queue is not DA-enabled, there is nothing but the configured timeouts to handle 
that situation. However, with a DA-enabled queue there are more options.</p>
<p>If the queue is DA-enabled, it may be <i>configured to persist messages to 
disk before it is terminated</i>. In that case, loss of messages never occurs 
(at the price of a potentially lengthy shutdown). Even if that setting is not 
applied, the queue should drain as many messages as possible to the disk. For 
that reason, it makes no sense to wait on a low water mark. Also, if the queue 
is already in DA run mode, it does not make any sense to switch back to regular 
run mode during termination and then try to process some messages via the 
regular consumer. It is much more appropriate the try completely drain the queue 
during the remaining timeout period. For the same reason, it is preferred that 
no new consumers be activated (via the DA queue's worker), as they only cost 
valuable CPU cycles and, more importantly, would potentially be long(er)-running 
and possibly be needed to be cancelled. To prevent all of that, <b>queue 
parameters are changed for DA-enabled queues:</b> the high water mark is to 1 
and the low water mark to 0 on the primary queue. The DA queue is commanded to 
run in enqueue-only mode. If the primary queue is <i>configured to persist 
messages to disk before it is terminated</i>, its SHUTDOWN timeout is changed to 
to eternal. These parameters will cause the queue to drain as much as possible 
to disk (and they may cause a case 3 DA run mode initiation). Please note that 
once the primary queue has been drained, the DA queue's worker will 
automatically switch back to regular (non-DA) run mode. <b>It must be ensured 
that no worker cancellation occurs during that switchback</b>. Please note that 
the queue may not switch back to regular run mode if it is not <i>configured to 
persist messages to disk before it is terminated</i>. In order to apply the new 
parameters, <b>worker threads must be awakened.</b> Remember we may not be in DA 
run mode at this stage. In that case, the regular workers must be awakened, which 
then will switch to DA run mode. No worker may be active, in that case one must 
be initiated. If in DA run mode and the DA worker is inactive, the&nbsp; &quot;<i>DA 
worker startup initiation</i>&quot; must be called to activate it. That routine 
ensures only one DA worker is started even with multiple concurrent callers - 
this may be the case here. The DA queue's worker may have requested DA worker 
startup in order to terminate on empty queue (which will probably not be honored 
as we have changed the low water mark).</p>
<p>After all this is done, the queue destructor requests termination of the 
queue's worker threads. It will use the normal timeouts and potentially cancel 
too-long running worker threads. <b>The shutdown process must ensure that all 
workers reach running state before they are commanded to terminate</b>. 
Otherwise it may run into a race condition that could lead to a false shutdown 
with workers running asynchronously. As a few workers may have just been started 
to initialize (to apply new parameter settings), the probability for this race 
condition is extremely high, especially on single-CPU systems.</p>
<p>After all workers have been shut down (or cancelled), the queue may still be 
in DA run mode. If so, this must be terminated, which now can simply be done by 
destructing the DA queue object. This is not a real switchback to regular run 
mode, but that doesn't matter because the queue object will soon be gone away.</p>
<p>Finally, the queue is mostly shut down and ready to be actually destructed. 
As a last try, the queuePersists() entry point is called. It is used to persists 
a non-DA-enabled queue in whatever way is possible for that queue. There may be 
no implementation for the specific queue type. Please note that this is not just 
a theoretical construct. This is an extremely important code path when the DA 
queue itself is destructed. Remember that it is a queue object in its own right. 
The DA queue is obviously not DA-enabled, so it calls into queuePersists() 
during its destruction - this is what enables us to persist the disk queue!</p>
<p>After that point, left over queue resources (mutexes, dynamic memory, ...) 
are freed and the queue object is actually destructed.</p>
<h2>Copyright</h2>
<p>Copyright (c) 2008 <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> 
and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p>
<p>Permission is granted to copy, distribute and/or modify this document under 
the terms of the GNU Free Documentation License, Version 1.2 or any later 
version published by the Free Software Foundation; with no Invariant Sections, 
no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be 
viewed at <a href="http://www.gnu.org/copyleft/fdl.html">
http://www.gnu.org/copyleft/fdl.html</a>.</p>
</body>
</html>
ref='#n1006'>1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875
/* 
   Unix SMB/CIFS implementation.
   LDAP protocol helper functions for SAMBA
   Copyright (C) Jean François Micouleau	1998
   Copyright (C) Gerald Carter			2001-2003
   Copyright (C) Shahms King			2001
   Copyright (C) Andrew Bartlett		2002-2003
   Copyright (C) Stefan (metze) Metzmacher	2002-2003

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "includes.h"
#include "smbldap.h"
#include "../libcli/security/security.h"
#include <tevent.h>
#include "lib/param/loadparm.h"

/* Try not to hit the up or down server forever */

#define SMBLDAP_DONT_PING_TIME 10	/* ping only all 10 seconds */
#define SMBLDAP_NUM_RETRIES 8	        /* retry only 8 times */

#define SMBLDAP_IDLE_TIME 150		/* After 2.5 minutes disconnect */


/*******************************************************************
 Search an attribute and return the first value found.
******************************************************************/

 bool smbldap_get_single_attribute (LDAP * ldap_struct, LDAPMessage * entry,
				    const char *attribute, char *value,
				    int max_len)
{
	char **values;
	size_t size = 0;

	if ( !attribute )
		return False;

	value[0] = '\0';

	if ((values = ldap_get_values (ldap_struct, entry, attribute)) == NULL) {
		DEBUG (10, ("smbldap_get_single_attribute: [%s] = [<does not exist>]\n", attribute));

		return False;
	}

	if (!convert_string(CH_UTF8, CH_UNIX,values[0], -1, value, max_len, &size)) {
		DEBUG(1, ("smbldap_get_single_attribute: string conversion of [%s] = [%s] failed!\n", 
			  attribute, values[0]));
		ldap_value_free(values);
		return False;
	}

	ldap_value_free(values);
#ifdef DEBUG_PASSWORDS
	DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n", attribute, value));
#endif	
	return True;
}

 char * smbldap_talloc_single_attribute(LDAP *ldap_struct, LDAPMessage *entry,
					const char *attribute,
					TALLOC_CTX *mem_ctx)
{
	char **values;
	char *result;
	size_t converted_size;

	if (attribute == NULL) {
		return NULL;
	}

	values = ldap_get_values(ldap_struct, entry, attribute);

	if (values == NULL) {
		DEBUG(10, ("attribute %s does not exist\n", attribute));
		return NULL;
	}

	if (ldap_count_values(values) != 1) {
		DEBUG(10, ("attribute %s has %d values, expected only one\n",
			   attribute, ldap_count_values(values)));
		ldap_value_free(values);
		return NULL;
	}

	if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
		DEBUG(10, ("pull_utf8_talloc failed\n"));
		ldap_value_free(values);
		return NULL;
	}

	ldap_value_free(values);

#ifdef DEBUG_PASSWORDS
	DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n",
		     attribute, result));
#endif	
	return result;
}

 char * smbldap_talloc_first_attribute(LDAP *ldap_struct, LDAPMessage *entry,
				       const char *attribute,
				       TALLOC_CTX *mem_ctx)
{
	char **values;
	char *result;
	size_t converted_size;

	if (attribute == NULL) {
		return NULL;
	}

	values = ldap_get_values(ldap_struct, entry, attribute);

	if (values == NULL) {
		DEBUG(10, ("attribute %s does not exist\n", attribute));
		return NULL;
	}

	if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
		DEBUG(10, ("pull_utf8_talloc failed\n"));
		ldap_value_free(values);
		return NULL;
	}

	ldap_value_free(values);

#ifdef DEBUG_PASSWORDS
	DEBUG (100, ("smbldap_get_first_attribute: [%s] = [%s]\n",
		     attribute, result));
#endif
	return result;
}

 char * smbldap_talloc_smallest_attribute(LDAP *ldap_struct, LDAPMessage *entry,
					  const char *attribute,
					  TALLOC_CTX *mem_ctx)
{
	char **values;
	char *result;
	size_t converted_size;
	int i, num_values;

	if (attribute == NULL) {
		return NULL;
	}

	values = ldap_get_values(ldap_struct, entry, attribute);

	if (values == NULL) {
		DEBUG(10, ("attribute %s does not exist\n", attribute));
		return NULL;
	}

	if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
		DEBUG(10, ("pull_utf8_talloc failed\n"));
		ldap_value_free(values);
		return NULL;
	}

	num_values = ldap_count_values(values);

	for (i=1; i<num_values; i++) {
		char *tmp;

		if (!pull_utf8_talloc(mem_ctx, &tmp, values[i],
				      &converted_size)) {
			DEBUG(10, ("pull_utf8_talloc failed\n"));
			TALLOC_FREE(result);
			ldap_value_free(values);
			return NULL;
		}

		if (strcasecmp_m(tmp, result) < 0) {
			TALLOC_FREE(result);
			result = tmp;
		} else {
			TALLOC_FREE(tmp);
		}
	}

	ldap_value_free(values);

#ifdef DEBUG_PASSWORDS
	DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n",
		     attribute, result));
#endif
	return result;
}

 bool smbldap_talloc_single_blob(TALLOC_CTX *mem_ctx, LDAP *ld,
				 LDAPMessage *msg, const char *attrib,
				 DATA_BLOB *blob)
{
	struct berval **values;

	values = ldap_get_values_len(ld, msg, attrib);
	if (!values) {
		return false;
	}

	if (ldap_count_values_len(values) != 1) {
		DEBUG(10, ("Expected one value for %s, got %d\n", attrib,
			   ldap_count_values_len(values)));
		return false;
	}

	*blob = data_blob_talloc(mem_ctx, values[0]->bv_val,
				 values[0]->bv_len);
	ldap_value_free_len(values);

	return (blob->data != NULL);
}

 bool smbldap_pull_sid(LDAP *ld, LDAPMessage *msg, const char *attrib,
		       struct dom_sid *sid)
{
	DATA_BLOB blob;
	bool ret;

	if (!smbldap_talloc_single_blob(talloc_tos(), ld, msg, attrib,
					&blob)) {
		return false;
	}
	ret = sid_parse((char *)blob.data, blob.length, sid);
	TALLOC_FREE(blob.data);
	return ret;
}

 static int ldapmsg_destructor(LDAPMessage **result) {
	ldap_msgfree(*result);
	return 0;
}

 void smbldap_talloc_autofree_ldapmsg(TALLOC_CTX *mem_ctx, LDAPMessage *result)
{
	LDAPMessage **handle;

	if (result == NULL) {
		return;
	}

	handle = talloc(mem_ctx, LDAPMessage *);
	SMB_ASSERT(handle != NULL);

	*handle = result;
	talloc_set_destructor(handle, ldapmsg_destructor);
}

 static int ldapmod_destructor(LDAPMod ***mod) {
	ldap_mods_free(*mod, True);
	return 0;
}

 void smbldap_talloc_autofree_ldapmod(TALLOC_CTX *mem_ctx, LDAPMod **mod)
{
	LDAPMod ***handle;

	if (mod == NULL) {
		return;
	}

	handle = talloc(mem_ctx, LDAPMod **);
	SMB_ASSERT(handle != NULL);

	*handle = mod;
	talloc_set_destructor(handle, ldapmod_destructor);
}

/************************************************************************
 Routine to manage the LDAPMod structure array
 manage memory used by the array, by each struct, and values
 ***********************************************************************/

static void smbldap_set_mod_internal(LDAPMod *** modlist, int modop, const char *attribute, const char *value, const DATA_BLOB *blob)
{
	LDAPMod **mods;
	int i;
	int j;

	mods = *modlist;

	/* sanity checks on the mod values */

	if (attribute == NULL || *attribute == '\0') {
		return;	
	}

#if 0	/* commented out after discussion with abartlet.  Do not reenable.
	   left here so other do not re-add similar code   --jerry */
       	if (value == NULL || *value == '\0')
		return;
#endif

	if (mods == NULL) {
		mods = SMB_MALLOC_P(LDAPMod *);
		if (mods == NULL) {
			smb_panic("smbldap_set_mod: out of memory!");
			/* notreached. */
		}
		mods[0] = NULL;
	}

	for (i = 0; mods[i] != NULL; ++i) {
		if (mods[i]->mod_op == modop && strequal(mods[i]->mod_type, attribute))
			break;
	}

	if (mods[i] == NULL) {
		mods = SMB_REALLOC_ARRAY (mods, LDAPMod *, i + 2);
		if (mods == NULL) {
			smb_panic("smbldap_set_mod: out of memory!");
			/* notreached. */
		}
		mods[i] = SMB_MALLOC_P(LDAPMod);
		if (mods[i] == NULL) {
			smb_panic("smbldap_set_mod: out of memory!");
			/* notreached. */
		}
		mods[i]->mod_op = modop;
		mods[i]->mod_values = NULL;
		mods[i]->mod_type = SMB_STRDUP(attribute);
		mods[i + 1] = NULL;
	}

	if (blob && (modop & LDAP_MOD_BVALUES)) {
		j = 0;
		if (mods[i]->mod_bvalues != NULL) {
			for (; mods[i]->mod_bvalues[j] != NULL; j++);
		}
		mods[i]->mod_bvalues = SMB_REALLOC_ARRAY(mods[i]->mod_bvalues, struct berval *, j + 2);

		if (mods[i]->mod_bvalues == NULL) {
			smb_panic("smbldap_set_mod: out of memory!");
			/* notreached. */
		}

		mods[i]->mod_bvalues[j] = SMB_MALLOC_P(struct berval);
		SMB_ASSERT(mods[i]->mod_bvalues[j] != NULL);

		mods[i]->mod_bvalues[j]->bv_val = (char *)smb_memdup(blob->data, blob->length);
		SMB_ASSERT(mods[i]->mod_bvalues[j]->bv_val != NULL);
		mods[i]->mod_bvalues[j]->bv_len = blob->length;

		mods[i]->mod_bvalues[j + 1] = NULL;
	} else if (value != NULL) {
		char *utf8_value = NULL;
		size_t converted_size;

		j = 0;
		if (mods[i]->mod_values != NULL) {
			for (; mods[i]->mod_values[j] != NULL; j++);
		}
		mods[i]->mod_values = SMB_REALLOC_ARRAY(mods[i]->mod_values, char *, j + 2);

		if (mods[i]->mod_values == NULL) {
			smb_panic("smbldap_set_mod: out of memory!");
			/* notreached. */
		}

		if (!push_utf8_talloc(talloc_tos(), &utf8_value, value, &converted_size)) {
			smb_panic("smbldap_set_mod: String conversion failure!");
			/* notreached. */
		}

		mods[i]->mod_values[j] = SMB_STRDUP(utf8_value);
		TALLOC_FREE(utf8_value);
		SMB_ASSERT(mods[i]->mod_values[j] != NULL);

		mods[i]->mod_values[j + 1] = NULL;
	}
	*modlist = mods;
}

 void smbldap_set_mod (LDAPMod *** modlist, int modop, const char *attribute, const char *value)
{
	smbldap_set_mod_internal(modlist, modop, attribute, value, NULL);
}

 void smbldap_set_mod_blob(LDAPMod *** modlist, int modop, const char *attribute, const DATA_BLOB *value)
{
	smbldap_set_mod_internal(modlist, modop | LDAP_MOD_BVALUES, attribute, NULL, value);
}

/**********************************************************************
  Set attribute to newval in LDAP, regardless of what value the
  attribute had in LDAP before.
*********************************************************************/

static void smbldap_make_mod_internal(LDAP *ldap_struct, LDAPMessage *existing,
				      LDAPMod ***mods,
				      const char *attribute, int op,
				      const char *newval,
				      const DATA_BLOB *newblob)
{
	char oldval[2048]; /* current largest allowed value is mungeddial */
	bool existed;
	DATA_BLOB oldblob = data_blob_null;

	if (existing != NULL) {
		if (op & LDAP_MOD_BVALUES) {
			existed = smbldap_talloc_single_blob(talloc_tos(), ldap_struct, existing, attribute, &oldblob);
		} else {
			existed = smbldap_get_single_attribute(ldap_struct, existing, attribute, oldval, sizeof(oldval));
		}
	} else {
		existed = False;
		*oldval = '\0';
	}

	if (existed) {
		bool equal = false;
		if (op & LDAP_MOD_BVALUES) {
			equal = (newblob && (data_blob_cmp(&oldblob, newblob) == 0));
		} else {
			/* all of our string attributes are case insensitive */
			equal = (newval && (strcasecmp_m(oldval, newval) == 0));
		}

		if (equal) {
			/* Believe it or not, but LDAP will deny a delete and
			   an add at the same time if the values are the
			   same... */
			DEBUG(10,("smbldap_make_mod: attribute |%s| not changed.\n", attribute));
			return;
		}

		/* There has been no value before, so don't delete it.
		 * Here's a possible race: We might end up with
		 * duplicate attributes */
		/* By deleting exactly the value we found in the entry this
		 * should be race-free in the sense that the LDAP-Server will
		 * deny the complete operation if somebody changed the
		 * attribute behind our back. */
		/* This will also allow modifying single valued attributes 
		 * in Novell NDS. In NDS you have to first remove attribute and then
		 * you could add new value */

		if (op & LDAP_MOD_BVALUES) {
			DEBUG(10,("smbldap_make_mod: deleting attribute |%s| blob\n", attribute));
			smbldap_set_mod_blob(mods, LDAP_MOD_DELETE, attribute, &oldblob);
		} else {
			DEBUG(10,("smbldap_make_mod: deleting attribute |%s| values |%s|\n", attribute, oldval));
			smbldap_set_mod(mods, LDAP_MOD_DELETE, attribute, oldval);
		}
	}

	/* Regardless of the real operation (add or modify)
	   we add the new value here. We rely on deleting
	   the old value, should it exist. */

	if (op & LDAP_MOD_BVALUES) {
		if (newblob && newblob->length) {
			DEBUG(10,("smbldap_make_mod: adding attribute |%s| blob\n", attribute));
			smbldap_set_mod_blob(mods, LDAP_MOD_ADD, attribute, newblob);
		}
	} else {
		if ((newval != NULL) && (strlen(newval) > 0)) {
			DEBUG(10,("smbldap_make_mod: adding attribute |%s| value |%s|\n", attribute, newval));
			smbldap_set_mod(mods, LDAP_MOD_ADD, attribute, newval);
		}
	}
}

 void smbldap_make_mod(LDAP *ldap_struct, LDAPMessage *existing,
		      LDAPMod ***mods,
		      const char *attribute, const char *newval)
{
	smbldap_make_mod_internal(ldap_struct, existing, mods, attribute,
				  0, newval, NULL);
}

 void smbldap_make_mod_blob(LDAP *ldap_struct, LDAPMessage *existing,
			    LDAPMod ***mods,
			    const char *attribute, const DATA_BLOB *newblob)
{
	smbldap_make_mod_internal(ldap_struct, existing, mods, attribute,
				  LDAP_MOD_BVALUES, NULL, newblob);
}

/**********************************************************************
 Some varients of the LDAP rebind code do not pass in the third 'arg' 
 pointer to a void*, so we try and work around it by assuming that the 
 value of the 'LDAP *' pointer is the same as the one we had passed in
 **********************************************************************/

struct smbldap_state_lookup {
	LDAP *ld;
	struct smbldap_state *smbldap_state;
	struct smbldap_state_lookup *prev, *next;
};

static struct smbldap_state_lookup *smbldap_state_lookup_list;

static struct smbldap_state *smbldap_find_state(LDAP *ld) 
{
	struct smbldap_state_lookup *t;

	for (t = smbldap_state_lookup_list; t; t = t->next) {
		if (t->ld == ld) {
			return t->smbldap_state;
		}
	}
	return NULL;
}

static void smbldap_delete_state(struct smbldap_state *smbldap_state) 
{
	struct smbldap_state_lookup *t;

	for (t = smbldap_state_lookup_list; t; t = t->next) {
		if (t->smbldap_state == smbldap_state) {
			DLIST_REMOVE(smbldap_state_lookup_list, t);
			SAFE_FREE(t);
			return;
		}
	}
}

static void smbldap_store_state(LDAP *ld, struct smbldap_state *smbldap_state) 
{
	struct smbldap_state *tmp_ldap_state;
	struct smbldap_state_lookup *t;

	if ((tmp_ldap_state = smbldap_find_state(ld))) {
		SMB_ASSERT(tmp_ldap_state == smbldap_state);
		return;
	}

	t = SMB_XMALLOC_P(struct smbldap_state_lookup);
	ZERO_STRUCTP(t);

	DLIST_ADD_END(smbldap_state_lookup_list, t, struct smbldap_state_lookup *);
	t->ld = ld;
	t->smbldap_state = smbldap_state;
}

/********************************************************************
 start TLS on an existing LDAP connection
*******************************************************************/

int smbldap_start_tls(LDAP *ldap_struct, int version)
{ 
#ifdef LDAP_OPT_X_TLS
	int rc;
#endif

	if (lp_ldap_ssl() != LDAP_SSL_START_TLS) {
		return LDAP_SUCCESS;
	}

#ifdef LDAP_OPT_X_TLS
	if (version != LDAP_VERSION3) {
		DEBUG(0, ("Need LDAPv3 for Start TLS\n"));
		return LDAP_OPERATIONS_ERROR;
	}

	if ((rc = ldap_start_tls_s (ldap_struct, NULL, NULL)) != LDAP_SUCCESS)	{
		DEBUG(0,("Failed to issue the StartTLS instruction: %s\n",
			 ldap_err2string(rc)));
		return rc;
	}

	DEBUG (3, ("StartTLS issued: using a TLS connection\n"));
	return LDAP_SUCCESS;
#else
	DEBUG(0,("StartTLS not supported by LDAP client libraries!\n"));
	return LDAP_OPERATIONS_ERROR;
#endif
}

/********************************************************************
 setup a connection to the LDAP server based on a uri
*******************************************************************/

static int smb_ldap_setup_conn(LDAP **ldap_struct, const char *uri)
{
	int rc;

	DEBUG(10, ("smb_ldap_setup_connection: %s\n", uri));

#ifdef HAVE_LDAP_INITIALIZE

	rc = ldap_initialize(ldap_struct, uri);
	if (rc) {
		DEBUG(0, ("ldap_initialize: %s\n", ldap_err2string(rc)));
		return rc;
	}

	if (lp_ldap_follow_referral() != Auto) {
		rc = ldap_set_option(*ldap_struct, LDAP_OPT_REFERRALS,
		     lp_ldap_follow_referral() ? LDAP_OPT_ON : LDAP_OPT_OFF);
		if (rc != LDAP_SUCCESS)
			DEBUG(0, ("Failed to set LDAP_OPT_REFERRALS: %s\n",
				ldap_err2string(rc)));
	}

	return LDAP_SUCCESS;
#else 

	/* Parse the string manually */

	{
		int port = 0;
		fstring protocol;
		fstring host;
		SMB_ASSERT(sizeof(protocol)>10 && sizeof(host)>254);


		/* skip leading "URL:" (if any) */
		if ( strnequal( uri, "URL:", 4 ) ) {
			uri += 4;
		}

		sscanf(uri, "%10[^:]://%254[^:/]:%d", protocol, host, &port);

		if (port == 0) {
			if (strequal(protocol, "ldap")) {
				port = LDAP_PORT;
			} else if (strequal(protocol, "ldaps")) {
				port = LDAPS_PORT;
			} else {
				DEBUG(0, ("unrecognised protocol (%s)!\n", protocol));
			}
		}

		if ((*ldap_struct = ldap_init(host, port)) == NULL)	{
			DEBUG(0, ("ldap_init failed !\n"));
			return LDAP_OPERATIONS_ERROR;
		}

	        if (strequal(protocol, "ldaps")) {
#ifdef LDAP_OPT_X_TLS
			int tls = LDAP_OPT_X_TLS_HARD;
			if (ldap_set_option (*ldap_struct, LDAP_OPT_X_TLS, &tls) != LDAP_SUCCESS)
			{
				DEBUG(0, ("Failed to setup a TLS session\n"));
			}

			DEBUG(3,("LDAPS option set...!\n"));
#else
			DEBUG(0,("smbldap_open_connection: Secure connection not supported by LDAP client libraries!\n"));
			return LDAP_OPERATIONS_ERROR;
#endif /* LDAP_OPT_X_TLS */
		}
	}
#endif /* HAVE_LDAP_INITIALIZE */

	/* now set connection timeout */
#ifdef LDAP_X_OPT_CONNECT_TIMEOUT /* Netscape */
	{
		int ct = lp_ldap_connection_timeout()*1000;
		rc = ldap_set_option(*ldap_struct, LDAP_X_OPT_CONNECT_TIMEOUT, &ct);
		if (rc != LDAP_SUCCESS) {
			DEBUG(0,("Failed to setup an ldap connection timeout %d: %s\n",
				ct, ldap_err2string(rc)));
		}
	}
#elif defined (LDAP_OPT_NETWORK_TIMEOUT) /* OpenLDAP */
	{
		struct timeval ct;
		ct.tv_usec = 0;
		ct.tv_sec = lp_ldap_connection_timeout();
		rc = ldap_set_option(*ldap_struct, LDAP_OPT_NETWORK_TIMEOUT, &ct);
		if (rc != LDAP_SUCCESS) {
			DEBUG(0,("Failed to setup an ldap connection timeout %d: %s\n",
				(int)ct.tv_sec, ldap_err2string(rc)));
		}
	}
#endif

	return LDAP_SUCCESS;
}

/********************************************************************
 try to upgrade to Version 3 LDAP if not already, in either case return current
 version 
 *******************************************************************/

static int smb_ldap_upgrade_conn(LDAP *ldap_struct, int *new_version)
{
	int version;
	int rc;

	/* assume the worst */
	*new_version = LDAP_VERSION2;

	rc = ldap_get_option(ldap_struct, LDAP_OPT_PROTOCOL_VERSION, &version);
	if (rc) {
		return rc;
	}

	if (version == LDAP_VERSION3) {
		*new_version = LDAP_VERSION3;
		return LDAP_SUCCESS;
	}

	/* try upgrade */
	version = LDAP_VERSION3;
	rc = ldap_set_option (ldap_struct, LDAP_OPT_PROTOCOL_VERSION, &version);
	if (rc) {
		return rc;
	}

	*new_version = LDAP_VERSION3;
	return LDAP_SUCCESS;
}

/*******************************************************************
 open a connection to the ldap server (just until the bind)
 ******************************************************************/

int smbldap_setup_full_conn(LDAP **ldap_struct, const char *uri)
{
	int rc, version;

	rc = smb_ldap_setup_conn(ldap_struct, uri);
	if (rc) {
		return rc;
	}

	rc = smb_ldap_upgrade_conn(*ldap_struct, &version);
	if (rc) {
		return rc;
	}

	rc = smbldap_start_tls(*ldap_struct, version);
	if (rc) {
		return rc;
	}

	return LDAP_SUCCESS;
}

/*******************************************************************
 open a connection to the ldap server.
******************************************************************/
static int smbldap_open_connection (struct smbldap_state *ldap_state)

{
	int rc = LDAP_SUCCESS;
	int version;
	int deref;
	LDAP **ldap_struct = &ldap_state->ldap_struct;

	rc = smb_ldap_setup_conn(ldap_struct, ldap_state->uri);
	if (rc) {
		return rc;
	}

	/* Store the LDAP pointer in a lookup list */

	smbldap_store_state(*ldap_struct, ldap_state);

	/* Upgrade to LDAPv3 if possible */

	rc = smb_ldap_upgrade_conn(*ldap_struct, &version);
	if (rc) {
		return rc;
	}

	/* Start TLS if required */

	rc = smbldap_start_tls(*ldap_struct, version);
	if (rc) {
		return rc;
	}

	/* Set alias dereferencing method */
	deref = lp_ldap_deref();
	if (deref != -1) {
		if (ldap_set_option (*ldap_struct, LDAP_OPT_DEREF, &deref) != LDAP_OPT_SUCCESS) {
			DEBUG(1,("smbldap_open_connection: Failed to set dereferencing method: %d\n", deref));
		} else {
			DEBUG(5,("Set dereferencing method: %d\n", deref));
		}
	}

	DEBUG(2, ("smbldap_open_connection: connection opened\n"));
	return rc;
}

/*******************************************************************
 a rebind function for authenticated referrals
 This version takes a void* that we can shove useful stuff in :-)
******************************************************************/
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
#else
static int rebindproc_with_state  (LDAP * ld, char **whop, char **credp, 
				   int *methodp, int freeit, void *arg)
{
	struct smbldap_state *ldap_state = arg;
	struct timespec ts;

	/** @TODO Should we be doing something to check what servers we rebind to?
	    Could we get a referral to a machine that we don't want to give our
	    username and password to? */

	if (freeit) {
		SAFE_FREE(*whop);
		if (*credp) {
			memset(*credp, '\0', strlen(*credp));
		}
		SAFE_FREE(*credp);
	} else {
		DEBUG(5,("rebind_proc_with_state: Rebinding as \"%s\"\n", 
			  ldap_state->bind_dn?ldap_state->bind_dn:"[Anonymous bind]"));

		if (ldap_state->anonymous) {
			*whop = NULL;
			*credp = NULL;
		} else {
			*whop = SMB_STRDUP(ldap_state->bind_dn);
			if (!*whop) {
				return LDAP_NO_MEMORY;
			}
			*credp = SMB_STRDUP(ldap_state->bind_secret);
			if (!*credp) {
				SAFE_FREE(*whop);
				return LDAP_NO_MEMORY;
			}
		}
		*methodp = LDAP_AUTH_SIMPLE;
	}

	clock_gettime_mono(&ts);
	ldap_state->last_rebind = convert_timespec_to_timeval(ts);

	return 0;
}
#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/

/*******************************************************************
 a rebind function for authenticated referrals
 This version takes a void* that we can shove useful stuff in :-)
 and actually does the connection.
******************************************************************/
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
static int rebindproc_connect_with_state (LDAP *ldap_struct, 
					  LDAP_CONST char *url, 
					  ber_tag_t request,
					  ber_int_t msgid, void *arg)
{
	struct smbldap_state *ldap_state =
		(struct smbldap_state *)arg;
	int rc;
	struct timespec ts;
	int version;

	DEBUG(5,("rebindproc_connect_with_state: Rebinding to %s as \"%s\"\n", 
		 url, ldap_state->bind_dn?ldap_state->bind_dn:"[Anonymous bind]"));

	/* call START_TLS again (ldaps:// is handled by the OpenLDAP library
	 * itself) before rebinding to another LDAP server to avoid to expose
	 * our credentials. At least *try* to secure the connection - Guenther */

	smb_ldap_upgrade_conn(ldap_struct, &version);
	smbldap_start_tls(ldap_struct, version);

	/** @TODO Should we be doing something to check what servers we rebind to?
	    Could we get a referral to a machine that we don't want to give our
	    username and password to? */

	rc = ldap_simple_bind_s(ldap_struct, ldap_state->bind_dn, ldap_state->bind_secret);

	/* only set the last rebind timestamp when we did rebind after a
	 * non-read LDAP operation. That way we avoid the replication sleep
	 * after a simple redirected search operation - Guenther */

	switch (request) {

		case LDAP_REQ_MODIFY:
		case LDAP_REQ_ADD:
		case LDAP_REQ_DELETE:
		case LDAP_REQ_MODDN:
		case LDAP_REQ_EXTENDED:
			DEBUG(10,("rebindproc_connect_with_state: "
				"setting last_rebind timestamp "
				"(req: 0x%02x)\n", (unsigned int)request));
			clock_gettime_mono(&ts);
			ldap_state->last_rebind = convert_timespec_to_timeval(ts);
			break;
		default:
			ZERO_STRUCT(ldap_state->last_rebind);
			break;
	}

	return rc;
}
#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/

/*******************************************************************
 Add a rebind function for authenticated referrals
******************************************************************/
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
#else
# if LDAP_SET_REBIND_PROC_ARGS == 2
static int rebindproc (LDAP *ldap_struct, char **whop, char **credp,
		       int *method, int freeit )
{
	struct smbldap_state *ldap_state = smbldap_find_state(ldap_struct);

	return rebindproc_with_state(ldap_struct, whop, credp,
				     method, freeit, ldap_state);
}
# endif /*LDAP_SET_REBIND_PROC_ARGS == 2*/
#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/

/*******************************************************************
 a rebind function for authenticated referrals
 this also does the connection, but no void*.
******************************************************************/
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
# if LDAP_SET_REBIND_PROC_ARGS == 2
static int rebindproc_connect (LDAP * ld, LDAP_CONST char *url, int request,
			       ber_int_t msgid)
{
	struct smbldap_state *ldap_state = smbldap_find_state(ld);

	return rebindproc_connect_with_state(ld, url, (ber_tag_t)request, msgid, 
					     ldap_state);
}
# endif /*LDAP_SET_REBIND_PROC_ARGS == 2*/
#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/

/*******************************************************************
 connect to the ldap server under system privilege.
******************************************************************/
static int smbldap_connect_system(struct smbldap_state *ldap_state)
{
	LDAP *ldap_struct = ldap_state->ldap_struct;
	int rc;
	int version;

	/* removed the sasl_bind_s "EXTERNAL" stuff, as my testsuite 
	   (OpenLDAP) doesnt' seem to support it */

	DEBUG(10,("ldap_connect_system: Binding to ldap server %s as \"%s\"\n",
		  ldap_state->uri, ldap_state->bind_dn));

#ifdef HAVE_LDAP_SET_REBIND_PROC
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
# if LDAP_SET_REBIND_PROC_ARGS == 2	
	ldap_set_rebind_proc(ldap_struct, &rebindproc_connect);	
# endif
# if LDAP_SET_REBIND_PROC_ARGS == 3	
	ldap_set_rebind_proc(ldap_struct, &rebindproc_connect_with_state, (void *)ldap_state);	
# endif
#else /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
# if LDAP_SET_REBIND_PROC_ARGS == 2	
	ldap_set_rebind_proc(ldap_struct, &rebindproc);	
# endif
# if LDAP_SET_REBIND_PROC_ARGS == 3	
	ldap_set_rebind_proc(ldap_struct, &rebindproc_with_state, (void *)ldap_state);	
# endif
#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
#endif

	/* When there is an alternative bind callback is set,
	   attempt to use it to perform the bind */
	if (ldap_state->bind_callback != NULL) {
		/* We have to allow bind callback to be run under become_root/unbecome_root
		   to make sure within smbd the callback has proper write access to its resources,
		   like credential cache. This is similar to passdb case where this callback is supposed
		   to be used. When used outside smbd, become_root()/unbecome_root() are no-op.
		*/
		become_root();
		rc = ldap_state->bind_callback(ldap_struct, ldap_state, ldap_state->bind_callback_data);
		unbecome_root();
	} else {
		rc = ldap_simple_bind_s(ldap_struct, ldap_state->bind_dn, ldap_state->bind_secret);
	}

	if (rc != LDAP_SUCCESS) {
		char *ld_error = NULL;
		ldap_get_option(ldap_state->ldap_struct, LDAP_OPT_ERROR_STRING,
				&ld_error);
		DEBUG(ldap_state->num_failures ? 2 : 0,
		      ("failed to bind to server %s with dn=\"%s\" Error: %s\n\t%s\n",
			       ldap_state->uri,
			       ldap_state->bind_dn ? ldap_state->bind_dn : "[Anonymous bind]",
			       ldap_err2string(rc),
			       ld_error ? ld_error : "(unknown)"));
		SAFE_FREE(ld_error);
		ldap_state->num_failures++;
		goto done;
	}

	ldap_state->num_failures = 0;
	ldap_state->paged_results = False;

	ldap_get_option(ldap_state->ldap_struct, LDAP_OPT_PROTOCOL_VERSION, &version);

	if (smbldap_has_control(ldap_state->ldap_struct, ADS_PAGE_CTL_OID) && version == 3) {
		ldap_state->paged_results = True;
	}

	DEBUG(3, ("ldap_connect_system: successful connection to the LDAP server\n"));
	DEBUGADD(10, ("ldap_connect_system: LDAP server %s support paged results\n", 
		ldap_state->paged_results ? "does" : "does not"));
done:
	if (rc != 0) {
		ldap_unbind(ldap_struct);
		ldap_state->ldap_struct = NULL;
	}
	return rc;
}

static void smbldap_idle_fn(struct tevent_context *tevent_ctx,
			    struct tevent_timer *te,
			    struct timeval now_abs,
			    void *private_data);

/**********************************************************************
 Connect to LDAP server (called before every ldap operation)
*********************************************************************/
static int smbldap_open(struct smbldap_state *ldap_state)
{
	int rc, opt_rc;
	bool reopen = False;
	SMB_ASSERT(ldap_state);

	if ((ldap_state->ldap_struct != NULL) && ((ldap_state->last_ping + SMBLDAP_DONT_PING_TIME) < time_mono(NULL))) {

#ifdef HAVE_UNIXSOCKET
		struct sockaddr_un addr;
#else
		struct sockaddr addr;
#endif
		socklen_t len = sizeof(addr);
		int sd;

		opt_rc = ldap_get_option(ldap_state->ldap_struct, LDAP_OPT_DESC, &sd);
		if (opt_rc == 0 && (getpeername(sd, (struct sockaddr *) &addr, &len)) < 0 )
			reopen = True;

#ifdef HAVE_UNIXSOCKET
		if (opt_rc == 0 && addr.sun_family == AF_UNIX)
			reopen = True;
#endif
		if (reopen) {
		    	/* the other end has died. reopen. */
		    	ldap_unbind(ldap_state->ldap_struct);
		    	ldap_state->ldap_struct = NULL;
		    	ldap_state->last_ping = (time_t)0;
		} else {
			ldap_state->last_ping = time_mono(NULL);
		} 
    	}

	if (ldap_state->ldap_struct != NULL) {
		DEBUG(11,("smbldap_open: already connected to the LDAP server\n"));
		return LDAP_SUCCESS;
	}

	if ((rc = smbldap_open_connection(ldap_state))) {
		return rc;
	}

	if ((rc = smbldap_connect_system(ldap_state))) {
		return rc;
	}


	ldap_state->last_ping = time_mono(NULL);
	ldap_state->pid = getpid();

	TALLOC_FREE(ldap_state->idle_event);

	if (ldap_state->tevent_context != NULL) {
		ldap_state->idle_event = tevent_add_timer(
			ldap_state->tevent_context, ldap_state,
			timeval_current_ofs(SMBLDAP_IDLE_TIME, 0),
			smbldap_idle_fn, ldap_state);
	}

	DEBUG(4,("The LDAP server is successfully connected\n"));

	return LDAP_SUCCESS;
}

/**********************************************************************
Disconnect from LDAP server 
*********************************************************************/
static NTSTATUS smbldap_close(struct smbldap_state *ldap_state)
{
	if (!ldap_state)
		return NT_STATUS_INVALID_PARAMETER;

	if (ldap_state->ldap_struct != NULL) {
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	smbldap_delete_state(ldap_state);

	TALLOC_FREE(ldap_state->idle_event);

	DEBUG(5,("The connection to the LDAP server was closed\n"));
	/* maybe free the results here --metze */

	return NT_STATUS_OK;
}

static SIG_ATOMIC_T got_alarm;

static void gotalarm_sig(int dummy)
{
	got_alarm = 1;
}

static time_t calc_ldap_abs_endtime(int ldap_to)
{
	if (ldap_to == 0) {
		/* No timeout - don't
		   return a value for
		   the alarm. */
		return (time_t)0;
	}

	/* Make the alarm time one second beyond
	   the timout we're setting for the
	   remote search timeout, to allow that
	   to fire in preference. */

	return time_mono(NULL)+ldap_to+1;
}

static int end_ldap_local_alarm(time_t absolute_endtime, int rc)
{
	if (absolute_endtime) {
		alarm(0);
		CatchSignal(SIGALRM, SIG_IGN);
		if (got_alarm) {
			/* Client timeout error code. */
			got_alarm = 0;
			return LDAP_TIMEOUT;
		}
	}
	return rc;
}

static void setup_ldap_local_alarm(struct smbldap_state *ldap_state, time_t absolute_endtime)
{
	time_t now = time_mono(NULL);

	if (absolute_endtime) {
		got_alarm = 0;
		CatchSignal(SIGALRM, gotalarm_sig);
		alarm(absolute_endtime - now);
	}

	if (ldap_state->pid != getpid()) {
		smbldap_close(ldap_state);
	}
}

static void get_ldap_errs(struct smbldap_state *ldap_state, char **pp_ld_error, int *p_ld_errno)
{
	ldap_get_option(ldap_state->ldap_struct,
			LDAP_OPT_ERROR_NUMBER, p_ld_errno);

	ldap_get_option(ldap_state->ldap_struct,
			LDAP_OPT_ERROR_STRING, pp_ld_error);
}

static int get_cached_ldap_connect(struct smbldap_state *ldap_state, time_t abs_endtime)
{
	int attempts = 0;

	while (1) {
		int rc;
		time_t now;

		now = time_mono(NULL);
		ldap_state->last_use = now;

		if (abs_endtime && now > abs_endtime) {
			smbldap_close(ldap_state);
			return LDAP_TIMEOUT;
		}

		rc = smbldap_open(ldap_state);

		if (rc == LDAP_SUCCESS) {
			return LDAP_SUCCESS;
		}

		attempts++;
		DEBUG(1, ("Connection to LDAP server failed for the "
			"%d try!\n", attempts));

		if (rc == LDAP_INSUFFICIENT_ACCESS) {
			/* The fact that we are non-root or any other
			 * access-denied condition will not change in the next
			 * round of trying */
			return rc;
		}

		if (got_alarm) {
			smbldap_close(ldap_state);
			return LDAP_TIMEOUT;
		}

		smb_msleep(1000);

		if (got_alarm) {
			smbldap_close(ldap_state);
			return LDAP_TIMEOUT;
		}
	}
}

/*********************************************************************
 ********************************************************************/

static int smbldap_search_ext(struct smbldap_state *ldap_state,
			      const char *base, int scope, const char *filter, 
			      const char *attrs[], int attrsonly,
			      LDAPControl **sctrls, LDAPControl **cctrls, 
			      int sizelimit, LDAPMessage **res)
{
	int 		rc = LDAP_SERVER_DOWN;
	char           *utf8_filter;
	int		to = lp_ldap_timeout();
	time_t		abs_endtime = calc_ldap_abs_endtime(to);
	struct		timeval timeout;
	struct		timeval *timeout_ptr = NULL;
	size_t		converted_size;

	SMB_ASSERT(ldap_state);

	DEBUG(5,("smbldap_search_ext: base => [%s], filter => [%s], "
		 "scope => [%d]\n", base, filter, scope));

	if (ldap_state->last_rebind.tv_sec > 0) {
		struct timeval	tval;
		struct timespec ts;
		int64_t	tdiff = 0;
		int		sleep_time = 0;

		clock_gettime_mono(&ts);
		tval = convert_timespec_to_timeval(ts);

		tdiff = usec_time_diff(&tval, &ldap_state->last_rebind);
		tdiff /= 1000; /* Convert to milliseconds. */

		sleep_time = lp_ldap_replication_sleep()-(int)tdiff;
		sleep_time = MIN(sleep_time, MAX_LDAP_REPLICATION_SLEEP_TIME);

		if (sleep_time > 0) {
			/* we wait for the LDAP replication */
			DEBUG(5,("smbldap_search_ext: waiting %d milliseconds "
				 "for LDAP replication.\n",sleep_time));
			smb_msleep(sleep_time);
			DEBUG(5,("smbldap_search_ext: go on!\n"));
		}
		ZERO_STRUCT(ldap_state->last_rebind);
	}

	if (!push_utf8_talloc(talloc_tos(), &utf8_filter, filter, &converted_size)) {
		return LDAP_NO_MEMORY;
	}

	/* Setup remote timeout for the ldap_search_ext_s call. */
	if (to) {
		timeout.tv_sec = to;
		timeout.tv_usec = 0;
		timeout_ptr = &timeout;
	}

	setup_ldap_local_alarm(ldap_state, abs_endtime);

	while (1) {
		char *ld_error = NULL;
		int ld_errno;

		rc = get_cached_ldap_connect(ldap_state, abs_endtime);
		if (rc != LDAP_SUCCESS) {
			break;
		}

		rc = ldap_search_ext_s(ldap_state->ldap_struct, base, scope, 
				       utf8_filter,
				       discard_const_p(char *, attrs),
				       attrsonly, sctrls, cctrls, timeout_ptr,
				       sizelimit, res);
		if (rc == LDAP_SUCCESS) {
			break;
		}

		get_ldap_errs(ldap_state, &ld_error, &ld_errno);

		DEBUG(10, ("Failed search for base: %s, error: %d (%s) "
			   "(%s)\n", base, ld_errno,
			   ldap_err2string(rc),
			   ld_error ? ld_error : "unknown"));
		SAFE_FREE(ld_error);

		if (ld_errno != LDAP_SERVER_DOWN) {
			break;
		}
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	TALLOC_FREE(utf8_filter);
	return end_ldap_local_alarm(abs_endtime, rc);
}

int smbldap_search(struct smbldap_state *ldap_state, 
		   const char *base, int scope, const char *filter, 
		   const char *attrs[], int attrsonly, 
		   LDAPMessage **res)
{
	return smbldap_search_ext(ldap_state, base, scope, filter, attrs,
				  attrsonly, NULL, NULL, LDAP_NO_LIMIT, res);
}

int smbldap_search_paged(struct smbldap_state *ldap_state, 
			 const char *base, int scope, const char *filter, 
			 const char **attrs, int attrsonly, int pagesize,
			 LDAPMessage **res, void **cookie)
{
	LDAPControl     pr;
	LDAPControl 	**rcontrols;
	LDAPControl 	*controls[2] = { NULL, NULL};
	BerElement 	*cookie_be = NULL;
	struct berval 	*cookie_bv = NULL;
	int		tmp = 0, i, rc;
	bool 		critical = True;

	*res = NULL;

	DEBUG(3,("smbldap_search_paged: base => [%s], filter => [%s],"
		 "scope => [%d], pagesize => [%d]\n",
		 base, filter, scope, pagesize));

	cookie_be = ber_alloc_t(LBER_USE_DER);
	if (cookie_be == NULL) {
		DEBUG(0,("smbldap_create_page_control: ber_alloc_t returns "
			 "NULL\n"));
		return LDAP_NO_MEMORY;
	}

	/* construct cookie */
	if (*cookie != NULL) {
		ber_printf(cookie_be, "{iO}", (ber_int_t) pagesize, *cookie);
		ber_bvfree((struct berval *)*cookie); /* don't need it from last time */
		*cookie = NULL;
	} else {
		ber_printf(cookie_be, "{io}", (ber_int_t) pagesize, "", 0);
	}
	ber_flatten(cookie_be, &cookie_bv);

	pr.ldctl_oid = discard_const_p(char, ADS_PAGE_CTL_OID);
	pr.ldctl_iscritical = (char) critical;
	pr.ldctl_value.bv_len = cookie_bv->bv_len;
	pr.ldctl_value.bv_val = cookie_bv->bv_val;

	controls[0] = &pr;
	controls[1] = NULL;

	rc = smbldap_search_ext(ldap_state, base, scope, filter, attrs, 
				 0, controls, NULL, LDAP_NO_LIMIT, res);

	ber_free(cookie_be, 1);
	ber_bvfree(cookie_bv);

	if (rc != 0) {
		DEBUG(3,("smbldap_search_paged: smbldap_search_ext(%s) "
			 "failed with [%s]\n", filter, ldap_err2string(rc)));
		goto done;
	}

	DEBUG(3,("smbldap_search_paged: search was successful\n"));

	rc = ldap_parse_result(ldap_state->ldap_struct, *res, NULL, NULL, 
			       NULL, NULL, &rcontrols,  0);
	if (rc != 0) {
		DEBUG(3,("smbldap_search_paged: ldap_parse_result failed " \
			 "with [%s]\n", ldap_err2string(rc)));
		goto done;
	}

	if (rcontrols == NULL)
		goto done;

	for (i=0; rcontrols[i]; i++) {

		if (strcmp(ADS_PAGE_CTL_OID, rcontrols[i]->ldctl_oid) != 0)
			continue;

		cookie_be = ber_init(&rcontrols[i]->ldctl_value);
		ber_scanf(cookie_be,"{iO}", &tmp, &cookie_bv);
		/* the berval is the cookie, but must be freed when it is all
		   done */
		if (cookie_bv->bv_len)
			*cookie=ber_bvdup(cookie_bv);
		else
			*cookie=NULL;
		ber_bvfree(cookie_bv);
		ber_free(cookie_be, 1);
		break;
	}
	ldap_controls_free(rcontrols);
done:	
	return rc;
}

int smbldap_modify(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[])
{
	int 		rc = LDAP_SERVER_DOWN;
	char           *utf8_dn;
	time_t		abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
	size_t		converted_size;

	SMB_ASSERT(ldap_state);

	DEBUG(5,("smbldap_modify: dn => [%s]\n", dn ));

	if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
		return LDAP_NO_MEMORY;
	}

	setup_ldap_local_alarm(ldap_state, abs_endtime);

	while (1) {
		char *ld_error = NULL;
		int ld_errno;

		rc = get_cached_ldap_connect(ldap_state, abs_endtime);
		if (rc != LDAP_SUCCESS) {
			break;
		}

		rc = ldap_modify_s(ldap_state->ldap_struct, utf8_dn, attrs);
		if (rc == LDAP_SUCCESS) {
			break;
		}

		get_ldap_errs(ldap_state, &ld_error, &ld_errno);

		DEBUG(10, ("Failed to modify dn: %s, error: %d (%s) "
			   "(%s)\n", dn, ld_errno,
			   ldap_err2string(rc),
			   ld_error ? ld_error : "unknown"));
		SAFE_FREE(ld_error);

		if (ld_errno != LDAP_SERVER_DOWN) {
			break;
		}
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	TALLOC_FREE(utf8_dn);
	return end_ldap_local_alarm(abs_endtime, rc);
}

int smbldap_add(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[])
{
	int 		rc = LDAP_SERVER_DOWN;
	char           *utf8_dn;
	time_t		abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
	size_t		converted_size;

	SMB_ASSERT(ldap_state);

	DEBUG(5,("smbldap_add: dn => [%s]\n", dn ));

	if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
		return LDAP_NO_MEMORY;
	}

	setup_ldap_local_alarm(ldap_state, abs_endtime);

	while (1) {
		char *ld_error = NULL;
		int ld_errno;

		rc = get_cached_ldap_connect(ldap_state, abs_endtime);
		if (rc != LDAP_SUCCESS) {
			break;
		}

		rc = ldap_add_s(ldap_state->ldap_struct, utf8_dn, attrs);
		if (rc == LDAP_SUCCESS) {
			break;
		}

		get_ldap_errs(ldap_state, &ld_error, &ld_errno);

		DEBUG(10, ("Failed to add dn: %s, error: %d (%s) "
			   "(%s)\n", dn, ld_errno,
			   ldap_err2string(rc),
			   ld_error ? ld_error : "unknown"));
		SAFE_FREE(ld_error);

		if (ld_errno != LDAP_SERVER_DOWN) {
			break;
		}
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	TALLOC_FREE(utf8_dn);
	return end_ldap_local_alarm(abs_endtime, rc);
}

int smbldap_delete(struct smbldap_state *ldap_state, const char *dn)
{
	int 		rc = LDAP_SERVER_DOWN;
	char           *utf8_dn;
	time_t		abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
	size_t		converted_size;

	SMB_ASSERT(ldap_state);

	DEBUG(5,("smbldap_delete: dn => [%s]\n", dn ));

	if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
		return LDAP_NO_MEMORY;
	}

	setup_ldap_local_alarm(ldap_state, abs_endtime);

	while (1) {
		char *ld_error = NULL;
		int ld_errno;

		rc = get_cached_ldap_connect(ldap_state, abs_endtime);
		if (rc != LDAP_SUCCESS) {
			break;
		}

		rc = ldap_delete_s(ldap_state->ldap_struct, utf8_dn);
		if (rc == LDAP_SUCCESS) {
			break;
		}

		get_ldap_errs(ldap_state, &ld_error, &ld_errno);

		DEBUG(10, ("Failed to delete dn: %s, error: %d (%s) "
			   "(%s)\n", dn, ld_errno,
			   ldap_err2string(rc),
			   ld_error ? ld_error : "unknown"));
		SAFE_FREE(ld_error);

		if (ld_errno != LDAP_SERVER_DOWN) {
			break;
		}
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	TALLOC_FREE(utf8_dn);
	return end_ldap_local_alarm(abs_endtime, rc);
}

int smbldap_extended_operation(struct smbldap_state *ldap_state, 
			       LDAP_CONST char *reqoid, struct berval *reqdata, 
			       LDAPControl **serverctrls, LDAPControl **clientctrls, 
			       char **retoidp, struct berval **retdatap)
{
	int 		rc = LDAP_SERVER_DOWN;
	time_t		abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());

	if (!ldap_state)
		return (-1);

	setup_ldap_local_alarm(ldap_state, abs_endtime);

	while (1) {
		char *ld_error = NULL;
		int ld_errno;

		rc = get_cached_ldap_connect(ldap_state, abs_endtime);
		if (rc != LDAP_SUCCESS) {
			break;
		}

		rc = ldap_extended_operation_s(ldap_state->ldap_struct, reqoid,
					       reqdata, serverctrls,
					       clientctrls, retoidp, retdatap);
		if (rc == LDAP_SUCCESS) {
			break;
		}

		get_ldap_errs(ldap_state, &ld_error, &ld_errno);

		DEBUG(10, ("Extended operation failed with error: "
			   "%d (%s) (%s)\n", ld_errno,
			   ldap_err2string(rc),
			   ld_error ? ld_error : "unknown"));
		SAFE_FREE(ld_error);

		if (ld_errno != LDAP_SERVER_DOWN) {
			break;
		}
		ldap_unbind(ldap_state->ldap_struct);
		ldap_state->ldap_struct = NULL;
	}

	return end_ldap_local_alarm(abs_endtime, rc);
}

/*******************************************************************
 run the search by name.
******************************************************************/
int smbldap_search_suffix (struct smbldap_state *ldap_state,
			   const char *filter, const char **search_attr,
			   LDAPMessage ** result)
{
	return smbldap_search(ldap_state, lp_ldap_suffix(talloc_tos()),
			      LDAP_SCOPE_SUBTREE,
			      filter, search_attr, 0, result);
}

static void smbldap_idle_fn(struct tevent_context *tevent_ctx,
			    struct tevent_timer *te,
			    struct timeval now_abs,
			    void *private_data)
{
	struct smbldap_state *state = (struct smbldap_state *)private_data;

	TALLOC_FREE(state->idle_event);

	if (state->ldap_struct == NULL) {
		DEBUG(10,("ldap connection not connected...\n"));
		return;
	}

	if ((state->last_use+SMBLDAP_IDLE_TIME) > time_mono(NULL)) {
		DEBUG(10,("ldap connection not idle...\n"));