PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/synapse/ldapsend.pas

http://delphistompclient.googlecode.com/
Pascal | 1189 lines | 951 code | 90 blank | 148 comment | 73 complexity | e1c9b3971a605acacac610db6c349987 MD5 | raw file
  1. {==============================================================================|
  2. | Project : Ararat Synapse | 001.006.000 |
  3. |==============================================================================|
  4. | Content: LDAP client |
  5. |==============================================================================|
  6. | Copyright (c)1999-2009, Lukas Gebauer |
  7. | All rights reserved. |
  8. | |
  9. | Redistribution and use in source and binary forms, with or without |
  10. | modification, are permitted provided that the following conditions are met: |
  11. | |
  12. | Redistributions of source code must retain the above copyright notice, this |
  13. | list of conditions and the following disclaimer. |
  14. | |
  15. | Redistributions in binary form must reproduce the above copyright notice, |
  16. | this list of conditions and the following disclaimer in the documentation |
  17. | and/or other materials provided with the distribution. |
  18. | |
  19. | Neither the name of Lukas Gebauer nor the names of its contributors may |
  20. | be used to endorse or promote products derived from this software without |
  21. | specific prior written permission. |
  22. | |
  23. | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
  24. | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
  25. | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
  26. | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
  27. | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
  28. | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
  29. | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
  30. | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  31. | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
  32. | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
  33. | DAMAGE. |
  34. |==============================================================================|
  35. | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
  36. | Portions created by Lukas Gebauer are Copyright (c)2003-2009. |
  37. | All Rights Reserved. |
  38. |==============================================================================|
  39. | Contributor(s): |
  40. |==============================================================================|
  41. | History: see HISTORY.HTM from distribution package |
  42. | (Found at URL: http://www.ararat.cz/synapse/) |
  43. |==============================================================================}
  44. {:@abstract(LDAP client)
  45. Used RFC: RFC-2251, RFC-2254, RFC-2829, RFC-2830
  46. }
  47. {$IFDEF FPC}
  48. {$MODE DELPHI}
  49. {$ENDIF}
  50. {$H+}
  51. unit ldapsend;
  52. interface
  53. uses
  54. SysUtils, Classes,
  55. blcksock, synautil, asn1util, synacode;
  56. const
  57. cLDAPProtocol = '389';
  58. LDAP_ASN1_BIND_REQUEST = $60;
  59. LDAP_ASN1_BIND_RESPONSE = $61;
  60. LDAP_ASN1_UNBIND_REQUEST = $42;
  61. LDAP_ASN1_SEARCH_REQUEST = $63;
  62. LDAP_ASN1_SEARCH_ENTRY = $64;
  63. LDAP_ASN1_SEARCH_DONE = $65;
  64. LDAP_ASN1_SEARCH_REFERENCE = $73;
  65. LDAP_ASN1_MODIFY_REQUEST = $66;
  66. LDAP_ASN1_MODIFY_RESPONSE = $67;
  67. LDAP_ASN1_ADD_REQUEST = $68;
  68. LDAP_ASN1_ADD_RESPONSE = $69;
  69. LDAP_ASN1_DEL_REQUEST = $4A;
  70. LDAP_ASN1_DEL_RESPONSE = $6B;
  71. LDAP_ASN1_MODIFYDN_REQUEST = $6C;
  72. LDAP_ASN1_MODIFYDN_RESPONSE = $6D;
  73. LDAP_ASN1_COMPARE_REQUEST = $6E;
  74. LDAP_ASN1_COMPARE_RESPONSE = $6F;
  75. LDAP_ASN1_ABANDON_REQUEST = $70;
  76. LDAP_ASN1_EXT_REQUEST = $77;
  77. LDAP_ASN1_EXT_RESPONSE = $78;
  78. type
  79. {:@abstract(LDAP attribute with list of their values)
  80. This class holding name of LDAP attribute and list of their values. This is
  81. descendant of TStringList class enhanced by some new properties.}
  82. TLDAPAttribute = class(TStringList)
  83. private
  84. FAttributeName: AnsiString;
  85. FIsBinary: Boolean;
  86. protected
  87. function Get(Index: integer): string; override;
  88. procedure Put(Index: integer; const Value: string); override;
  89. procedure SetAttributeName(Value: AnsiString);
  90. published
  91. {:Name of LDAP attribute.}
  92. property AttributeName: AnsiString read FAttributeName Write SetAttributeName;
  93. {:Return @true when attribute contains binary data.}
  94. property IsBinary: Boolean read FIsBinary;
  95. end;
  96. {:@abstract(List of @link(TLDAPAttribute))
  97. This object can hold list of TLDAPAttribute objects.}
  98. TLDAPAttributeList = class(TObject)
  99. private
  100. FAttributeList: TList;
  101. function GetAttribute(Index: integer): TLDAPAttribute;
  102. public
  103. constructor Create;
  104. destructor Destroy; override;
  105. {:Clear list.}
  106. procedure Clear;
  107. {:Return count of TLDAPAttribute objects in list.}
  108. function Count: integer;
  109. {:Add new TLDAPAttribute object to list.}
  110. function Add: TLDAPAttribute;
  111. {:Delete one TLDAPAttribute object from list.}
  112. procedure Del(Index: integer);
  113. {:Find and return attribute with requested name. Returns nil if not found.}
  114. function Find(AttributeName: AnsiString): TLDAPAttribute;
  115. {:List of TLDAPAttribute objects.}
  116. property Items[Index: Integer]: TLDAPAttribute read GetAttribute; default;
  117. end;
  118. {:@abstract(LDAP result object)
  119. This object can hold LDAP object. (their name and all their attributes with
  120. values)}
  121. TLDAPResult = class(TObject)
  122. private
  123. FObjectName: AnsiString;
  124. FAttributes: TLDAPAttributeList;
  125. public
  126. constructor Create;
  127. destructor Destroy; override;
  128. published
  129. {:Name of this LDAP object.}
  130. property ObjectName: AnsiString read FObjectName write FObjectName;
  131. {:Here is list of object attributes.}
  132. property Attributes: TLDAPAttributeList read FAttributes;
  133. end;
  134. {:@abstract(List of LDAP result objects)
  135. This object can hold list of LDAP objects. (for example result of LDAP SEARCH.)}
  136. TLDAPResultList = class(TObject)
  137. private
  138. FResultList: TList;
  139. function GetResult(Index: integer): TLDAPResult;
  140. public
  141. constructor Create;
  142. destructor Destroy; override;
  143. {:Clear all TLDAPResult objects in list.}
  144. procedure Clear;
  145. {:Return count of TLDAPResult objects in list.}
  146. function Count: integer;
  147. {:Create and add new TLDAPResult object to list.}
  148. function Add: TLDAPResult;
  149. {:List of TLDAPResult objects.}
  150. property Items[Index: Integer]: TLDAPResult read GetResult; default;
  151. end;
  152. {:Define possible operations for LDAP MODIFY operations.}
  153. TLDAPModifyOp = (
  154. MO_Add,
  155. MO_Delete,
  156. MO_Replace
  157. );
  158. {:Specify possible values for search scope.}
  159. TLDAPSearchScope = (
  160. SS_BaseObject,
  161. SS_SingleLevel,
  162. SS_WholeSubtree
  163. );
  164. {:Specify possible values about alias dereferencing.}
  165. TLDAPSearchAliases = (
  166. SA_NeverDeref,
  167. SA_InSearching,
  168. SA_FindingBaseObj,
  169. SA_Always
  170. );
  171. {:@abstract(Implementation of LDAP client)
  172. (version 2 and 3)
  173. Note: Are you missing properties for setting Username and Password? Look to
  174. parent @link(TSynaClient) object!
  175. Are you missing properties for specify server address and port? Look to
  176. parent @link(TSynaClient) too!}
  177. TLDAPSend = class(TSynaClient)
  178. private
  179. FSock: TTCPBlockSocket;
  180. FResultCode: Integer;
  181. FResultString: AnsiString;
  182. FFullResult: AnsiString;
  183. FAutoTLS: Boolean;
  184. FFullSSL: Boolean;
  185. FSeq: integer;
  186. FResponseCode: integer;
  187. FResponseDN: AnsiString;
  188. FReferals: TStringList;
  189. FVersion: integer;
  190. FSearchScope: TLDAPSearchScope;
  191. FSearchAliases: TLDAPSearchAliases;
  192. FSearchSizeLimit: integer;
  193. FSearchTimeLimit: integer;
  194. FSearchResult: TLDAPResultList;
  195. FExtName: AnsiString;
  196. FExtValue: AnsiString;
  197. function Connect: Boolean;
  198. function BuildPacket(const Value: AnsiString): AnsiString;
  199. function ReceiveResponse: AnsiString;
  200. function DecodeResponse(const Value: AnsiString): AnsiString;
  201. function LdapSasl(Value: AnsiString): AnsiString;
  202. function TranslateFilter(Value: AnsiString): AnsiString;
  203. function GetErrorString(Value: integer): AnsiString;
  204. public
  205. constructor Create;
  206. destructor Destroy; override;
  207. {:Try to connect to LDAP server and start secure channel, when it is required.}
  208. function Login: Boolean;
  209. {:Try to bind to LDAP server with @link(TSynaClient.Username) and
  210. @link(TSynaClient.Password). If this is empty strings, then it do annonymous
  211. Bind. When you not call Bind on LDAPv3, then is automaticly used anonymous
  212. mode.
  213. This method using plaintext transport of password! It is not secure!}
  214. function Bind: Boolean;
  215. {:Try to bind to LDAP server with @link(TSynaClient.Username) and
  216. @link(TSynaClient.Password). If this is empty strings, then it do annonymous
  217. Bind. When you not call Bind on LDAPv3, then is automaticly used anonymous
  218. mode.
  219. This method using SASL with DIGEST-MD5 method for secure transfer of your
  220. password.}
  221. function BindSasl: Boolean;
  222. {:Close connection to LDAP server.}
  223. function Logout: Boolean;
  224. {:Modify content of LDAP attribute on this object.}
  225. function Modify(obj: AnsiString; Op: TLDAPModifyOp; const Value: TLDAPAttribute): Boolean;
  226. {:Add list of attributes to specified object.}
  227. function Add(obj: AnsiString; const Value: TLDAPAttributeList): Boolean;
  228. {:Delete this LDAP object from server.}
  229. function Delete(obj: AnsiString): Boolean;
  230. {:Modify object name of this LDAP object.}
  231. function ModifyDN(obj, newRDN, newSuperior: AnsiString; DeleteoldRDN: Boolean): Boolean;
  232. {:Try to compare Attribute value with this LDAP object.}
  233. function Compare(obj, AttributeValue: AnsiString): Boolean;
  234. {:Search LDAP base for LDAP objects by Filter.}
  235. function Search(obj: AnsiString; TypesOnly: Boolean; Filter: AnsiString;
  236. const Attributes: TStrings): Boolean;
  237. {:Call any LDAPv3 extended command.}
  238. function Extended(const Name, Value: AnsiString): Boolean;
  239. {:Try to start SSL/TLS connection to LDAP server.}
  240. function StartTLS: Boolean;
  241. published
  242. {:Specify version of used LDAP protocol. Default value is 3.}
  243. property Version: integer read FVersion Write FVersion;
  244. {:Result code of last LDAP operation.}
  245. property ResultCode: Integer read FResultCode;
  246. {:Human readable description of result code of last LDAP operation.}
  247. property ResultString: AnsiString read FResultString;
  248. {:Binary string with full last response of LDAP server. This string is
  249. encoded by ASN.1 BER encoding! You need this only for debugging.}
  250. property FullResult: AnsiString read FFullResult;
  251. {:If @true, then try to start TSL mode in Login procedure.}
  252. property AutoTLS: Boolean read FAutoTLS Write FAutoTLS;
  253. {:If @true, then use connection to LDAP server through SSL/TLS tunnel.}
  254. property FullSSL: Boolean read FFullSSL Write FFullSSL;
  255. {:Sequence number of last LDAp command. It is incremented by any LDAP command.}
  256. property Seq: integer read FSeq;
  257. {:Specify what search scope is used in search command.}
  258. property SearchScope: TLDAPSearchScope read FSearchScope Write FSearchScope;
  259. {:Specify how to handle aliases in search command.}
  260. property SearchAliases: TLDAPSearchAliases read FSearchAliases Write FSearchAliases;
  261. {:Specify result size limit in search command. Value 0 means without limit.}
  262. property SearchSizeLimit: integer read FSearchSizeLimit Write FSearchSizeLimit;
  263. {:Specify search time limit in search command (seconds). Value 0 means
  264. without limit.}
  265. property SearchTimeLimit: integer read FSearchTimeLimit Write FSearchTimeLimit;
  266. {:Here is result of search command.}
  267. property SearchResult: TLDAPResultList read FSearchResult;
  268. {:On each LDAP operation can LDAP server return some referals URLs. Here is
  269. their list.}
  270. property Referals: TStringList read FReferals;
  271. {:When you call @link(Extended) operation, then here is result Name returned
  272. by server.}
  273. property ExtName: AnsiString read FExtName;
  274. {:When you call @link(Extended) operation, then here is result Value returned
  275. by server.}
  276. property ExtValue: AnsiString read FExtValue;
  277. {:TCP socket used by all LDAP operations.}
  278. property Sock: TTCPBlockSocket read FSock;
  279. end;
  280. {:Dump result of LDAP SEARCH into human readable form. Good for debugging.}
  281. function LDAPResultDump(const Value: TLDAPResultList): AnsiString;
  282. implementation
  283. {==============================================================================}
  284. function TLDAPAttribute.Get(Index: integer): string;
  285. begin
  286. Result := inherited Get(Index);
  287. if FIsbinary then
  288. Result := DecodeBase64(Result);
  289. end;
  290. procedure TLDAPAttribute.Put(Index: integer; const Value: string);
  291. var
  292. s: AnsiString;
  293. begin
  294. s := Value;
  295. if FIsbinary then
  296. s := EncodeBase64(Value)
  297. else
  298. s :=UnquoteStr(s, '"');
  299. inherited Put(Index, s);
  300. end;
  301. procedure TLDAPAttribute.SetAttributeName(Value: AnsiString);
  302. begin
  303. FAttributeName := Value;
  304. FIsBinary := Pos(';binary', Lowercase(value)) > 0;
  305. end;
  306. {==============================================================================}
  307. constructor TLDAPAttributeList.Create;
  308. begin
  309. inherited Create;
  310. FAttributeList := TList.Create;
  311. end;
  312. destructor TLDAPAttributeList.Destroy;
  313. begin
  314. Clear;
  315. FAttributeList.Free;
  316. inherited Destroy;
  317. end;
  318. procedure TLDAPAttributeList.Clear;
  319. var
  320. n: integer;
  321. x: TLDAPAttribute;
  322. begin
  323. for n := Count - 1 downto 0 do
  324. begin
  325. x := GetAttribute(n);
  326. if Assigned(x) then
  327. x.Free;
  328. end;
  329. FAttributeList.Clear;
  330. end;
  331. function TLDAPAttributeList.Count: integer;
  332. begin
  333. Result := FAttributeList.Count;
  334. end;
  335. function TLDAPAttributeList.GetAttribute(Index: integer): TLDAPAttribute;
  336. begin
  337. Result := nil;
  338. if Index < Count then
  339. Result := TLDAPAttribute(FAttributeList[Index]);
  340. end;
  341. function TLDAPAttributeList.Add: TLDAPAttribute;
  342. begin
  343. Result := TLDAPAttribute.Create;
  344. FAttributeList.Add(Result);
  345. end;
  346. procedure TLDAPAttributeList.Del(Index: integer);
  347. var
  348. x: TLDAPAttribute;
  349. begin
  350. x := GetAttribute(Index);
  351. if Assigned(x) then
  352. x.free;
  353. FAttributeList.Delete(Index);
  354. end;
  355. function TLDAPAttributeList.Find(AttributeName: AnsiString): TLDAPAttribute;
  356. var
  357. n: integer;
  358. x: TLDAPAttribute;
  359. begin
  360. Result := nil;
  361. AttributeName := lowercase(AttributeName);
  362. for n := 0 to Count - 1 do
  363. begin
  364. x := GetAttribute(n);
  365. if Assigned(x) then
  366. if lowercase(x.AttributeName) = Attributename then
  367. begin
  368. result := x;
  369. break;
  370. end;
  371. end;
  372. end;
  373. {==============================================================================}
  374. constructor TLDAPResult.Create;
  375. begin
  376. inherited Create;
  377. FAttributes := TLDAPAttributeList.Create;
  378. end;
  379. destructor TLDAPResult.Destroy;
  380. begin
  381. FAttributes.Free;
  382. inherited Destroy;
  383. end;
  384. {==============================================================================}
  385. constructor TLDAPResultList.Create;
  386. begin
  387. inherited Create;
  388. FResultList := TList.Create;
  389. end;
  390. destructor TLDAPResultList.Destroy;
  391. begin
  392. Clear;
  393. FResultList.Free;
  394. inherited Destroy;
  395. end;
  396. procedure TLDAPResultList.Clear;
  397. var
  398. n: integer;
  399. x: TLDAPResult;
  400. begin
  401. for n := Count - 1 downto 0 do
  402. begin
  403. x := GetResult(n);
  404. if Assigned(x) then
  405. x.Free;
  406. end;
  407. FResultList.Clear;
  408. end;
  409. function TLDAPResultList.Count: integer;
  410. begin
  411. Result := FResultList.Count;
  412. end;
  413. function TLDAPResultList.GetResult(Index: integer): TLDAPResult;
  414. begin
  415. Result := nil;
  416. if Index < Count then
  417. Result := TLDAPResult(FResultList[Index]);
  418. end;
  419. function TLDAPResultList.Add: TLDAPResult;
  420. begin
  421. Result := TLDAPResult.Create;
  422. FResultList.Add(Result);
  423. end;
  424. {==============================================================================}
  425. constructor TLDAPSend.Create;
  426. begin
  427. inherited Create;
  428. FReferals := TStringList.Create;
  429. FFullResult := '';
  430. FSock := TTCPBlockSocket.Create;
  431. FTimeout := 60000;
  432. FTargetPort := cLDAPProtocol;
  433. FAutoTLS := False;
  434. FFullSSL := False;
  435. FSeq := 0;
  436. FVersion := 3;
  437. FSearchScope := SS_WholeSubtree;
  438. FSearchAliases := SA_Always;
  439. FSearchSizeLimit := 0;
  440. FSearchTimeLimit := 0;
  441. FSearchResult := TLDAPResultList.Create;
  442. end;
  443. destructor TLDAPSend.Destroy;
  444. begin
  445. FSock.Free;
  446. FSearchResult.Free;
  447. FReferals.Free;
  448. inherited Destroy;
  449. end;
  450. function TLDAPSend.GetErrorString(Value: integer): AnsiString;
  451. begin
  452. case Value of
  453. 0:
  454. Result := 'Success';
  455. 1:
  456. Result := 'Operations error';
  457. 2:
  458. Result := 'Protocol error';
  459. 3:
  460. Result := 'Time limit Exceeded';
  461. 4:
  462. Result := 'Size limit Exceeded';
  463. 5:
  464. Result := 'Compare FALSE';
  465. 6:
  466. Result := 'Compare TRUE';
  467. 7:
  468. Result := 'Auth method not supported';
  469. 8:
  470. Result := 'Strong auth required';
  471. 9:
  472. Result := '-- reserved --';
  473. 10:
  474. Result := 'Referal';
  475. 11:
  476. Result := 'Admin limit exceeded';
  477. 12:
  478. Result := 'Unavailable critical extension';
  479. 13:
  480. Result := 'Confidentality required';
  481. 14:
  482. Result := 'Sasl bind in progress';
  483. 16:
  484. Result := 'No such attribute';
  485. 17:
  486. Result := 'Undefined attribute type';
  487. 18:
  488. Result := 'Inappropriate matching';
  489. 19:
  490. Result := 'Constraint violation';
  491. 20:
  492. Result := 'Attribute or value exists';
  493. 21:
  494. Result := 'Invalid attribute syntax';
  495. 32:
  496. Result := 'No such object';
  497. 33:
  498. Result := 'Alias problem';
  499. 34:
  500. Result := 'Invalid DN syntax';
  501. 36:
  502. Result := 'Alias dereferencing problem';
  503. 48:
  504. Result := 'Inappropriate authentication';
  505. 49:
  506. Result := 'Invalid credentials';
  507. 50:
  508. Result := 'Insufficient access rights';
  509. 51:
  510. Result := 'Busy';
  511. 52:
  512. Result := 'Unavailable';
  513. 53:
  514. Result := 'Unwilling to perform';
  515. 54:
  516. Result := 'Loop detect';
  517. 64:
  518. Result := 'Naming violation';
  519. 65:
  520. Result := 'Object class violation';
  521. 66:
  522. Result := 'Not allowed on non leaf';
  523. 67:
  524. Result := 'Not allowed on RDN';
  525. 68:
  526. Result := 'Entry already exists';
  527. 69:
  528. Result := 'Object class mods prohibited';
  529. 71:
  530. Result := 'Affects multiple DSAs';
  531. 80:
  532. Result := 'Other';
  533. else
  534. Result := '--unknown--';
  535. end;
  536. end;
  537. function TLDAPSend.Connect: Boolean;
  538. begin
  539. // Do not call this function! It is calling by LOGIN method!
  540. FSock.CloseSocket;
  541. FSock.LineBuffer := '';
  542. FSeq := 0;
  543. FSock.Bind(FIPInterface, cAnyPort);
  544. if FSock.LastError = 0 then
  545. FSock.Connect(FTargetHost, FTargetPort);
  546. if FSock.LastError = 0 then
  547. if FFullSSL then
  548. FSock.SSLDoConnect;
  549. Result := FSock.LastError = 0;
  550. end;
  551. function TLDAPSend.BuildPacket(const Value: AnsiString): AnsiString;
  552. begin
  553. Inc(FSeq);
  554. Result := ASNObject(ASNObject(ASNEncInt(FSeq), ASN1_INT) + Value, ASN1_SEQ);
  555. end;
  556. function TLDAPSend.ReceiveResponse: AnsiString;
  557. var
  558. x: Byte;
  559. i,j: integer;
  560. begin
  561. Result := '';
  562. FFullResult := '';
  563. x := FSock.RecvByte(FTimeout);
  564. if x <> ASN1_SEQ then
  565. Exit;
  566. Result := AnsiChar(x);
  567. x := FSock.RecvByte(FTimeout);
  568. Result := Result + AnsiChar(x);
  569. if x < $80 then
  570. i := 0
  571. else
  572. i := x and $7F;
  573. if i > 0 then
  574. Result := Result + FSock.RecvBufferStr(i, Ftimeout);
  575. if FSock.LastError <> 0 then
  576. begin
  577. Result := '';
  578. Exit;
  579. end;
  580. //get length of LDAP packet
  581. j := 2;
  582. i := ASNDecLen(j, Result);
  583. //retreive rest of LDAP packet
  584. if i > 0 then
  585. Result := Result + FSock.RecvBufferStr(i, Ftimeout);
  586. if FSock.LastError <> 0 then
  587. begin
  588. Result := '';
  589. Exit;
  590. end;
  591. FFullResult := Result;
  592. end;
  593. function TLDAPSend.DecodeResponse(const Value: AnsiString): AnsiString;
  594. var
  595. i, x: integer;
  596. Svt: Integer;
  597. s, t: AnsiString;
  598. begin
  599. Result := '';
  600. FResultCode := -1;
  601. FResultstring := '';
  602. FResponseCode := -1;
  603. FResponseDN := '';
  604. FReferals.Clear;
  605. i := 1;
  606. ASNItem(i, Value, Svt);
  607. x := StrToIntDef(ASNItem(i, Value, Svt), 0);
  608. if (svt <> ASN1_INT) or (x <> FSeq) then
  609. Exit;
  610. s := ASNItem(i, Value, Svt);
  611. FResponseCode := svt;
  612. if FResponseCode in [LDAP_ASN1_BIND_RESPONSE, LDAP_ASN1_SEARCH_DONE,
  613. LDAP_ASN1_MODIFY_RESPONSE, LDAP_ASN1_ADD_RESPONSE, LDAP_ASN1_DEL_RESPONSE,
  614. LDAP_ASN1_MODIFYDN_RESPONSE, LDAP_ASN1_COMPARE_RESPONSE,
  615. LDAP_ASN1_EXT_RESPONSE] then
  616. begin
  617. FResultCode := StrToIntDef(ASNItem(i, Value, Svt), -1);
  618. FResponseDN := ASNItem(i, Value, Svt);
  619. FResultString := ASNItem(i, Value, Svt);
  620. if FResultString = '' then
  621. FResultString := GetErrorString(FResultCode);
  622. if FResultCode = 10 then
  623. begin
  624. s := ASNItem(i, Value, Svt);
  625. if svt = $A3 then
  626. begin
  627. x := 1;
  628. while x < Length(s) do
  629. begin
  630. t := ASNItem(x, s, Svt);
  631. FReferals.Add(t);
  632. end;
  633. end;
  634. end;
  635. end;
  636. Result := Copy(Value, i, Length(Value) - i + 1);
  637. end;
  638. function TLDAPSend.LdapSasl(Value: AnsiString): AnsiString;
  639. var
  640. nonce, cnonce, nc, realm, qop, uri, response: AnsiString;
  641. s: AnsiString;
  642. a1, a2: AnsiString;
  643. l: TStringList;
  644. n: integer;
  645. begin
  646. l := TStringList.Create;
  647. try
  648. nonce := '';
  649. realm := '';
  650. l.CommaText := Value;
  651. n := IndexByBegin('nonce=', l);
  652. if n >= 0 then
  653. nonce := UnQuoteStr(Trim(SeparateRight(l[n], 'nonce=')), '"');
  654. n := IndexByBegin('realm=', l);
  655. if n >= 0 then
  656. realm := UnQuoteStr(Trim(SeparateRight(l[n], 'realm=')), '"');
  657. cnonce := IntToHex(GetTick, 8);
  658. nc := '00000001';
  659. qop := 'auth';
  660. uri := 'ldap/' + FSock.ResolveIpToName(FSock.GetRemoteSinIP);
  661. a1 := md5(FUsername + ':' + realm + ':' + FPassword)
  662. + ':' + nonce + ':' + cnonce;
  663. a2 := 'AUTHENTICATE:' + uri;
  664. s := strtohex(md5(a1))+':' + nonce + ':' + nc + ':' + cnonce + ':'
  665. + qop +':'+strtohex(md5(a2));
  666. response := strtohex(md5(s));
  667. Result := 'username="' + Fusername + '",realm="' + realm + '",nonce="';
  668. Result := Result + nonce + '",cnonce="' + cnonce + '",nc=' + nc + ',qop=';
  669. Result := Result + qop + ',digest-uri="' + uri + '",response=' + response;
  670. finally
  671. l.Free;
  672. end;
  673. end;
  674. function TLDAPSend.TranslateFilter(Value: AnsiString): AnsiString;
  675. var
  676. x: integer;
  677. s, t, l: AnsiString;
  678. r: string;
  679. c: Ansichar;
  680. attr, rule: AnsiString;
  681. dn: Boolean;
  682. begin
  683. Result := '';
  684. if Value = '' then
  685. Exit;
  686. s := Value;
  687. if Value[1] = '(' then
  688. begin
  689. x := RPos(')', Value);
  690. s := Copy(Value, 2, x - 2);
  691. end;
  692. if s = '' then
  693. Exit;
  694. case s[1] of
  695. '!':
  696. // NOT rule (recursive call)
  697. begin
  698. Result := ASNOBject(TranslateFilter(GetBetween('(', ')', s)), $A2);
  699. end;
  700. '&':
  701. // AND rule (recursive call)
  702. begin
  703. repeat
  704. t := GetBetween('(', ')', s);
  705. s := Trim(SeparateRight(s, t));
  706. if s <> '' then
  707. if s[1] = ')' then
  708. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(s, 1, 1);
  709. Result := Result + TranslateFilter(t);
  710. until s = '';
  711. Result := ASNOBject(Result, $A0);
  712. end;
  713. '|':
  714. // OR rule (recursive call)
  715. begin
  716. repeat
  717. t := GetBetween('(', ')', s);
  718. s := Trim(SeparateRight(s, t));
  719. if s <> '' then
  720. if s[1] = ')' then
  721. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(s, 1, 1);
  722. Result := Result + TranslateFilter(t);
  723. until s = '';
  724. Result := ASNOBject(Result, $A1);
  725. end;
  726. else
  727. begin
  728. l := Trim(SeparateLeft(s, '='));
  729. r := Trim(SeparateRight(s, '='));
  730. if l <> '' then
  731. begin
  732. c := l[Length(l)];
  733. case c of
  734. ':':
  735. // Extensible match
  736. begin
  737. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(l, Length(l), 1);
  738. dn := False;
  739. attr := '';
  740. rule := '';
  741. if Pos(':dn', l) > 0 then
  742. begin
  743. dn := True;
  744. l := ReplaceString(l, ':dn', '');
  745. end;
  746. attr := Trim(SeparateLeft(l, ':'));
  747. rule := Trim(SeparateRight(l, ':'));
  748. if rule = l then
  749. rule := '';
  750. if rule <> '' then
  751. Result := ASNObject(rule, $81);
  752. if attr <> '' then
  753. Result := Result + ASNObject(attr, $82);
  754. Result := Result + ASNObject(DecodeTriplet(r, '\'), $83);
  755. if dn then
  756. Result := Result + ASNObject(AsnEncInt($ff), $84)
  757. else
  758. Result := Result + ASNObject(AsnEncInt(0), $84);
  759. Result := ASNOBject(Result, $a9);
  760. end;
  761. '~':
  762. // Approx match
  763. begin
  764. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(l, Length(l), 1);
  765. Result := ASNOBject(l, ASN1_OCTSTR)
  766. + ASNOBject(DecodeTriplet(r, '\'), ASN1_OCTSTR);
  767. Result := ASNOBject(Result, $a8);
  768. end;
  769. '>':
  770. // Greater or equal match
  771. begin
  772. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(l, Length(l), 1);
  773. Result := ASNOBject(l, ASN1_OCTSTR)
  774. + ASNOBject(DecodeTriplet(r, '\'), ASN1_OCTSTR);
  775. Result := ASNOBject(Result, $a5);
  776. end;
  777. '<':
  778. // Less or equal match
  779. begin
  780. {$IFDEF CIL}Borland.Delphi.{$ENDIF}System.Delete(l, Length(l), 1);
  781. Result := ASNOBject(l, ASN1_OCTSTR)
  782. + ASNOBject(DecodeTriplet(r, '\'), ASN1_OCTSTR);
  783. Result := ASNOBject(Result, $a6);
  784. end;
  785. else
  786. // present
  787. if r = '*' then
  788. Result := ASNOBject(l, $87)
  789. else
  790. if Pos('*', r) > 0 then
  791. // substrings
  792. begin
  793. s := Fetch(r, '*');
  794. if s <> '' then
  795. Result := ASNOBject(DecodeTriplet(s, '\'), $80);
  796. while r <> '' do
  797. begin
  798. if Pos('*', r) <= 0 then
  799. break;
  800. s := Fetch(r, '*');
  801. Result := Result + ASNOBject(DecodeTriplet(s, '\'), $81);
  802. end;
  803. if r <> '' then
  804. Result := Result + ASNOBject(DecodeTriplet(r, '\'), $82);
  805. Result := ASNOBject(l, ASN1_OCTSTR)
  806. + ASNOBject(Result, ASN1_SEQ);
  807. Result := ASNOBject(Result, $a4);
  808. end
  809. else
  810. begin
  811. // Equality match
  812. Result := ASNOBject(l, ASN1_OCTSTR)
  813. + ASNOBject(DecodeTriplet(r, '\'), ASN1_OCTSTR);
  814. Result := ASNOBject(Result, $a3);
  815. end;
  816. end;
  817. end;
  818. end;
  819. end;
  820. end;
  821. function TLDAPSend.Login: Boolean;
  822. begin
  823. Result := False;
  824. if not Connect then
  825. Exit;
  826. Result := True;
  827. if FAutoTLS then
  828. Result := StartTLS;
  829. end;
  830. function TLDAPSend.Bind: Boolean;
  831. var
  832. s: AnsiString;
  833. begin
  834. s := ASNObject(ASNEncInt(FVersion), ASN1_INT)
  835. + ASNObject(FUsername, ASN1_OCTSTR)
  836. + ASNObject(FPassword, $80);
  837. s := ASNObject(s, LDAP_ASN1_BIND_REQUEST);
  838. Fsock.SendString(BuildPacket(s));
  839. s := ReceiveResponse;
  840. DecodeResponse(s);
  841. Result := FResultCode = 0;
  842. end;
  843. function TLDAPSend.BindSasl: Boolean;
  844. var
  845. s, t: AnsiString;
  846. x, xt: integer;
  847. digreq: AnsiString;
  848. begin
  849. Result := False;
  850. if FPassword = '' then
  851. Result := Bind
  852. else
  853. begin
  854. digreq := ASNObject(ASNEncInt(FVersion), ASN1_INT)
  855. + ASNObject('', ASN1_NULL)
  856. + ASNObject(ASNObject('DIGEST-MD5', ASN1_OCTSTR), $A3);
  857. digreq := ASNObject(digreq, LDAP_ASN1_BIND_REQUEST);
  858. Fsock.SendString(BuildPacket(digreq));
  859. s := ReceiveResponse;
  860. t := DecodeResponse(s);
  861. if FResultCode = 14 then
  862. begin
  863. s := t;
  864. x := 1;
  865. t := ASNItem(x, s, xt);
  866. s := ASNObject(ASNEncInt(FVersion), ASN1_INT)
  867. + ASNObject('', ASN1_NULL)
  868. + ASNObject(ASNObject('DIGEST-MD5', ASN1_OCTSTR), $A3)
  869. + ASNObject(LdapSasl(t), ASN1_OCTSTR);
  870. s := ASNObject(s, LDAP_ASN1_BIND_REQUEST);
  871. Fsock.SendString(BuildPacket(s));
  872. s := ReceiveResponse;
  873. DecodeResponse(s);
  874. if FResultCode = 14 then
  875. begin
  876. Fsock.SendString(BuildPacket(digreq));
  877. s := ReceiveResponse;
  878. DecodeResponse(s);
  879. end;
  880. Result := FResultCode = 0;
  881. end;
  882. end;
  883. end;
  884. function TLDAPSend.Logout: Boolean;
  885. begin
  886. Fsock.SendString(BuildPacket(ASNObject('', LDAP_ASN1_UNBIND_REQUEST)));
  887. FSock.CloseSocket;
  888. Result := True;
  889. end;
  890. function TLDAPSend.Modify(obj: AnsiString; Op: TLDAPModifyOp; const Value: TLDAPAttribute): Boolean;
  891. var
  892. s: AnsiString;
  893. n: integer;
  894. begin
  895. s := '';
  896. for n := 0 to Value.Count -1 do
  897. s := s + ASNObject(Value[n], ASN1_OCTSTR);
  898. s := ASNObject(Value.AttributeName, ASN1_OCTSTR) + ASNObject(s, ASN1_SETOF);
  899. s := ASNObject(ASNEncInt(Ord(Op)), ASN1_ENUM) + ASNObject(s, ASN1_SEQ);
  900. s := ASNObject(s, ASN1_SEQ);
  901. s := ASNObject(obj, ASN1_OCTSTR) + ASNObject(s, ASN1_SEQ);
  902. s := ASNObject(s, LDAP_ASN1_MODIFY_REQUEST);
  903. Fsock.SendString(BuildPacket(s));
  904. s := ReceiveResponse;
  905. DecodeResponse(s);
  906. Result := FResultCode = 0;
  907. end;
  908. function TLDAPSend.Add(obj: AnsiString; const Value: TLDAPAttributeList): Boolean;
  909. var
  910. s, t: AnsiString;
  911. n, m: integer;
  912. begin
  913. s := '';
  914. for n := 0 to Value.Count - 1 do
  915. begin
  916. t := '';
  917. for m := 0 to Value[n].Count - 1 do
  918. t := t + ASNObject(Value[n][m], ASN1_OCTSTR);
  919. t := ASNObject(Value[n].AttributeName, ASN1_OCTSTR)
  920. + ASNObject(t, ASN1_SETOF);
  921. s := s + ASNObject(t, ASN1_SEQ);
  922. end;
  923. s := ASNObject(obj, ASN1_OCTSTR) + ASNObject(s, ASN1_SEQ);
  924. s := ASNObject(s, LDAP_ASN1_ADD_REQUEST);
  925. Fsock.SendString(BuildPacket(s));
  926. s := ReceiveResponse;
  927. DecodeResponse(s);
  928. Result := FResultCode = 0;
  929. end;
  930. function TLDAPSend.Delete(obj: AnsiString): Boolean;
  931. var
  932. s: AnsiString;
  933. begin
  934. s := ASNObject(obj, LDAP_ASN1_DEL_REQUEST);
  935. Fsock.SendString(BuildPacket(s));
  936. s := ReceiveResponse;
  937. DecodeResponse(s);
  938. Result := FResultCode = 0;
  939. end;
  940. function TLDAPSend.ModifyDN(obj, newRDN, newSuperior: AnsiString; DeleteOldRDN: Boolean): Boolean;
  941. var
  942. s: AnsiString;
  943. begin
  944. s := ASNObject(obj, ASN1_OCTSTR) + ASNObject(newRDN, ASN1_OCTSTR);
  945. if DeleteOldRDN then
  946. s := s + ASNObject(ASNEncInt($ff), ASN1_BOOL)
  947. else
  948. s := s + ASNObject(ASNEncInt(0), ASN1_BOOL);
  949. if newSuperior <> '' then
  950. s := s + ASNObject(newSuperior, $80);
  951. s := ASNObject(s, LDAP_ASN1_MODIFYDN_REQUEST);
  952. Fsock.SendString(BuildPacket(s));
  953. s := ReceiveResponse;
  954. DecodeResponse(s);
  955. Result := FResultCode = 0;
  956. end;
  957. function TLDAPSend.Compare(obj, AttributeValue: AnsiString): Boolean;
  958. var
  959. s: AnsiString;
  960. begin
  961. s := ASNObject(Trim(SeparateLeft(AttributeValue, '=')), ASN1_OCTSTR)
  962. + ASNObject(Trim(SeparateRight(AttributeValue, '=')), ASN1_OCTSTR);
  963. s := ASNObject(obj, ASN1_OCTSTR) + ASNObject(s, ASN1_SEQ);
  964. s := ASNObject(s, LDAP_ASN1_COMPARE_REQUEST);
  965. Fsock.SendString(BuildPacket(s));
  966. s := ReceiveResponse;
  967. DecodeResponse(s);
  968. Result := FResultCode = 0;
  969. end;
  970. function TLDAPSend.Search(obj: AnsiString; TypesOnly: Boolean; Filter: AnsiString;
  971. const Attributes: TStrings): Boolean;
  972. var
  973. s, t, u: AnsiString;
  974. n, i, x: integer;
  975. r: TLDAPResult;
  976. a: TLDAPAttribute;
  977. begin
  978. FSearchResult.Clear;
  979. FReferals.Clear;
  980. s := ASNObject(obj, ASN1_OCTSTR);
  981. s := s + ASNObject(ASNEncInt(Ord(FSearchScope)), ASN1_ENUM);
  982. s := s + ASNObject(ASNEncInt(Ord(FSearchAliases)), ASN1_ENUM);
  983. s := s + ASNObject(ASNEncInt(FSearchSizeLimit), ASN1_INT);
  984. s := s + ASNObject(ASNEncInt(FSearchTimeLimit), ASN1_INT);
  985. if TypesOnly then
  986. s := s + ASNObject(ASNEncInt($ff), ASN1_BOOL)
  987. else
  988. s := s + ASNObject(ASNEncInt(0), ASN1_BOOL);
  989. if Filter = '' then
  990. Filter := '(objectclass=*)';
  991. t := TranslateFilter(Filter);
  992. if t = '' then
  993. s := s + ASNObject('', ASN1_NULL)
  994. else
  995. s := s + t;
  996. t := '';
  997. for n := 0 to Attributes.Count - 1 do
  998. t := t + ASNObject(Attributes[n], ASN1_OCTSTR);
  999. s := s + ASNObject(t, ASN1_SEQ);
  1000. s := ASNObject(s, LDAP_ASN1_SEARCH_REQUEST);
  1001. Fsock.SendString(BuildPacket(s));
  1002. repeat
  1003. s := ReceiveResponse;
  1004. t := DecodeResponse(s);
  1005. if FResponseCode = LDAP_ASN1_SEARCH_ENTRY then
  1006. begin
  1007. //dekoduj zaznam
  1008. r := FSearchResult.Add;
  1009. n := 1;
  1010. r.ObjectName := ASNItem(n, t, x);
  1011. ASNItem(n, t, x);
  1012. if x = ASN1_SEQ then
  1013. begin
  1014. while n < Length(t) do
  1015. begin
  1016. s := ASNItem(n, t, x);
  1017. if x = ASN1_SEQ then
  1018. begin
  1019. i := n + Length(s);
  1020. a := r.Attributes.Add;
  1021. u := ASNItem(n, t, x);
  1022. a.AttributeName := u;
  1023. ASNItem(n, t, x);
  1024. if x = ASN1_SETOF then
  1025. while n < i do
  1026. begin
  1027. u := ASNItem(n, t, x);
  1028. a.Add(u);
  1029. end;
  1030. end;
  1031. end;
  1032. end;
  1033. end;
  1034. if FResponseCode = LDAP_ASN1_SEARCH_REFERENCE then
  1035. begin
  1036. n := 1;
  1037. while n < Length(t) do
  1038. FReferals.Add(ASNItem(n, t, x));
  1039. end;
  1040. until FResponseCode = LDAP_ASN1_SEARCH_DONE;
  1041. Result := FResultCode = 0;
  1042. end;
  1043. function TLDAPSend.Extended(const Name, Value: AnsiString): Boolean;
  1044. var
  1045. s, t: AnsiString;
  1046. x, xt: integer;
  1047. begin
  1048. s := ASNObject(Name, $80);
  1049. if Value <> '' then
  1050. s := s + ASNObject(Value, $81);
  1051. s := ASNObject(s, LDAP_ASN1_EXT_REQUEST);
  1052. Fsock.SendString(BuildPacket(s));
  1053. s := ReceiveResponse;
  1054. t := DecodeResponse(s);
  1055. Result := FResultCode = 0;
  1056. if Result then
  1057. begin
  1058. x := 1;
  1059. FExtName := ASNItem(x, t, xt);
  1060. FExtValue := ASNItem(x, t, xt);
  1061. end;
  1062. end;
  1063. function TLDAPSend.StartTLS: Boolean;
  1064. begin
  1065. Result := Extended('1.3.6.1.4.1.1466.20037', '');
  1066. if Result then
  1067. begin
  1068. Fsock.SSLDoConnect;
  1069. Result := FSock.LastError = 0;
  1070. end;
  1071. end;
  1072. {==============================================================================}
  1073. function LDAPResultDump(const Value: TLDAPResultList): AnsiString;
  1074. var
  1075. n, m, o: integer;
  1076. r: TLDAPResult;
  1077. a: TLDAPAttribute;
  1078. begin
  1079. Result := 'Results: ' + IntToStr(Value.Count) + CRLF +CRLF;
  1080. for n := 0 to Value.Count - 1 do
  1081. begin
  1082. Result := Result + 'Result: ' + IntToStr(n) + CRLF;
  1083. r := Value[n];
  1084. Result := Result + ' Object: ' + r.ObjectName + CRLF;
  1085. for m := 0 to r.Attributes.Count - 1 do
  1086. begin
  1087. a := r.Attributes[m];
  1088. Result := Result + ' Attribute: ' + a.AttributeName + CRLF;
  1089. for o := 0 to a.Count - 1 do
  1090. Result := Result + ' ' + a[o] + CRLF;
  1091. end;
  1092. end;
  1093. end;
  1094. end.