a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
aa370a1999-04-24Johan Schön // LDAP client protocol implementation for Pike. //
8157482005-04-20Martin Stjernholm // $Id: client.pike,v 1.96 2005/04/20 15:20:22 mast Exp $
aa370a1999-04-24Johan Schön // // Honza Petrous, hop@unibase.cz // // ---------------------------------------------------------------------- // // History: // // v0.0 1998-05-25 Starting up! // v1.0 1998-06-21 Core functions (open, bind, unbind, delete, add, // compare, search), only V2 operations, // some additional restrictions // v1.1u 1998-07-03 Unfinished version (for testing) // v1.2a 1998-07-14 New beta code (I love jump over versions like Roxen ;) // - added asn1_enumerated, asn1_boolean, asn1_nulllist // and asn1_application_null // - search op: corrected ASN1 type // - unbind op: corrected ASN1 type // v1.2d 1998-08 - added logging of bad LDAP PDU in readmsg // v1.3 1998-09-18 - resolved "null attribute list" bug in search // (Now is compatible with MS Exchange ;-) // v1.9 1999-03 - redesign of LDAP.pmod // !!! Library uses new ASN1 API => uncompatible !!! // !!! with Pike 0.5 (needed Pike 0.6 or higher) !!! // - sliced library code to the several files: // * LDAP.pmod/protocol.pike // (low level code) // * LDAP.pmod/client.pike // (main code) // * LDAP.pmod/ldap_errors.h // (error array) // * LDAP.pmod/ldap_globals.h // (global defininitions) // * LDAP.pmod/ldap_privates.pmod // (ASN.1 LDAP-related classes and hacks) // - changed default of 'ldap_scope' to 0 // - search filter now correctly processed '\(' & '\)' // [! Still unimplemented escaped conditions chars!] // // v1.10 1999-03-28 - moved core to the new 'protocol' code // 1999-03-28 - rewritten ldap_[op] startup code // // v1.11 1999-04-10 - search filter now processed multiple wild '*' chars // [ Escaping untested, yet ]
b68dde2000-02-12Honza Petrous // v1.13 2000-02-12 - fixed search NOT op bug (end revision normalized)
aa370a1999-04-24Johan Schön //
4ad4cd2000-02-17Honza Petrous // v1.14 2000-02-17 - added decoding of UTF8 strings for v3 protocol //
6de1dc2000-07-14Honza Petrous // newer versions - see CVS at roxen.com (hop) //
59a35e2000-07-25Honza Petrous // - corrected deUTF8 values in result // -
fc02d12000-07-20Honza Petrous //
aa370a1999-04-24Johan Schön // Specifications: // // RFC 1558 (search filter representations) // RFC 1777,1778,1779 (version2 spec) // RFC 1823 (v2 API) // RFC 2251,2252,2253,2254,2255,2256 (version3 spec) // draft-ietf-asid-ldap-c-api-00.txt (v3 API)
4f57c32004-05-25Henrik Grubbström (Grubba) // RFC 2279 (UTF-8) // RFC 2696 (paged requests)
aa370a1999-04-24Johan Schön // // Interesting, applicable // RFC 2307 (LDAP as network information services; draft?)
ffaf452004-04-14Martin Nilsson #if constant(.ldap_privates)
aa370a1999-04-24Johan Schön  #include "ldap_globals.h"
ca6e1f2004-02-29Martin Nilsson #if constant(SSL.Cipher.CipherAlgorithm)
5532e12003-02-07Martin Nilsson import SSL.Constants;
6c23602002-07-31Martin Nilsson #endif
0668612001-09-14Honza Petrous 
84e8f02005-04-06Martin Stjernholm import ".";
cec0932005-03-11Martin Stjernholm 
aa370a1999-04-24Johan Schön // ------------------------ // ASN.1 decode macros
6e47642005-03-10Martin Stjernholm  #define ASN1_GET_RESULTAPP(X) ((X)->elements[1]->get_tag())
6a40e72005-03-14Martin Stjernholm #define ASN1_GET_DN(X) ((X)->elements[0]->value)
6e47642005-03-10Martin Stjernholm #define ASN1_GET_ATTR_ARRAY(X) (sizeof ((X)->elements) > 1 && \ (array) ((X)->elements[1]->elements)) #define ASN1_GET_ATTR_NAME(X) ((X)->elements[0]->value) #define ASN1_GET_ATTR_VALUES(X) ((X)->elements[1]->elements->value) #define ASN1_DECODE_RESULTAPP(X) ASN1_GET_RESULTAPP (.ldap_privates.ldap_der_decode(X))
aa370a1999-04-24Johan Schön #define ASN1_DECODE_RESULTCODE(X) (int)(.ldap_privates.ldap_der_decode(X)->elements[1]->elements[0]->value->cast_to_int())
bf334d2001-08-15Honza Petrous #define ASN1_DECODE_RESULTSTRING(X) (.ldap_privates.ldap_der_decode(X)->elements[1]->elements[2]->value) #define ASN1_DECODE_RESULTREFS(X) (.ldap_privates.ldap_der_decode(X)->elements[1]->elements[3]->elements)
aa370a1999-04-24Johan Schön #define ASN1_DECODE_RAWDEBUG(X) (.ldap_privates.ldap_der_decode(X)->debug_string())
0668612001-09-14Honza Petrous  //! Contains the client implementation of the LDAP protocol. //! All of the version 2 protocol features are implemented //! but only the base parts of the version 3.
aa370a1999-04-24Johan Schön  inherit .protocol;
f96d1b2004-05-26Henrik Grubbström (Grubba)  private {
84e8f02005-04-06Martin Stjernholm  string bound_dn; // When actually bound, set to the bind DN. string ldap_basedn; // baseDN int ldap_scope; // SCOPE_* int ldap_deref; // 0: ... int ldap_sizelimit; int ldap_timelimit;
f96d1b2004-05-26Henrik Grubbström (Grubba)  mapping lauth = ([]);
49ae3a2005-04-07Martin Stjernholm  object default_filter_obj; // Filter object parsed from lauth->filter.
4729292005-04-06Martin Stjernholm  result last_rv; // last returned value
f96d1b2004-05-26Henrik Grubbström (Grubba)  }
aa370a1999-04-24Johan Schön 
4b5b452005-04-02Martin Nilsson //! @ignore
6e47642005-03-10Martin Stjernholm static function(string:string) get_attr_decoder (string attr,
3baf5c2005-03-10Martin Stjernholm  DO_IF_DEBUG (void|int nowarn))
6e47642005-03-10Martin Stjernholm { if (mapping(string:mixed) attr_descr = get_attr_type_descr (attr)) { if (function(string:string) decoder =
cec0932005-03-11Martin Stjernholm  syntax_decode_fns[attr_descr->syntax_oid])
6e47642005-03-10Martin Stjernholm  return decoder; #ifdef DEBUG
cec0932005-03-11Martin Stjernholm  else if (!get_constant_name (attr_descr->syntax_oid))
6e47642005-03-10Martin Stjernholm  werror ("Warning: Unknown syntax %O for attribute %O - " "binary content assumed.\n", attr_descr->syntax_oid, attr); #endif } #ifdef DEBUG else if (!nowarn && !has_suffix (attr, ";binary") && !has_value (attr, ";binary;")) werror ("Warning: Couldn't fetch attribute description for %O - "
c908692005-04-06Martin Stjernholm  "binary content assumed.\n%s", attr, describe_backtrace(backtrace()));
6e47642005-03-10Martin Stjernholm #endif return 0; }
4b5b452005-04-02Martin Nilsson //! @endignore
6e47642005-03-10Martin Stjernholm  static function(string:string) get_attr_encoder (string attr) { if (mapping(string:mixed) attr_descr = get_attr_type_descr (attr)) { if (function(string:string) encoder =
cec0932005-03-11Martin Stjernholm  syntax_encode_fns[attr_descr->syntax_oid])
6e47642005-03-10Martin Stjernholm  return encoder; #ifdef DEBUG
cec0932005-03-11Martin Stjernholm  else if (!get_constant_name (attr_descr->syntax_oid))
6e47642005-03-10Martin Stjernholm  werror ("Warning: Unknown syntax %O for attribute %O - " "binary content assumed.\n", attr_descr->syntax_oid, attr); #endif } #ifdef DEBUG else if (!has_suffix (attr, ";binary") && !has_value (attr, ";binary;")) werror ("Warning: Couldn't fetch attribute description for %O - " "binary content assumed.\n", attr); #endif return 0; }
0668612001-09-14Honza Petrous  //! Contains the result of a LDAP search. //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.search], @[LDAP.client.result.fetch]
0668612001-09-14Honza Petrous  //!
aa370a1999-04-24Johan Schön  class result // ------------------ { private int resultcode = LDAP_SUCCESS;
bf334d2001-08-15Honza Petrous  private string resultstring;
aa370a1999-04-24Johan Schön  private int actnum = 0;
4729292005-04-06Martin Stjernholm  private array(mapping(string:string|array(string))) entry = ({});
4066882005-03-11Martin Stjernholm  private int flags;
bf334d2001-08-15Honza Petrous  array(string) referrals;
aa370a1999-04-24Johan Schön 
6e47642005-03-10Martin Stjernholm  static array decode_entries (array(string) rawres) {
4729292005-04-06Martin Stjernholm  array(mapping(string:string|array(string))) res = ({});
aa370a1999-04-24Johan Schön 
6a40e72005-03-14Martin Stjernholm #define DECODE_ENTRIES(SET_DN, SET_ATTR) do { \
4729292005-04-06Martin Stjernholm  if (flags & SEARCH_MULTIVAL_ARRAYS_ONLY) { \ foreach (rawres, string rawent) { \ object derent = .ldap_privates.ldap_der_decode (rawent)->elements[1]; \ if (array(object) derattribs = ASN1_GET_ATTR_ARRAY (derent)) { \ string dn = (SET_DN); \ mapping(string:string|array) attrs = (["dn": dn]); \ foreach (derattribs, object derattr) { \ string attr; \ {SET_ATTR;} \ sscanf (attr, "%[^;]", string bare_attr); \ if (mapping(string:mixed) attr_descr = \ get_attr_type_descr (bare_attr)) { \ if (attr_descr["SINGLE-VALUE"]) { \ if (sizeof (attrs[attr])) { \ DO_IF_DEBUG ( \ if (sizeof (attrs[attr]) > 1) \ ERROR ("Got multple values %O for single valued " \ "attribute %O.\n", attrs[attr], attr); \ ); \ attrs[attr] = attrs[attr][0]; \ } \ else \ attrs[attr] = 0; \ } \ } \ DO_IF_DEBUG ( \ else if (dn != "") \ werror ("Warning: Couldn't fetch attribute description for %O - " \ "multivalued attribute assumed.\n", attr); \ ); \ } \ res += ({attrs}); \ } \ } \ } \ \ else { \ foreach (rawres, string rawent) { \ object derent = .ldap_privates.ldap_der_decode (rawent)->elements[1]; \ if (array(object) derattribs = ASN1_GET_ATTR_ARRAY (derent)) { \ string dn = (SET_DN); \ mapping(string:array) attrs = (["dn": ({dn})]); \ foreach (derattribs, object derattr) { \ string attr; \ {SET_ATTR;} \ } \ res += ({attrs}); \ } \
4066882005-03-11Martin Stjernholm  } \ } \ } while (0) if (ldap_version < 3) { // Use the values raw.
cec0932005-03-11Martin Stjernholm  if (flags & SEARCH_LOWER_ATTRS)
6a40e72005-03-14Martin Stjernholm  DECODE_ENTRIES (ASN1_GET_DN (derent), {
4729292005-04-06Martin Stjernholm  attrs[attr = lower_case (ASN1_GET_ATTR_NAME (derattr))] =
6a40e72005-03-14Martin Stjernholm  ASN1_GET_ATTR_VALUES (derattr); });
4066882005-03-11Martin Stjernholm  else
6a40e72005-03-14Martin Stjernholm  DECODE_ENTRIES (ASN1_GET_DN (derent), {
4729292005-04-06Martin Stjernholm  attrs[attr = ASN1_GET_ATTR_NAME (derattr)] =
6a40e72005-03-14Martin Stjernholm  ASN1_GET_ATTR_VALUES (derattr); });
4066882005-03-11Martin Stjernholm  }
6e47642005-03-10Martin Stjernholm 
4066882005-03-11Martin Stjernholm  else { // LDAPv3: Decode values as appropriate according to the // schema. Note that attributes with the ";binary" option // won't be matched by get_attr_type_descr and are therefore // left untouched.
cec0932005-03-11Martin Stjernholm  if (flags & SEARCH_LOWER_ATTRS)
6a40e72005-03-14Martin Stjernholm  DECODE_ENTRIES (utf8_to_string (ASN1_GET_DN (derent)), {
4729292005-04-06Martin Stjernholm  attr = lower_case (ASN1_GET_ATTR_NAME (derattr));
15d1722005-03-29Martin Stjernholm  if (function(string:string) decoder = // Microsoft AD has several attributes in its root DSE // that they have not bothered to include in their // schema. So if this is the root being fetched then // send the nowarn flag to get_attr_encoder to avoid // complaints about that. get_attr_decoder (attr, DO_IF_DEBUG (dn == "")))
6a40e72005-03-14Martin Stjernholm  attrs[attr] = map (ASN1_GET_ATTR_VALUES (derattr), decoder); else attrs[attr] = ASN1_GET_ATTR_VALUES (derattr); });
4066882005-03-11Martin Stjernholm  else
6a40e72005-03-14Martin Stjernholm  DECODE_ENTRIES (utf8_to_string (ASN1_GET_DN (derent)), {
4729292005-04-06Martin Stjernholm  attr = ASN1_GET_ATTR_NAME (derattr);
15d1722005-03-29Martin Stjernholm  if (function(string:string) decoder = get_attr_decoder (attr, DO_IF_DEBUG (dn == "")))
6a40e72005-03-14Martin Stjernholm  attrs[attr] = map (ASN1_GET_ATTR_VALUES (derattr), decoder); else attrs[attr] = ASN1_GET_ATTR_VALUES (derattr); });
aa370a1999-04-24Johan Schön  }
4ad4cd2000-02-17Honza Petrous 
4066882005-03-11Martin Stjernholm #undef DECODE_ENTRIES
6659452003-09-01Martin Nilsson  return res;
6e47642005-03-10Martin Stjernholm  }
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  //! //! You can't create instances of this object yourself. //! The only way to create it is via a search of a LDAP server.
4066882005-03-11Martin Stjernholm  object|int create(array rawres, void|int stuff, void|int flags) {
aa370a1999-04-24Johan Schön  // rawres: array of result in raw format, but WITHOUT LDAP PDU !!! // stuff: 1=bind result; ...
4066882005-03-11Martin Stjernholm  this_program::flags = flags;
6e47642005-03-10Martin Stjernholm  // Note: Might do additional schema queries to the server while // decoding the result. That means possible interleaving problem // if search() is extended to not retrieve the complete reply at // once.
aa370a1999-04-24Johan Schön  int lastel = sizeof(rawres) - 1; if (lastel < 0) {
4598b32005-03-08Martin Stjernholm  seterr (LDAP_LOCAL_ERROR);
aa370a1999-04-24Johan Schön  THROW(({"LDAP: Internal error.\n",backtrace()}));
4598b32005-03-08Martin Stjernholm  return -ldap_errno;
aa370a1999-04-24Johan Schön  } DWRITE(sprintf("result.create: rawres=%O\n",rawres[lastel])); // The last element of 'rawres' is result itself resultcode = ASN1_DECODE_RESULTCODE(rawres[lastel]); DWRITE(sprintf("result.create: code=%d\n",resultcode)); resultstring = ASN1_DECODE_RESULTSTRING(rawres[lastel]);
c908692005-04-06Martin Stjernholm  if (resultstring == "") resultstring = 0; else if (ldap_version >= 3) resultstring = utf8_to_string (resultstring);
8157482005-04-20Martin Stjernholm  DWRITE(sprintf("result.create: str=%O\n",resultstring));
bf334d2001-08-15Honza Petrous #ifdef V3_REFERRALS
aa370a1999-04-24Johan Schön  // referral (v3 mode)
bf334d2001-08-15Honza Petrous  if(resultcode == 10) { referrals = ({}); foreach(ASN1_DECODE_RESULTREFS(rawres[lastel]), object ref1) referrals += ({ ref1->value }); DWRITE(sprintf("result.create: refs=%O\n",referrals)); } #endif
aa370a1999-04-24Johan Schön  DWRITE(sprintf("result.create: elements=%d\n",lastel+1));
cf4e0e2005-03-09Martin Stjernholm #if 0 DWRITE(sprintf("result.create: entries=%O\n",rawres[..lastel-1])); #endif
6e47642005-03-10Martin Stjernholm  entry = decode_entries (rawres[..lastel-1]);
aa370a1999-04-24Johan Schön  #if 0 // Context specific proccessing of 'rawres' switch(stuff) { case 1: DWRITE("result.create: stuff=1\n"); break; default: DWRITE(sprintf("result.create: stuff=%d\n", stuff)); } #endif
6659452003-09-01Martin Nilsson  return this;
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  } //!
c908692005-04-06Martin Stjernholm  //! Returns the error number in the search result.
0668612001-09-14Honza Petrous  //! //! @seealso
c908692005-04-06Martin Stjernholm  //! @[error_string], @[server_error_string]
6659452003-09-01Martin Nilsson  int error_number() { return resultcode; }
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  //!
c908692005-04-06Martin Stjernholm  //! Returns the description of the error in the search result. //! This is the error string from the server, or a standard error //! message corresponding to the error number if the server didn't //! provide any description.
0668612001-09-14Honza Petrous  //! //! @seealso
c908692005-04-06Martin Stjernholm  //! @[server_error_string], @[error_number]
0668612001-09-14Honza Petrous  string error_string() {
c908692005-04-06Martin Stjernholm  return resultstring || ldap_error_strings[resultcode];
0668612001-09-14Honza Petrous  }
aa370a1999-04-24Johan Schön 
c908692005-04-06Martin Stjernholm  //! Returns the error string from the server, or zero if the //! server didn't provide any. //! //! @seealso //! @[error_string], @[error_number] string server_error_string() {return resultstring;}
0668612001-09-14Honza Petrous  //! //! Returns the number of entries. //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.result.count_entries]
cf4e0e2005-03-09Martin Stjernholm  int num_entries() { return sizeof (entry); }
0668612001-09-14Honza Petrous  //! //! Returns the number of entries from current cursor //! possition till end of the list. //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.result.first], @[LDAP.client.result.next]
cf4e0e2005-03-09Martin Stjernholm  int count_entries() { return sizeof (entry) - actnum; }
0668612001-09-14Honza Petrous 
cf4e0e2005-03-09Martin Stjernholm  //! Returns the current entry pointed to by the cursor. //! a mapping with an entry for each attribute. Each value //! is an array of the values for the attribute. The
0668612001-09-14Honza Petrous  //!
9acfda2001-10-04Martin Nilsson  //! @param index
0668612001-09-14Honza Petrous  //! Optional argument can be used for direct access //! to the entry other then currently pointed by cursor.
cf4e0e2005-03-09Martin Stjernholm  //! //! @returns //! The return value is a mapping describing the entry: //! //! @mapping //! @member string attribute //! An attribute in the entry. The value is an array containing
f75dd42005-03-23Martin Stjernholm  //! the returned attribute value(s) on string form.
cf4e0e2005-03-09Martin Stjernholm  //! //! @member string "dn" //! This special entry contains the object name of the entry as //! a distinguished name. //! @endmapping //! //! Zero is returned if the cursor is outside the valid range of //! entries. //! //! @note
84e8f02005-04-06Martin Stjernholm  //! It's undefined whether or not destructive operations on the //! returned mapping will affect future @[fetch] calls for the //! same entry.
cf4e0e2005-03-09Martin Stjernholm  //!
a51bcf2005-03-14Martin Stjernholm  //! @note //! In Pike 7.6 and earlier, the special @expr{"dn"@} entry was //! incorrectly returned in UTF-8 encoded form for LDAPv3 //! connections. //!
cf4e0e2005-03-09Martin Stjernholm  //! @seealso //! @[fetch_all]
4729292005-04-06Martin Stjernholm  int|mapping(string:string|array(string)) fetch(int|void idx) {
0668612001-09-14Honza Petrous  if (!idx) idx = actnum + 1; if ((idx <= num_entries()) && (idx > 0)) { actnum = idx - 1;
6659452003-09-01Martin Nilsson  return entry[actnum];
aa370a1999-04-24Johan Schön  }
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
0668612001-09-14Honza Petrous  //!
4729292005-04-06Martin Stjernholm  //! Returns distinguished name (DN) of the current entry in the //! result list. Note that this is the same as getting the //! @expr{"dn"@} field from the return value from @[fetch].
a51bcf2005-03-14Martin Stjernholm  //! //! @note //! In Pike 7.6 and earlier, this field was incorrectly returned //! in UTF-8 encoded form for LDAPv3 connections.
4729292005-04-06Martin Stjernholm  string get_dn() { string|array(string) dn = fetch()->dn; return stringp (dn) ? dn : dn[0]; // To cope with SEARCH_MULTIVAL_ARRAYS_ONLY. }
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  //! //! Initialized the result cursor to the first entry //! in the result list. //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.result.next]
aa370a1999-04-24Johan Schön  void first() { actnum = 0; }
0668612001-09-14Honza Petrous  //! //! Moves the result cursor to the next entry //! in the result list. Returns number of remained entries //! in the result list. Returns 0 at the end. //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.result.next]
aa370a1999-04-24Johan Schön  int next() {
50cd592005-03-23Martin Stjernholm  if (actnum < sizeof (entry))
aa370a1999-04-24Johan Schön  actnum++;
50cd592005-03-23Martin Stjernholm  return count_entries();
aa370a1999-04-24Johan Schön  }
0668612001-09-14Honza Petrous 
4729292005-04-06Martin Stjernholm  array(mapping(string:string|array(string))) fetch_all()
cf4e0e2005-03-09Martin Stjernholm  //! Convenience function to fetch all entries at once. The cursor //! isn't affected. //! //! @returns //! Returns an array where each element is the entry from the //! result. Don't be destructive on the returned value. //! //! @seealso //! @[fetch] { return entry; }
aa370a1999-04-24Johan Schön  } // end of class 'result' ---------------
f96d1b2004-05-26Henrik Grubbström (Grubba)  // helper functions and macros #ifdef ENABLE_PAGED_SEARCH #define IF_ELSE_PAGED_SEARCH(X,Y) X #else /* !ENABLE_PAGED_SEARCH */ #define IF_ELSE_PAGED_SEARCH(X,Y) Y #endif #ifdef LDAP_PROTOCOL_PROFILE
d0fc872005-03-08Martin Stjernholm #define PROFILE(STR, CODE...) DWRITE_PROF(STR + ": %O\n", gauge {CODE;})
f96d1b2004-05-26Henrik Grubbström (Grubba) #else
d0fc872005-03-08Martin Stjernholm #define PROFILE(STR, CODE...) do { CODE; } while(0)
f96d1b2004-05-26Henrik Grubbström (Grubba) #endif
aa370a1999-04-24Johan Schön  private int chk_ver() { if ((ldap_version != 2) && (ldap_version != 3)) { seterr (LDAP_PROTOCOL_ERROR); THROW(({"LDAP: Unknown/unsupported protocol version.\n",backtrace()}));
6659452003-09-01Martin Nilsson  return -ldap_errno;
aa370a1999-04-24Johan Schön  }
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  } private int chk_binded() { // For version 2: we must be 'binded' first !!!
84e8f02005-04-06Martin Stjernholm  switch (ldap_version) { case 2: if (!bound_dn) { seterr (LDAP_PROTOCOL_ERROR); THROW(({"LDAP: Must bind first.\n",backtrace()})); return -ldap_errno; } break; case 3: if (!bound_dn) bind(); break;
aa370a1999-04-24Johan Schön  }
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  } private int chk_dn(string dn) { if ((!dn) || (!sizeof(dn))) { seterr (LDAP_INVALID_DN_SYNTAX); THROW(({"LDAP: Invalid DN syntax.\n",backtrace()}));
6659452003-09-01Martin Nilsson  return -ldap_errno;
aa370a1999-04-24Johan Schön  }
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
968b542001-09-14Honza Petrous  //! Several information about code itself and about active connection too
0668612001-09-14Honza Petrous  mapping info;
6e47642005-03-10Martin Stjernholm #ifndef PARSE_RFCS
0668612001-09-14Honza Petrous  //! @decl void create()
9acfda2001-10-04Martin Nilsson  //! @decl void create(string url) //! @decl void create(string url, object context)
0668612001-09-14Honza Petrous  //! //! Create object. The first optional argument can be used later //! for subsequence operations. The second one can specify
3c96592001-11-02H. William Welliver III  //! TLS context of connection. The default context only allows //! 128-bit encryption methods, so you may need to provide your //! own context if your LDAP server supports only export encryption.
0668612001-09-14Honza Petrous  //! //! @param url
cf4e0e2005-03-09Martin Stjernholm  //! LDAP server URL on the form //! @expr{"ldap://hostname/basedn?attrlist?scope?ext"@}. See RFC
84e8f02005-04-06Martin Stjernholm  //! 2255. It can also be a mapping as returned by //! @[Protocol.LDAP.parse_ldap_url].
0668612001-09-14Honza Petrous  //! //! @param context //! TLS context of connection //! //! @seealso
dbd5912001-09-14Honza Petrous  //! @[LDAP.client.bind], @[LDAP.client.search]
84e8f02005-04-06Martin Stjernholm  void create(string|mapping(string:mixed)|void url, object|void context)
aa370a1999-04-24Johan Schön  {
8157482005-04-20Martin Stjernholm  info = ([ "code_revision" : ("$Revision: 1.96 $"/" ")[1] ]);
0668612001-09-14Honza Petrous  if(!url || !sizeof(url)) url = LDAP_DEFAULT_URL;
fc02d12000-07-20Honza Petrous 
84e8f02005-04-06Martin Stjernholm  if (mappingp (url)) lauth = url; else lauth = parse_ldap_url(url);
fc02d12000-07-20Honza Petrous 
0668612001-09-14Honza Petrous  if(!stringp(lauth->scheme) ||
6c23602002-07-31Martin Nilsson  ((lauth->scheme != "ldap")
ca6e1f2004-02-29Martin Nilsson #if constant(SSL.Cipher.CipherAlgorithm)
6c23602002-07-31Martin Nilsson  && (lauth->scheme != "ldaps") #endif )) {
fc02d12000-07-20Honza Petrous  THROW(({"Unknown scheme in server URL.\n",backtrace()})); }
59a35e2000-07-25Honza Petrous  if(!lauth->host)
0668612001-09-14Honza Petrous  lauth += ([ "host" : LDAP_DEFAULT_HOST ]);
59a35e2000-07-25Honza Petrous  if(!lauth->port)
0668612001-09-14Honza Petrous  lauth += ([ "port" : lauth->scheme == "ldap" ? LDAP_DEFAULT_PORT : LDAPS_DEFAULT_PORT ]);
ca6e1f2004-02-29Martin Nilsson #if constant(SSL.Cipher.CipherAlgorithm)
0668612001-09-14Honza Petrous  if(lauth->scheme == "ldaps" && !context) { context = SSL.context(); // Allow only strong crypto context->preferred_suites = ({ SSL_rsa_with_idea_cbc_sha, SSL_rsa_with_rc4_128_sha, SSL_rsa_with_rc4_128_md5, SSL_rsa_with_3des_ede_cbc_sha, }); }
6c23602002-07-31Martin Nilsson #endif
4cb56b2004-10-13H. William Welliver III  if(!(low_fd->connect(lauth->host, lauth->port))) {
0668612001-09-14Honza Petrous  //errno = ldapfd->errno(); seterr (LDAP_SERVER_DOWN); DWRITE("client.create: ERROR: can't open socket.\n"); //ldapfd->destroy(); //ldap=0; //ok = 0; //if(con_fail)
563bd72004-01-11Martin Nilsson  // con_fail(this, @extra_args);
aa370a1999-04-24Johan Schön  THROW(({"Failed to connect to LDAP server.\n",backtrace()})); }
0668612001-09-14Honza Petrous 
ca6e1f2004-02-29Martin Nilsson #if constant(SSL.Cipher.CipherAlgorithm)
0668612001-09-14Honza Petrous  if(lauth->scheme == "ldaps") {
f90e3c2004-02-03Martin Nilsson  context->random = Crypto.Random.random_string;
4cb56b2004-10-13H. William Welliver III  ::create(SSL.sslfile(low_fd, context, 1,1));
0668612001-09-14Honza Petrous  info->tls_version = ldapfd->version; } else
4cb56b2004-10-13H. William Welliver III  ::create(low_fd);
828dd02002-09-05H. William Welliver III #else if(lauth->scheme == "ldaps") { THROW(({"LDAP: LDAPS is not available without SSL support.\n",backtrace()})); } else
4cb56b2004-10-13H. William Welliver III  ::create(low_fd);
6c23602002-07-31Martin Nilsson #endif
0668612001-09-14Honza Petrous  DWRITE("client.create: connected!\n");
cf4e0e2005-03-09Martin Stjernholm  DWRITE(sprintf("client.create: remote = %s\n", low_fd->query_address()));
59a35e2000-07-25Honza Petrous  DWRITE_HI("client.OPEN: " + lauth->host + ":" + (string)(lauth->port) + " - OK\n");
aa370a1999-04-24Johan Schön 
84e8f02005-04-06Martin Stjernholm  reset_options();
aa370a1999-04-24Johan Schön  } // create
6e47642005-03-10Martin Stjernholm #endif
aa370a1999-04-24Johan Schön 
84e8f02005-04-06Martin Stjernholm void reset_options() //! Resets all connection options, such as the scope and the base DN, //! to the defaults determined from the LDAP URL when the connection //! was created. { set_scope (lauth->scope || SCOPE_BASE); set_basedn (lauth->basedn); ldap_deref = 0; ldap_sizelimit = 0; ldap_timelimit = 0; last_rv = 0; }
aa370a1999-04-24Johan Schön  private mixed send_bind_op(string name, string password) { // Simple BIND operation object msgval, vers, namedn, auth, app;
8b4c172003-07-01Anders Johansson  string pass = password; password = "censored";
aa370a1999-04-24Johan Schön  vers = Standards.ASN1.Types.asn1_integer(ldap_version); namedn = Standards.ASN1.Types.asn1_octet_string(name);
8b4c172003-07-01Anders Johansson  auth = ASN1_CONTEXT_OCTET_STRING(0, pass);
aa370a1999-04-24Johan Schön  // SASL credentials ommited msgval = ASN1_APPLICATION_SEQUENCE(0, ({vers, namedn, auth}));
6659452003-09-01Martin Nilsson  return do_op(msgval);
aa370a1999-04-24Johan Schön  }
4cb56b2004-10-13H. William Welliver III  private mixed send_starttls_op(object|void context) { object msgval; #if constant(SSL.Cipher.CipherAlgorithm) // can we do this now? if(ldapfd->context) { THROW(({"LDAP: TLS/SSL already established.\n",backtrace()})); } // NOTE: should we be on the lookout for requests in flight? msgval = ASN1_APPLICATION_SEQUENCE(23, ({Standards.ASN1.Types.OctetString("1.3.6.1.4.1.1466.20037")})); do_op(msgval); int result = ASN1_DECODE_RESULTCODE(readbuf); if(result!=0) return 0; // otherwise, we can try to negotiate. if(!context) { context = SSL.context(); // Allow only strong crypto context->preferred_suites = ({ SSL_rsa_with_idea_cbc_sha, SSL_rsa_with_rc4_128_sha, SSL_rsa_with_rc4_128_md5, SSL_rsa_with_3des_ede_cbc_sha, }); } object _f = ldapfd; ldapfd=SSL.sslfile(_f, context, 1, 1); return 1; #endif return 0; } //! Requests that a SSL/TLS session be negotiated on the connection. //! If the connection is already secure, this call will fail. //! //! @param context //! an optional SSL.context object to provide to the //! SSL/TLS connection client. //! //! Returns @expr{1@} on success, @expr{0@} otherwise. //! int start_tls (void|SSL.context context) { int id; mixed raw; if(ldap_version < 3) { seterr (LDAP_PROTOCOL_ERROR); THROW(({"LDAP: Unknown/unsupported protocol version.\n",backtrace()})); return -ldap_errno; }
5149432004-10-14H. William Welliver III  return send_starttls_op(context||UNDEFINED);
4cb56b2004-10-13H. William Welliver III  return 1; } // start_tls //! @decl int bind()
9acfda2001-10-04Martin Nilsson  //! @decl int bind(string dn, string password) //! @decl int bind(string dn, string password, int version)
0668612001-09-14Honza Petrous  //! //! Authenticates connection to the direcory. //! //! First form uses default value previously entered in create. //! //! Second form uses value from parameters: //! //! @param dn //! The distinguished name (DN) of an entry aginst which will //! be made authentication. //! @param password //! Password used for authentication. //! //! Third form allows specify the version of LDAP protocol used //! by connection to the LDAP server. //! //! @param version
cbe8c92003-04-07Martin Nilsson  //! Only @expr{2@} or @expr{3@} can be entered.
0668612001-09-14Honza Petrous  //!
bcc2352002-07-12Honza Petrous  //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns @expr{1@} on success, @expr{0@} otherwise.
bcc2352002-07-12Honza Petrous  //!
0668612001-09-14Honza Petrous  //! @note //! Only simple authentication type is implemented. So be warned //! clear text passwords are sent to the directory server.
bcc2352002-07-12Honza Petrous  //! //! @note //! The API change: the returning code was changed in Pike 7.3+
4cb56b2004-10-13H. William Welliver III  //! to follow his logic better.
0668612001-09-14Honza Petrous  int bind (string|void dn, string|void password, int|void version) {
aa370a1999-04-24Johan Schön  int id; mixed raw;
8b4c172003-07-01Anders Johansson  string pass = password; password = "censored";
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  if (!version) version = LDAP_DEFAULT_VERSION;
aa370a1999-04-24Johan Schön  if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
84e8f02005-04-06Martin Stjernholm  if (bound_dn && ldap_version <= 2) { ERROR ("Can't bind a connection more than once in LDAPv2.\n"); return 0; }
0668612001-09-14Honza Petrous  if (!stringp(dn)) dn = mappingp(lauth->ext) ? lauth->ext->bindname||"" : "";
8b4c172003-07-01Anders Johansson  if (!stringp(pass)) pass = "";
0668612001-09-14Honza Petrous  ldap_version = version;
aa370a1999-04-24Johan Schön  if(ldap_version == 3) {
0668612001-09-14Honza Petrous  dn = string_to_utf8(dn);
8b4c172003-07-01Anders Johansson  pass = string_to_utf8(pass);
aa370a1999-04-24Johan Schön  }
8b4c172003-07-01Anders Johansson  if(intp(raw = send_bind_op(dn, pass))) {
aa370a1999-04-24Johan Schön  THROW(({error_string()+"\n",backtrace()}));
84e8f02005-04-06Martin Stjernholm  return 0;
aa370a1999-04-24Johan Schön  }
84e8f02005-04-06Martin Stjernholm  bound_dn = 0;
bcc2352002-07-12Honza Petrous  last_rv = result(({raw}),1); if (!last_rv->error_number())
84e8f02005-04-06Martin Stjernholm  bound_dn = dn;
bcc2352002-07-12Honza Petrous  DWRITE_HI(sprintf("client.BIND: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
84e8f02005-04-06Martin Stjernholm  return !!bound_dn;
aa370a1999-04-24Johan Schön  } // bind
4cb56b2004-10-13H. William Welliver III 
aa370a1999-04-24Johan Schön  private int send_unbind_op() { // UNBIND operation writemsg(ASN1_APPLICATION_OCTET_STRING(2, "")); //ldap::close();
6659452003-09-01Martin Nilsson  return 1;
aa370a1999-04-24Johan Schön  }
e371662004-09-14Martin Stjernholm #if 0
aa370a1999-04-24Johan Schön  void destroy() { //send_unbind_op();
e371662004-09-14Martin Stjernholm  // Hazard area: General confusion error. /mast //destruct(this);
aa370a1999-04-24Johan Schön  }
e371662004-09-14Martin Stjernholm #endif
aa370a1999-04-24Johan Schön 
0668612001-09-14Honza Petrous  //! //! Unbinds from the directory and close the connection.
aa370a1999-04-24Johan Schön  int unbind () { if (send_unbind_op() < 1) { THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return -ldap_errno;
aa370a1999-04-24Johan Schön  }
84e8f02005-04-06Martin Stjernholm  bound_dn = 0;
aa370a1999-04-24Johan Schön  DWRITE_HI("client.UNBIND: OK\n"); } // unbind private int|string send_op_withdn(int op, string dn) { // DELETE, ...
6659452003-09-01Martin Nilsson  return do_op(ASN1_APPLICATION_OCTET_STRING(op, dn));
aa370a1999-04-24Johan Schön  }
0668612001-09-14Honza Petrous  //! //! Deletes entry from the LDAP server. //! //! @param dn //! The distinguished name of deleted entry.
bcc2352002-07-12Honza Petrous  //!
acd2582002-07-13Honza Petrous  //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns @expr{1@} on success, @expr{0@} otherwise.
acd2582002-07-13Honza Petrous  //!
bcc2352002-07-12Honza Petrous  //! @note //! The API change: the returning code was changed in Pike 7.3+ //! to follow his logic better.
aa370a1999-04-24Johan Schön  int delete (string dn) { int id; mixed raw; if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_dn(dn))
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if(ldap_version == 3) {
6f3cc51999-08-12Marcus Comstedt  dn = string_to_utf8(dn);
aa370a1999-04-24Johan Schön  } if(intp(raw = send_op_withdn(10, dn))) { THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
bcc2352002-07-12Honza Petrous  last_rv = result(({raw})); DWRITE_HI(sprintf("client.DELETE: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
6659452003-09-01Martin Nilsson  return !last_rv->error_number();
aa370a1999-04-24Johan Schön  } // delete
8d06462005-03-10Martin Stjernholm  private int|string send_compare_op(string dn, string attr, string value) {
aa370a1999-04-24Johan Schön  // COMPARE object msgval; msgval = ASN1_APPLICATION_SEQUENCE(14, ({ Standards.ASN1.Types.asn1_octet_string(dn), Standards.ASN1.Types.asn1_sequence(
8d06462005-03-10Martin Stjernholm  ({ Standards.ASN1.Types.asn1_octet_string(attr), Standards.ASN1.Types.asn1_octet_string(value)
aa370a1999-04-24Johan Schön  })) }) );
6659452003-09-01Martin Nilsson  return do_op(msgval);
aa370a1999-04-24Johan Schön  }
0668612001-09-14Honza Petrous  //!
8d06462005-03-10Martin Stjernholm  //! Compares an attribute value with one in the directory.
dbd5912001-09-14Honza Petrous  //! //! @param dn
8d06462005-03-10Martin Stjernholm  //! The distinguished name of the entry.
dbd5912001-09-14Honza Petrous  //!
8d06462005-03-10Martin Stjernholm  //! @param attr //! The type (aka name) of the attribute to compare. //! //! @param value
6e47642005-03-10Martin Stjernholm  //! The value to compare with. It's UTF-8 encoded automatically if //! the attribute syntax specifies that.
bcc2352002-07-12Honza Petrous  //!
acd2582002-07-13Honza Petrous  //! @returns
8d06462005-03-10Martin Stjernholm  //! Returns @expr{1@} if at least one of the values for the
dbb5892005-03-13Martin Stjernholm  //! attribute in the directory is equal to @[value], @expr{0@} if //! it didn't match or there was some error (use @[error_number] to //! find out).
acd2582002-07-13Honza Petrous  //!
bcc2352002-07-12Honza Petrous  //! @note
dbb5892005-03-13Martin Stjernholm  //! This function has changed arguments since version 7.6. From //! 7.3 to 7.6 it was effectively useless since it always returned //! true.
8d06462005-03-10Martin Stjernholm  //! //! @note //! The equality matching rule for the attribute governs the //! comparison. There are attributes where the assertion syntax //! used here isn't the same as the attribute value syntax. int compare (string dn, string attr, string value) {
aa370a1999-04-24Johan Schön  int id; mixed raw; // if (!aval || sizeof(aval)<2) // error if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_dn(dn))
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if(ldap_version == 3) {
6f3cc51999-08-12Marcus Comstedt  dn = string_to_utf8(dn);
6e47642005-03-10Martin Stjernholm  if (function(string:string) encoder = get_attr_encoder (attr)) value = encoder (value);
aa370a1999-04-24Johan Schön  }
8d06462005-03-10Martin Stjernholm  if(intp(raw = send_compare_op(dn, attr, value))) {
aa370a1999-04-24Johan Schön  THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
bcc2352002-07-12Honza Petrous  last_rv = result(({raw})); DWRITE_HI(sprintf("client.COMPARE: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
8d06462005-03-10Martin Stjernholm  return last_rv->error_number() == LDAP_COMPARE_TRUE;
aa370a1999-04-24Johan Schön  } // compare private int|string send_add_op(string dn, mapping(string:array(string)) attrs) { // ADD object msgval; string atype; array(object) oatt = ({}); foreach(indices(attrs), atype) { string aval; array(object) ohlp = ({}); foreach(values(attrs[atype]), aval) ohlp += ({Standards.ASN1.Types.asn1_octet_string(aval)}); oatt += ({Standards.ASN1.Types.asn1_sequence( ({Standards.ASN1.Types.asn1_octet_string(atype), Standards.ASN1.Types.asn1_set(ohlp) })) }); } msgval = ASN1_APPLICATION_SEQUENCE(8, ({Standards.ASN1.Types.asn1_octet_string(dn), Standards.ASN1.Types.asn1_sequence(oatt) }));
6659452003-09-01Martin Nilsson  return do_op(msgval);
aa370a1999-04-24Johan Schön  }
3c96592001-11-02H. William Welliver III  //! The Add Operation allows a client to request the addition //! of an entry into the directory //! //! @param dn //! The Distinguished Name of the entry to be added. //! //! @param attrs
6e47642005-03-10Martin Stjernholm  //! The mapping of attributes and their values that make up the //! content of the entry being added. Values that are sent UTF-8 //! encoded according the the attribute syntaxes are encoded //! automatically.
acd2582002-07-13Honza Petrous  //! //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns @expr{1@} on success, @expr{0@} otherwise.
3c96592001-11-02H. William Welliver III  //!
bcc2352002-07-12Honza Petrous  //! @note //! The API change: the returning code was changed in Pike 7.3+ //! to follow his logic better.
aa370a1999-04-24Johan Schön  int add (string dn, mapping(string:array(string)) attrs) { int id; mixed raw; if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_dn(dn))
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if(ldap_version == 3) {
6f3cc51999-08-12Marcus Comstedt  dn = string_to_utf8(dn);
6e47642005-03-10Martin Stjernholm  attrs += ([]); // No need to UTF-8 encode the attribute names themselves since // only ascii chars are allowed in them. foreach (indices(attrs), string attr) if (function(string:string) encoder = get_attr_encoder (attr)) attrs[attr] = map (attrs[attr], encoder);
aa370a1999-04-24Johan Schön  } if(intp(raw = send_add_op(dn, attrs))) { THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
bcc2352002-07-12Honza Petrous  last_rv = result(({raw})); DWRITE_HI(sprintf("client.ADD: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
6659452003-09-01Martin Nilsson  return !last_rv->error_number();
aa370a1999-04-24Johan Schön  } // add
f75dd42005-03-23Martin Stjernholm static mapping(string:array(string)) simple_read (string object_name,
49ae3a2005-04-07Martin Stjernholm  object filter,
f75dd42005-03-23Martin Stjernholm  array attrs)
6e47642005-03-10Martin Stjernholm // Makes a base object search for object_name. The result is returned // as a mapping where the attribute types have been lowercased and the // string values are unprocessed. { object|int search_request = make_search_op(object_name, 0, 0, 0, 0, 0, filter, attrs); //werror("search_request: %O\n", search_request); string|int raw; if(intp(raw = do_op(search_request))) { THROW(({error_string()+"\n",backtrace()})); return 0; } mapping(string:array(string)) res = ([]); do { object response = .ldap_privates.ldap_der_decode(raw); if (ASN1_GET_RESULTAPP (response) == 5) break; //werror("res: %O\n", res); response = response->elements[1]; foreach(ASN1_GET_ATTR_ARRAY (response), object attr) res[lower_case (ASN1_GET_ATTR_NAME (attr))] = ASN1_GET_ATTR_VALUES (attr); // NB: The msgid stuff is defunct in readmsg, so we can // just as well pass a zero there. :P if (intp(raw = readmsg(0))) { THROW(({error_string()+"\n",backtrace()})); return 0; } } while (1); return res; } static mapping(string:array(string)) root_dse;
2130652005-03-23Martin Stjernholm array(string) get_root_dse_attr (string attr) //! Returns the value of an attribute in the root DSE (DSA-Specific //! Entry) of the bound server. The result is cached. A working //! connection is assumed.
6e47642005-03-10Martin Stjernholm //! //! @returns
2130652005-03-23Martin Stjernholm //! The return value is an array of the attribute values, which have //! been UTF-8 decoded where appropriate.
6e47642005-03-10Martin Stjernholm //!
2130652005-03-23Martin Stjernholm //! Don't be destructive on the returned array.
4729292005-04-06Martin Stjernholm //! //! @note //! This function intentionally does not try to simplify the return //! values for single-valued attributes (c.f. //! @[Protocols.LDAP.SEARCH_MULTIVAL_ARRAYS_ONLY]). That since (at //! least) Microsoft AD has a bunch of attributes in the root DSE //! that they don't bother to provide schema entries for. The return //! value format wouldn't be reliable if they suddenly change that.
6e47642005-03-10Martin Stjernholm {
2130652005-03-23Martin Stjernholm  attr = lower_case (attr); if (!root_dse || zero_type (root_dse[attr])) { PROFILE("get_root_dse_attr", { multiset(string) attrs = root_dse ? (<>) : // Get a bunch of attributes in one go. (<
6e47642005-03-10Martin Stjernholm  // Request all standard operational attributes (RFC 2252, // section 5.1). Some of them are probably not applicable // in the root DSE, but better safe than sorry.
2130652005-03-23Martin Stjernholm  "createtimestamp", "modifytimestamp", "creatorsname", "modifiersname", "subschemasubentry", "attributetypes", "objectclasses", "matchingrules", "matchingruleuse",
6e47642005-03-10Martin Stjernholm  // Request the standard root DSE operational attributes // (RFC 2252, section 5.2).
2130652005-03-23Martin Stjernholm  "namingcontexts", "altserver", "supportedextension", "supportedcontrol", "supportedsaslmechanisms", "supportedldapversion", >); attrs[attr] = 1; mapping(string:array(string)) res =
8cc0002005-04-07Martin Stjernholm  simple_read ("", get_cached_filter ("(objectClass=*)"), indices (attrs));
2130652005-03-23Martin Stjernholm  foreach (indices (res), string attr)
6e47642005-03-10Martin Stjernholm  // Microsoft AD has several attributes in its root DSE that // they haven't bothered to include in their schema. Send // the nowarn flag to get_attr_encoder to avoid complaints // about that.
3baf5c2005-03-10Martin Stjernholm  if (function(string:string) decoder = get_attr_decoder (attr, DO_IF_DEBUG (1)))
2130652005-03-23Martin Stjernholm  res[attr] = map (res[attr], decoder); if (root_dse) root_dse[attr] = res[attr]; else { root_dse = res; if (!root_dse[attr]) root_dse[attr] = 0; }
6e47642005-03-10Martin Stjernholm  }); }
2130652005-03-23Martin Stjernholm  return root_dse[attr];
6e47642005-03-10Martin Stjernholm }
9434432005-02-03Martin Stjernholm static object make_control (string control_type, void|string value, void|int critical) { array(object) seq = ({Standards.ASN1.Types.asn1_octet_string (control_type), ASN1_BOOLEAN (critical)}); if (value) seq += ({Standards.ASN1.Types.asn1_octet_string (value)}); return Standards.ASN1.Types.asn1_sequence (seq); } static multiset(string) supported_controls; multiset(string) get_supported_controls() //! Returns a multiset containing the controls supported by the //! server. They are returned as object identifiers on string form. //! A working connection is assumed. //! //! @seealso //! @[search] {
6e47642005-03-10Martin Stjernholm  if (!supported_controls)
2130652005-03-23Martin Stjernholm  if (array(string) res = get_root_dse_attr ("supportedControl")) supported_controls = mkmultiset (res);
9434432005-02-03Martin Stjernholm  return supported_controls; }
49ae3a2005-04-07Martin Stjernholm object make_filter (string filter) //! Returns the ASN1 object parsed from the given filter. This is a //! wrapper for @[Protocols.LDAP.make_filter] which parses the filter //! with the LDAP protocol version currently in use by this //! connection. //! //! @throws //! If there's a parse error in the filter then a //! @[Protocols.LDAP.FilterError] is thrown as from //! @[Protocols.LDAP.make_filter]. { return Protocols.LDAP.make_filter (filter, ldap_version); }
aa370a1999-04-24Johan Schön 
8cc0002005-04-07Martin Stjernholm object get_cached_filter (string filter) //! This is a wrapper for @[Protocols.LDAP.get_cached_filter] which //! passes the LDAP protocol version currently in use by this //! connection. //! //! @throws //! If there's a parse error in the filter then a //! @[Protocols.LDAP.FilterError] is thrown as from //! @[Protocols.LDAP.make_filter]. { return Protocols.LDAP.get_cached_filter (filter, ldap_version); }
49ae3a2005-04-07Martin Stjernholm object get_default_filter() //! Returns the ASN1 object parsed from the filter specified in the //! LDAP URL, or zero if the URL doesn't specify any filter. //! //! @throws //! If there's a parse error in the filter then a //! @[Protocols.LDAP.FilterError] is thrown as from //! @[Protocols.LDAP.make_filter]. { if (!default_filter_obj && lauth->filter) default_filter_obj = make_filter (lauth->filter); return default_filter_obj; }
aa370a1999-04-24Johan Schön 
4f57c32004-05-25Henrik Grubbström (Grubba)  private object|int make_search_op(string basedn, int scope, int deref, int sizelimit, int timelimit,
49ae3a2005-04-07Martin Stjernholm  int attrsonly, object filter,
4f57c32004-05-25Henrik Grubbström (Grubba)  void|array(string) attrs) { // SEARCH
aa370a1999-04-24Johan Schön  // limitations: !!! sizelimit and timelimit should be unsigned int !!!
49ae3a2005-04-07Martin Stjernholm  object msgval;
aa370a1999-04-24Johan Schön  array(object) ohlp;
49ae3a2005-04-07Martin Stjernholm  ohlp = ({filter});
aa370a1999-04-24Johan Schön  if (arrayp(attrs)) { //explicitly defined attributes array(object) o2 = ({}); foreach(attrs, string s2) o2 += ({Standards.ASN1.Types.asn1_octet_string(s2)}); ohlp += ({Standards.ASN1.Types.asn1_sequence(o2)}); } else ohlp += ({Standards.ASN1.Types.asn1_sequence(({}))});
4f57c32004-05-25Henrik Grubbström (Grubba)  return ASN1_APPLICATION_SEQUENCE(3,
aa370a1999-04-24Johan Schön  ({ Standards.ASN1.Types.asn1_octet_string(basedn), ASN1_ENUMERATED(scope), ASN1_ENUMERATED(deref), Standards.ASN1.Types.asn1_integer(sizelimit), Standards.ASN1.Types.asn1_integer(timelimit), ASN1_BOOLEAN(attrsonly ? -1 : 0), @ohlp })) ; }
bcc2352002-07-12Honza Petrous  //! Search LDAP directory. //!
66f19d2002-02-14Martin Nilsson  //! @param filter
49ae3a2005-04-07Martin Stjernholm  //! Search filter to override the one from the LDAP URL. It's //! either a string with the format specified in RFC 2254, or an //! object returned by @[Protocols.LDAP.make_filter].
bcc2352002-07-12Honza Petrous  //!
66f19d2002-02-14Martin Nilsson  //! @param attrs
49ae3a2005-04-07Martin Stjernholm  //! The array of attribute names which will be returned by server
bcc2352002-07-12Honza Petrous  //! for every entry. //! //! @param attrsonly
49ae3a2005-04-07Martin Stjernholm  //! This flag causes server return only the attribute types (aka //! names) but not their values. The values will instead be empty //! arrays or - if @[Protocols.LDAP.SEARCH_MULTIVAL_ARRAYS_ONLY] //! is given - zeroes for single-valued attributes.
bcc2352002-07-12Honza Petrous  //!
9434432005-02-03Martin Stjernholm  //! @param controls //! Extra controls to send in the search query, to modify how the //! server executes the search in various ways. The value is a //! mapping with an entry for each control. //!
cf4e0e2005-03-09Martin Stjernholm  //! @mapping //! @member string object_identifier //! The index is the object identifier in string form for the //! control type. There are constants in @[Protocols.LDAP] for //! the object identifiers for some known controls. //! //! The mapping value is an array of two elements: //! //! @array //! @elem int 0 //! The first element is an integer flag that specifies //! whether the control is critical or not. If it is nonzero, //! the server returns an error if it doesn't understand the //! control. If it is zero, the server ignores it instead. //! //! @elem string|int(0..0) 1 //! The second element is the string value to pass with the //! control. It may also be zero to not pass any value at all. //! @endarray //! @endmapping
9434432005-02-03Martin Stjernholm  //!
4066882005-03-11Martin Stjernholm  //! @param flags //! Bitfield with flags to control various behavior at the client //! side of the search operation. See the //! @expr{Protocol.LDAP.SEARCH_*@} constants for details. //!
bcc2352002-07-12Honza Petrous  //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns object @[LDAP.client.result] on success, @expr{0@}
bcc2352002-07-12Honza Petrous  //! otherwise. //! //! @note //! The API change: the returning code was changed in Pike 7.3+ //! to follow his logic better. //! //! @seealso
f75dd42005-03-23Martin Stjernholm  //! @[result], @[result.fetch], @[read], @[get_supported_controls],
49ae3a2005-04-07Martin Stjernholm  //! @[Protocols.LDAP.quote_filter_value], @[Protocols.LDAP.make_filter] result|int search (string|object|void filter, array(string)|void attrs,
9434432005-02-03Martin Stjernholm  int|void attrsonly,
4066882005-03-11Martin Stjernholm  void|mapping(string:array(int|string)) controls, void|int flags) {
aa370a1999-04-24Johan Schön 
b080de2001-06-21Honza Petrous  int id,nv;
aa370a1999-04-24Johan Schön  mixed raw; array(string) rawarr = ({}); DWRITE_HI("client.SEARCH: " + (string)filter + "\n"); if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
49ae3a2005-04-07Martin Stjernholm  if (!objectp (filter)) if (mixed err = catch { if (!filter) filter = get_default_filter(); else filter = make_filter (filter); }) { if (objectp (err) && err->is_ldap_filter_error) { seterr (LDAP_FILTER_ERROR); return 0; } else throw (err); }
4f57c32004-05-25Henrik Grubbström (Grubba) 
9434432005-02-03Martin Stjernholm  object|int search_request =
4f57c32004-05-25Henrik Grubbström (Grubba)  make_search_op(ldap_basedn, ldap_scope, ldap_deref, ldap_sizelimit, ldap_timelimit, attrsonly, filter, attrs||lauth->attributes); if(intp(search_request)) {
aa370a1999-04-24Johan Schön  THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
4f57c32004-05-25Henrik Grubbström (Grubba) 
9434432005-02-03Martin Stjernholm  array(object) common_controls; if (controls) { common_controls = allocate (sizeof (controls)); int i; foreach (controls; string type; array(int|string) data) common_controls[i++] = make_control (type, [string] data[1], [int] data[0]); } else common_controls = ({}); #if 0 // Microsoft AD stuff that previously was added by default. There // doesn't appear to be a good reason for it. It's now possible // for the caller to do it, anyway. /mast
cec0932005-03-11Martin Stjernholm  if (get_supported_controls()[LDAP_SERVER_DOMAIN_SCOPE_OID]) {
9434432005-02-03Martin Stjernholm  // LDAP_SERVER_DOMAIN_SCOPE_OID // "Tells server not to generate referrals" (NtLdap.h)
cec0932005-03-11Martin Stjernholm  common_controls += ({make_control (LDAP_SERVER_DOMAIN_SCOPE_OID)});
9434432005-02-03Martin Stjernholm  } #endif #ifdef ENABLE_PAGED_SEARCH get_supported_controls(); #endif
4f57c32004-05-25Henrik Grubbström (Grubba)  object cookie = Standards.ASN1.Types.asn1_octet_string(""); rawarr = ({}); do {
f96d1b2004-05-26Henrik Grubbström (Grubba)  PROFILE("send_search_op", {
9434432005-02-03Martin Stjernholm  array ctrls = common_controls; IF_ELSE_PAGED_SEARCH (
cec0932005-03-11Martin Stjernholm  if (supported_controls[LDAP_PAGED_RESULT_OID_STRING]) {
1818952004-06-18Henrik Grubbström (Grubba)  // LDAP Control Extension for Simple Paged Results Manipulation // RFC 2696.
9434432005-02-03Martin Stjernholm  ctrls += ({make_control (
cec0932005-03-11Martin Stjernholm  LDAP_PAGED_RESULT_OID_STRING,
9434432005-02-03Martin Stjernholm  Standards.ASN1.Types.asn1_sequence( ({ // size Standards.ASN1.Types.asn1_integer(0x7fffffff), cookie, // cookie }))->get_der(),
8157482005-04-20Martin Stjernholm  sizeof(cookie->value))});
9434432005-02-03Martin Stjernholm  },); object controls; if (sizeof(ctrls)) { controls = .ldap_privates.asn1_sequence(0, ctrls); }
f96d1b2004-05-26Henrik Grubbström (Grubba) 
9434432005-02-03Martin Stjernholm  if(intp(raw = do_op(search_request, controls))) {
4f57c32004-05-25Henrik Grubbström (Grubba)  THROW(({error_string()+"\n",backtrace()})); return 0; }
f96d1b2004-05-26Henrik Grubbström (Grubba)  });
aa370a1999-04-24Johan Schön 
f96d1b2004-05-26Henrik Grubbström (Grubba)  PROFILE("rawarr++", { rawarr += ({raw});
4f57c32004-05-25Henrik Grubbström (Grubba)  while (ASN1_DECODE_RESULTAPP(raw) != 5) {
f96d1b2004-05-26Henrik Grubbström (Grubba)  PROFILE("readmsg", raw = readmsg(id));
4f57c32004-05-25Henrik Grubbström (Grubba)  if (intp(raw)) { THROW(({error_string()+"\n",backtrace()})); return 0; } rawarr += ({raw}); } // while
f96d1b2004-05-26Henrik Grubbström (Grubba)  });
4f57c32004-05-25Henrik Grubbström (Grubba)  // At this point @[raw] contains a SearchResultDone. cookie = 0;
f96d1b2004-05-26Henrik Grubbström (Grubba)  IF_ELSE_PAGED_SEARCH({ if ((ASN1_DECODE_RESULTCODE(raw) != 10) &&
8648672004-06-18Henrik Grubbström (Grubba)  (sizeof(.ldap_privates.ldap_der_decode(raw)->elements) > 2)) { object controls = .ldap_privates.ldap_der_decode(raw)->elements[2]; foreach(controls->elements, object control) { if (!control->constructed || !sizeof(control) || control->elements[0]->type_name != "OCTET STRING") { //werror("Protocol error in control %O\n", control); // FIXME: Fail? continue; }
9434432005-02-03Martin Stjernholm  if (control->elements[0]->value !=
cec0932005-03-11Martin Stjernholm  LDAP_PAGED_RESULT_OID_STRING) {
8648672004-06-18Henrik Grubbström (Grubba)  //werror("Unknown control %O\n", control->elements[0]->value); // FIXME: Should look at criticallity flag. continue; } if (sizeof(control) == 1) continue; int pos = 1; if (control->elements[1]->type_name == "BOOLEAN") { if (sizeof(control) == 2) continue; pos = 2; } if (control->elements[pos]->type_name != "OCTET STRING") { // FIXME: Error? continue; } object control_info = .ldap_privates.ldap_der_decode(control->elements[pos]->value); if (!control_info->constructed || sizeof(control_info) < 2 || control_info->elements[1]->type_name != "OCTET STRING") { // Unexpected control information. continue; } if (sizeof(control_info->elements[1]->value)) { cookie = control_info->elements[1]; } } if (cookie) {
f96d1b2004-05-26Henrik Grubbström (Grubba)  // Remove the extra end marker. rawarr = rawarr[..sizeof(rawarr)-2]; } }
8648672004-06-18Henrik Grubbström (Grubba) 
f96d1b2004-05-26Henrik Grubbström (Grubba)  },);
4f57c32004-05-25Henrik Grubbström (Grubba)  } while (cookie);
f96d1b2004-05-26Henrik Grubbström (Grubba) 
4066882005-03-11Martin Stjernholm  PROFILE("result", last_rv = result (rawarr, 0, flags));
bcc2352002-07-12Honza Petrous  if(objectp(last_rv))
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
aa370a1999-04-24Johan Schön  //if (rv->error_number() || !rv->num_entries()) // if error or entries=0 // rv = rv->error_number();
bcc2352002-07-12Honza Petrous  DWRITE_HI(sprintf("client.SEARCH: %s (entries: %d)\n", last_rv->error_string(), last_rv->num_entries()));
6659452003-09-01Martin Nilsson  return last_rv;
aa370a1999-04-24Johan Schön  } // search
4729292005-04-06Martin Stjernholm mapping(string:string|array(string)) read ( string object_name, void|string filter, void|array(string) attrs, void|int attrsonly, void|mapping(string:array(int|string)) controls, void|int flags)
f75dd42005-03-23Martin Stjernholm //! Reads a specified object in the LDAP server. @[object_name] is the //! distinguished name for the object. The rest of the arguments are //! the same as to @[search]. //! //! The default filter and attributes that might have been set in the //! LDAP URL doesn't affect this call. If @[filter] isn't set then //! @expr{"(objectClass=*)"@} is used. //! //! @returns //! Returns a mapping of the requested attributes. It has the same //! form as the response from @[result.fetch]. //! //! @seealso //! @[search] { if (chk_ver()) return 0; if (chk_binded()) return 0; if(ldap_version == 3) { object_name = string_to_utf8 (object_name); if (filter) filter = string_to_utf8(filter); } object|int search_request = make_search_op (object_name, 0, ldap_deref, ldap_sizelimit, ldap_timelimit, attrsonly,
8cc0002005-04-07Martin Stjernholm  filter || get_cached_filter ("(objectClass=*)"),
49ae3a2005-04-07Martin Stjernholm  attrs);
f75dd42005-03-23Martin Stjernholm  if(intp(search_request)) { THROW(({error_string()+"\n",backtrace()})); return 0; } object ctrls; if (controls) { array(object) control_list = allocate (sizeof (controls)); int i; foreach (controls; string type; array(int|string) data) control_list[i++] = make_control (type, [string] data[1], [int] data[0]); if (sizeof (control_list)) ctrls = .ldap_privates.asn1_sequence(0, control_list); } string|int raw; PROFILE ("send_get_op", { if(intp(raw = do_op(search_request, ctrls))) { THROW(({error_string()+"\n",backtrace()})); return 0; } }); array(string) rawarr; PROFILE("rawarr++", { rawarr = ({raw}); while (ASN1_DECODE_RESULTAPP(raw) != 5) { // NB: The msgid stuff is defunct in readmsg, so we can // just as well pass a zero there. :P PROFILE("readmsg", raw = readmsg(0)); if (intp(raw)) { THROW(({error_string()+"\n",backtrace()})); return 0; } rawarr += ({raw}); } // while }); PROFILE ("result", last_rv = result (rawarr, 0, flags));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
f75dd42005-03-23Martin Stjernholm  if (ldap_errno != LDAP_SUCCESS) return 0; return last_rv->fetch(); }
4729292005-04-06Martin Stjernholm string|array(string) read_attr (string object_name, string attr, void|string filter, void|mapping(string:array(int|string)) controls)
f75dd42005-03-23Martin Stjernholm //! Reads a specified attribute of a specified object in the LDAP //! server. @[object_name] is the distinguished name of the object and //! @[attr] is the attribute. The rest of the arguments are the same //! as to @[search]. //! //! The default filter that might have been set in the LDAP URL //! doesn't affect this call. If @[filter] isn't set then //! @expr{"(objectClass=*)"@} is used. //! //! @returns
4729292005-04-06Martin Stjernholm //! For single-valued attributes, the value is returned as a string. //! For multivalued attributes, the value is returned as an array of //! strings. Returns zero if there was an error.
f75dd42005-03-23Martin Stjernholm //! //! @seealso //! @[read], @[get_root_dse_attr] {
4729292005-04-06Martin Stjernholm  if (mapping(string:string|array(string)) res = read (object_name, filter, ({attr}), 0, controls, SEARCH_MULTIVAL_ARRAYS_ONLY)) {
f75dd42005-03-23Martin Stjernholm  m_delete (res, "dn"); // Get the value regardless of the case of the attribute name that // the server used in the response. return get_iterator (res)->value(); } return 0; }
aa370a1999-04-24Johan Schön 
4066882005-03-11Martin Stjernholm //! Return the LDAP protocol version in use. int get_protocol_version() {return ldap_version;}
8a105d2005-03-24Martin Stjernholm  //! Sets the base DN for searches using @[search] and schema queries //! using @[get_attr_type_descr]. //! //! @note //! For compatibility, the old base DN is returned. However, if //! LDAPv3 is used, the value is UTF-8 encoded. Use @[get_basedn] //! separately instead.
aa370a1999-04-24Johan Schön  string set_basedn (string base_dn) { string old_dn = ldap_basedn; if(ldap_version == 3) {
6f3cc51999-08-12Marcus Comstedt  base_dn = string_to_utf8(base_dn);
aa370a1999-04-24Johan Schön  } ldap_basedn = base_dn; DWRITE_HI("client.SET_BASEDN = " + base_dn + "\n");
6659452003-09-01Martin Nilsson  return old_dn;
aa370a1999-04-24Johan Schön  }
8a105d2005-03-24Martin Stjernholm //! Returns the current base DN for searches using @[search] and //! schema queries using @[get_attr_type_descr].
f75dd42005-03-23Martin Stjernholm string get_basedn() {return utf8_to_string (ldap_basedn);}
6e47642005-03-10Martin Stjernholm 
84e8f02005-04-06Martin Stjernholm //! Returns the bind DN currently in use for the connection. Zero is //! returned if the connection isn't bound. The empty string is //! returned if the connection is in use but no bind DN has been given //! explicitly to @[bind]. string get_bound_dn() {return bound_dn;}
227d482001-11-05Honza Petrous  //! //! Sets value of scope for search operation. //! //! @param scope
8a105d2005-03-24Martin Stjernholm  //! The value can be one of the @expr{SCOPE_*@} constants or a //! string @expr{"base"@}, @expr{"one"@} or @expr{"sub"@}.
227d482001-11-05Honza Petrous  //!
8a105d2005-03-24Martin Stjernholm  //! @returns //! Returns the @expr{SCOPE_*@} constant for the old scope.
227d482001-11-05Honza Petrous  int set_scope (int|string scope) {
aa370a1999-04-24Johan Schön  int old_scope = ldap_scope;
227d482001-11-05Honza Petrous  // support for string based values if(stringp(scope)) switch (lower_case(scope)) {
8a105d2005-03-24Martin Stjernholm  case "sub": scope = SCOPE_SUB; break; case "one": scope = SCOPE_ONE; break; case "base": scope = SCOPE_BASE; break; default: ERROR ("Invalid scope %O.\n", scope);
227d482001-11-05Honza Petrous  } else
8a105d2005-03-24Martin Stjernholm  if (!(<SCOPE_BASE, SCOPE_ONE, SCOPE_SUB>)[scope]) ERROR ("Invalid scope %O.\n", scope);
227d482001-11-05Honza Petrous 
aa370a1999-04-24Johan Schön  ldap_scope = scope; DWRITE_HI("client.SET_SCOPE = " + (string)scope + "\n");
6659452003-09-01Martin Nilsson  return old_scope;
aa370a1999-04-24Johan Schön  }
6e47642005-03-10Martin Stjernholm //! Return the currently set scope as a string @expr{"base"@}, //! @expr{"one"@}, or @expr{"sub"@}.
8a105d2005-03-24Martin Stjernholm string get_scope() {return ([SCOPE_BASE: "base", SCOPE_ONE: "one", SCOPE_SUB: "sub"])[ldap_scope];}
6e47642005-03-10Martin Stjernholm 
66f19d2002-02-14Martin Nilsson  //! @param option_type //! LDAP_OPT_xxx //! @param value //! new value for option
aa370a1999-04-24Johan Schön  int set_option (int opttype, int value) { DWRITE_HI("client.SET_OPTION: " + (string)opttype + " = " + (string)value + "\n"); switch (opttype) { case 1: // LDAP_OPT_DEREF //if (intp(value)) ldap_deref = value; //else
6659452003-09-01Martin Nilsson  // return -1;
aa370a1999-04-24Johan Schön  break; case 2: // LDAP_OPT_SIZELIMIT //if (intp(value)) ldap_sizelimit = value; //else
6659452003-09-01Martin Nilsson  // return -1;
aa370a1999-04-24Johan Schön  break; case 3: // LDAP_OPT_TIMELIMIT //if (intp(value)) ldap_timelimit = value; //else
6659452003-09-01Martin Nilsson  // return -1;
aa370a1999-04-24Johan Schön  break; case 4: // LDAP_OPT_REFERRALS
6659452003-09-01Martin Nilsson  default: return -1;
aa370a1999-04-24Johan Schön  }
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
66f19d2002-02-14Martin Nilsson  //! @param option_type //! LDAP_OPT_xxx
aa370a1999-04-24Johan Schön  int get_option (int opttype) { DWRITE_HI("client.GET_OPTION: " + (string)opttype + "\n"); switch (opttype) { case 1: // LDAP_OPT_DEREF
6659452003-09-01Martin Nilsson  return ldap_deref;
aa370a1999-04-24Johan Schön  case 2: // LDAP_OPT_SIZELIMIT
6659452003-09-01Martin Nilsson  return ldap_sizelimit;
aa370a1999-04-24Johan Schön  case 3: // LDAP_OPT_TIMELIMIT
6659452003-09-01Martin Nilsson  return ldap_timelimit;
aa370a1999-04-24Johan Schön  case 4: // LDAP_OPT_REFERRALS }
6659452003-09-01Martin Nilsson  return -1;
aa370a1999-04-24Johan Schön  }
8a105d2005-03-24Martin Stjernholm mapping(string:mixed) get_parsed_url() {return lauth;} //! Returns a mapping containing the data parsed from the LDAP URL //! passed to @[create]. The mapping has the same format as the return
84e8f02005-04-06Martin Stjernholm //! value from @[Protocols.LDAP.parse_ldap_url]. Don't be destructive //! on the returned value.
8a105d2005-03-24Martin Stjernholm 
aa370a1999-04-24Johan Schön  private int|string send_modify_op(string dn, mapping(string:array(mixed)) attropval) { // MODIFY object o, msgval; string atype; array(object) oatt = ({}), attrarr; foreach(indices(attropval), atype) { if(!intp((attropval[atype])[0]))
6659452003-09-01Martin Nilsson  return seterr(LDAP_PROTOCOL_ERROR);
aa370a1999-04-24Johan Schön  attrarr = ({}); for(int ix=1; ix<sizeof(attropval[atype]); ix++) attrarr += ({Standards.ASN1.Types.asn1_octet_string( (attropval[atype])[ix])}); // if(sizeof(attrarr)) // attributevalue ? o = Standards.ASN1.Types.asn1_sequence( ({Standards.ASN1.Types.asn1_octet_string(atype), Standards.ASN1.Types.asn1_set(attrarr) })); // else // o = Standards.ASN1.Encode.asn1_sequence( // Standards.ASN1.Encode.asn1_octet_string(atype)); oatt += ({Standards.ASN1.Types.asn1_sequence( ({ASN1_ENUMERATED((attropval[atype])[0]), o }))}); } //foreach msgval = ASN1_APPLICATION_SEQUENCE(6, ({ Standards.ASN1.Types.asn1_octet_string(dn), Standards.ASN1.Types.asn1_sequence(oatt) }));
6659452003-09-01Martin Nilsson  return do_op(msgval);
aa370a1999-04-24Johan Schön  }
3c96592001-11-02H. William Welliver III  private int|string send_modifydn_op(string dn, string newrdn, int deleteoldrdn, string newsuperior) {
aa370a1999-04-24Johan Schön 
3c96592001-11-02H. William Welliver III  object msgval; array seq=({ Standards.ASN1.Types.asn1_octet_string(dn), Standards.ASN1.Types.asn1_octet_string(newrdn), Standards.ASN1.Types.asn1_boolean(deleteoldrdn) }); if(newsuperior) seq+=({Standards.ASN1.Types.asn1_octet_string(newsuperior)}); msgval = ASN1_APPLICATION_SEQUENCE(12, seq);
6659452003-09-01Martin Nilsson  return do_op(msgval);
3c96592001-11-02H. William Welliver III  } //! The Modify DN Operation allows a client to change the leftmost //! (least significant) component of the name of an entry in the directory, //! or to move a subtree of entries to a new location in the directory. //! //! @param dn //! DN of source object //! //! @param newrdn //! RDN of destination //! //! @param deleteoldrdn //! The parameter controls whether the old RDN attribute values //! are to be retained as attributes of the entry, or deleted //! from the entry. //! //! @param newsuperior //! If present, this is the Distinguished Name of the entry //! which becomes the immediate superior of the existing entry. //!
acd2582002-07-13Honza Petrous  //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns @expr{1@} on success, @expr{0@} otherwise.
acd2582002-07-13Honza Petrous  //!
bcc2352002-07-12Honza Petrous  //! @note //! The API change: the returning code was changed in Pike 7.3+ //! to follow his logic better.
3c96592001-11-02H. William Welliver III  int modifydn (string dn, string newrdn, int deleteoldrdn, string|void newsuperior) { mixed raw; if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
3c96592001-11-02H. William Welliver III  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
3c96592001-11-02H. William Welliver III  if (chk_dn(dn))
6659452003-09-01Martin Nilsson  return 0;
3c96592001-11-02H. William Welliver III  if(ldap_version == 3) { dn = string_to_utf8(dn); newrdn = string_to_utf8(newrdn); if(newsuperior) newsuperior = string_to_utf8(newsuperior); } if(intp(raw = send_modifydn_op(dn, newrdn, deleteoldrdn, newsuperior))) { THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
3c96592001-11-02H. William Welliver III  }
bcc2352002-07-12Honza Petrous  last_rv = result(({raw})); DWRITE_HI(sprintf("client.MODIFYDN: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
6659452003-09-01Martin Nilsson  return !last_rv->error_number();
3c96592001-11-02H. William Welliver III  } //modifydn //! The Modify Operation allows a client to request that a modification //! of an entry be performed on its behalf by a server. //! //! @param dn //! The distinguished name of modified entry. //! //! @param attropval //! The mapping of attributes with requested operation and attribute's //! values. //!
6e47642005-03-10Martin Stjernholm  //! @code //! attropval=([ attribute: ({operation, value1, value2, ...}) ]) //! @endcode //! //! Where operation is one of the following: //! //! @dl
4b5b452005-04-02Martin Nilsson  //! @item Protocols.LDAP.MODIFY_ADD
6e47642005-03-10Martin Stjernholm  //! Add values listed to the given attribute, creating the //! attribute if necessary.
4b5b452005-04-02Martin Nilsson  //! @item Protocols.LDAP.MODIFY_DELETE
6e47642005-03-10Martin Stjernholm  //! Delete values listed from the given attribute, removing the //! entire attribute if no values are listed, or if all current //! values of the attribute are listed for deletion.
4b5b452005-04-02Martin Nilsson  //! @item Protocols.LDAP.MODIFY_REPLACE
6e47642005-03-10Martin Stjernholm  //! Replace all existing values of the given attribute with the //! new values listed, creating the attribute if it did not //! already exist. A replace with no value will delete the entire //! attribute if it exists, and is ignored if the attribute does //! not exist. //! @enddl
3c96592001-11-02H. William Welliver III  //!
6e47642005-03-10Martin Stjernholm  //! Values that are sent UTF-8 encoded according the the attribute //! syntaxes are encoded automatically.
3c96592001-11-02H. William Welliver III  //!
bcc2352002-07-12Honza Petrous  //! @returns
6e47642005-03-10Martin Stjernholm  //! Returns @expr{1@} on success, @expr{0@} otherwise.
bcc2352002-07-12Honza Petrous  //! //! @note //! The API change: the returning code was changed in Pike 7.3+ //! to follow his logic better.
6e47642005-03-10Martin Stjernholm  int modify (string dn, mapping(string:array(int(0..2)|string)) attropval) {
aa370a1999-04-24Johan Schön  int id; mixed raw; if (chk_ver())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_binded())
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if (chk_dn(dn))
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  if(ldap_version == 3) {
6f3cc51999-08-12Marcus Comstedt  dn = string_to_utf8(dn);
6e47642005-03-10Martin Stjernholm  attropval += ([]); // No need to UTF-8 encode the attribute names themselves since // only ascii chars are allowed in them. foreach (indices (attropval), string attr) if (function(string:string) encoder = get_attr_encoder (attr)) { array(int(0..2)|string) op = attropval[attr] + ({}); for (int i = sizeof (op); --i;) // Skips first element. op[i] = encoder (op[i]); attropval[attr] = op; }
aa370a1999-04-24Johan Schön  } if(intp(raw = send_modify_op(dn, attropval))) { THROW(({error_string()+"\n",backtrace()}));
6659452003-09-01Martin Nilsson  return 0;
aa370a1999-04-24Johan Schön  }
bcc2352002-07-12Honza Petrous  last_rv = result(({raw})); DWRITE_HI(sprintf("client.MODIFY: %s\n", last_rv->error_string()));
c908692005-04-06Martin Stjernholm  seterr (last_rv->error_number(), last_rv->error_string());
6659452003-09-01Martin Nilsson  return !last_rv->error_number();
aa370a1999-04-24Johan Schön  } // modify
bcc2352002-07-12Honza Petrous  //! Gets referrals. //! //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns array of referrals or @expr{0@}.
0b5e392002-07-22H. William Welliver III  array|int get_referrals() {
bcc2352002-07-12Honza Petrous  if(last_rv->referrals) return last_rv->referrals; return 0; }
aa370a1999-04-24Johan Schön 
84e8f02005-04-06Martin Stjernholm  //! Compatibility alias for @[Protocols.LDAP.parse_ldap_url]. mapping(string:mixed) parse_url (string ldapuri) { return parse_ldap_url (ldapuri); }
6de1dc2000-07-14Honza Petrous 
6e47642005-03-10Martin Stjernholm  // Schema handling. static mapping(string:array(string)) query_subschema (string dn, array(string) attrs) // Queries the server for the specified attributes in the subschema // applicable for the specified object. The return value is on the
f75dd42005-03-23Martin Stjernholm // same form as from simple_read (specifically there's no UTF-8
6e47642005-03-10Martin Stjernholm // decoding of the values). // // If dn == "" then the attribute values might be joined from several // schemas. (Might change since I'm not sure whether that's really // useful or not - haven't got a good grasp on how multiple schemas // interact in the same server. /mast) { mapping(string:array(string)) subschema_response; int utf8_decode_dns; if (dn == "" && root_dse) subschema_response = root_dse; else { subschema_response =
8cc0002005-04-07Martin Stjernholm  simple_read (dn, get_cached_filter ("(objectClass=*)"), ({"subschemaSubentry"}));
6e47642005-03-10Martin Stjernholm  utf8_decode_dns = 1; } if (subschema_response) if (array(string) subschema_dns = subschema_response->subschemasubentry) { if (sizeof (subschema_dns) == 1)
f75dd42005-03-23Martin Stjernholm  return simple_read (
6e47642005-03-10Martin Stjernholm  utf8_decode_dns ? utf8_to_string (subschema_dns[0]) : subschema_dns[0],
8cc0002005-04-07Martin Stjernholm  get_cached_filter ("(objectClass=subschema)"), attrs);
6e47642005-03-10Martin Stjernholm  else { // This should afaics only occur for the root DSE, but it's a // bit confusing: RFC 2252 section 5.1.5 specifies that // subschemaSubentry is single valued, while RFC 2251 section // 3.4 says that it can contain zero or more values in the // root DSE. /mast mapping(string:array(string)) res = ([]); foreach (subschema_dns, string subschema_dn) {
f75dd42005-03-23Martin Stjernholm  if (mapping(string:array(string)) subres = simple_read (
6e47642005-03-10Martin Stjernholm  utf8_decode_dns ? utf8_to_string (subschema_dn) : subschema_dn,
8cc0002005-04-07Martin Stjernholm  get_cached_filter ("(objectClass=subschema)"), attrs))
6e47642005-03-10Martin Stjernholm  foreach (indices (subres), string attr) res[attr] += subres[attr]; } return res; } } return 0; } static mapping(string:mixed) parse_schema_terms ( string str, mapping(string:string|multiset(string)) known_terms, string errmsg_prefix) // Parses a string containing a parenthesized list of terms as used in // several schema related attributes. The known_terms mapping // specifies the syntax of the known terms. If there's an entry "" in // it it's used for all other encountered terms. { string orig_str = str, oid; // Doin a slightly lax check of the oid syntax here. if (!sscanf (str, "(%*[ ]%[0-9.]%*[ ]%s", oid, str)) ERROR ("%sExpected '(' at beginning: %O\n", errmsg_prefix, orig_str); if (!sizeof (oid)) ERROR ("%sNumeric object identifier missing at start: %O\n", errmsg_prefix, orig_str); mapping(string:mixed) res = (["oid": oid]); do { int pos = sizeof (str); // Note: RFC 2252 is not clear on what chars are allowed in term // identifier. We assume the same set as for attribute names. sscanf (str, "%[-;a-zA-Z0-9]%*[ ]%s", string term_id, str); if (!sizeof (term_id)) ERROR ("%sTerm identifier expected at pos %d: %O\n", errmsg_prefix, sizeof (orig_str) - pos, orig_str); string|multiset(string) term_syntax = known_terms[term_id] || known_terms[""]; switch (term_syntax) { case 0: ERROR ("%sUnknown term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); case "flag": // Existence only - no value. res[term_id] = 1; break; case "oid": { // Numeric oid or name. // No strict syntax check here. sscanf (str, "%[-;a-zA-Z0-9.]%*[ ]%s", string oid, str); if (!sizeof (oid)) ERROR ("%sExpected oid after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); res[term_id] = oid; break; } case "oidlen": { // OID with optional length. // Cope with Microsoft AD which incorrectly quotes this field // and allows a name (RFC 2252 section 4.3.2 specifies a // numeric oid only). int ms_kludge; string oid; if (has_prefix (str, "'")) { ms_kludge = 1; // No strict syntax check here. sscanf (str, "'%[-;a-zA-Z0-9.]%s", oid, str); } else { // No strict syntax check here. sscanf (str, "%[0-9.]%s", oid, str); } if (!sizeof (oid)) ERROR ("%sExpected numeric oid after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); term_id = lower_case (term_id); res[term_id + "_oid"] = oid; if (sscanf (str, "{%d}%s", int len, str)) res[term_id + "_len"] = len; if (ms_kludge) { if (!sscanf (str, "'%*[ ]%s", str)) ERROR ("%sUnterminated quoted oid after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); } else sscanf (str, "%*[ ]%s", str); break; } case "qdstring": // Quoted UTF-8 string. string parse_qdstring (string what) { string qstr; switch (sscanf (str, "'%[^']'%*[ ]%s", qstr, str)) { case 0: ERROR ("%sExpected %s after term %O at pos %d: %O\n", errmsg_prefix, what, term_id, sizeof (orig_str) - pos, orig_str); case 1: ERROR ("%sUnterminated %s after term %O at pos %d: %O\n", errmsg_prefix, what, term_id, sizeof (orig_str) - pos, orig_str); } if (catch (qstr = utf8_to_string (qstr))) ERROR ("%sMalformed UTF-8 in %s after term %O at pos %d: %O\n", term_id, what, sizeof (orig_str) - pos, orig_str);
cec0932005-03-11Martin Stjernholm  return ldap_decode_string (qstr);
6e47642005-03-10Martin Stjernholm  }; res[term_id] = parse_qdstring ("quoted string"); break; case "qdstrings": // One or more quoted UTF-8 strings. if (sscanf (str, "(%*[ ]%s", str)) { array(string) list = ({}); do { if (str == "") ERROR ("%sUnterminated parenthesis after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); list += ({parse_qdstring ("quoted string in list")}); } while (!sscanf (str, ")%*[ ]%s", str)); res[term_id] = list; } else res[term_id] = ({parse_qdstring ("quoted string")}); break; case "qdescr": // Quoted name. string parse_qdescr (string what) { string name; // No strict syntax check here. switch (sscanf (str, "'%[-;a-zA-Z0-9]'%*[ ]%s", name, str)) { case 0: ERROR ("%sExpected %s after term %O at pos %d: %O\n", errmsg_prefix, what, term_id, sizeof (orig_str) - pos, orig_str); case 1: ERROR ("%sInvalid chars in %s after term %O at pos %d: %O\n", errmsg_prefix, what, term_id, sizeof (orig_str) - pos, orig_str); } return name; }; res[term_id] = parse_qdescr ("quoted descr"); break; case "qdescrs": // One or more quoted names. if (sscanf (str, "(%*[ ]%s", str)) { array(string) list = ({}); do { if (str == "") ERROR ("%sUnterminated parenthesis after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); list += ({parse_qdescr ("quoted descr in list")}); } while (!sscanf (str, ")%*[ ]%s", str)); res[term_id] = list; } else res[term_id] = ({parse_qdescr ("quoted descr")}); break; default: if (multisetp (term_syntax)) { // One of a set. sscanf (str, "%[-;a-zA-Z0-9.]%*[ ]%s", string choice, str); if (!sizeof (choice)) ERROR ("%sExpected keyword after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); if (!term_syntax[choice]) ERROR ("%sUnknown keyword after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); res[term_id] = choice; break; } ERROR ("Unknown syntax %O in known_perms.\n", term_syntax); } } while (!sscanf (str, ")%s", str)); if (str != "") ERROR ("%sUnexpected data after ending ')' at pos %d: %O\n", errmsg_prefix, sizeof (orig_str) - sizeof (str) - 1, orig_str); return res; } static mapping(string:mapping(string:mixed)) attr_type_descrs; mapping(string:mixed) get_attr_type_descr (string attr, void|int standard_attrs) //! Returns the attribute type description for the given attribute, //! which includes the name, object identifier, syntax, etc (see //! section 4.2 in RFC 2252 for details). //! //! This might do a query to the server, but results are cached. //! //! @param attr //! The name of the attribute. Might also be the object identifier //! on string form. //! //! @param standard_attrs //! Flag that controls how the known standard attributes stored in //! @[Protocols.LDAP] are to be used: //! //! @int //! @value 0 //! Check the known standard attributes first. If the attribute //! isn't found there, query the server. This is the default. //! @value 1 //! Don't check the known standard attributes, i.e. always use the //! schema from the server. //! @value 2 //! Only check the known standard attributes. The server is never //! contacted. //! @endint //! //! @returns //! Returns a mapping where the indices are the terms that the //! server has returned and the values are their values on string //! form (dequoted and converted from UTF-8 as appropriate). Terms //! without values get @expr{1@} as value in the mapping. //! //! The mapping might contain the following members (all except //! @expr{"oid"@} are optional): //! //! @mapping //! @member string "oid" //! The object identifier on string form (i.e. a dotted decimal //! string). //! @member string "NAME" //! Array with one or more names used for the attribute. //! @member string "DESC" //! Description. //! @member string "OBSOLETE" //! Flag. //! @member string "SUP" //! Derived from this other attribute. The value is the name or //! oid of it. Note that the attribute description from the //! referenced type always is merged with the current one to make //! the returned description complete. //! @member string "EQUALITY" //! The value is the name or oid of a matching rule. //! @member string "ORDERING" //! The value is the name or oid of a matching rule. //! @member string "SUBSTR" //! The value is the name or oid of a matching rule. //! @member string "syntax_oid" //! The value is the oid of the syntax (RFC 2252, section 4.3.2). //! (This is extracted from the @expr{"SYNTAX"@} term.) //! @member string "syntax_len" //! Optional suggested minimum upper bound of the number of //! characters in the attribute (or bytes if the attribute is //! binary). (This is extracted from the @expr{"SYNTAX"@} term.) //! @member string "SINGLE-VALUE" //! Flag. Default multi-valued. //! @member string "COLLECTIVE" //! Flag. Default not collective. //! @member string "NO-USER-MODIFICATION" //! Flag. Default user modifiable. //! @member string "USAGE" //! The value is any of the following: //! @string //! @value "userApplications" //! @value "directoryOperation" //! Self-explanatory. //! @value "distributedOperation" //! DSA-shared. //! @value "dSAOperation" //! DSA-specific, value depends on server. //! @endstring //! @endmapping //! //! There might be more fields for server extensions. //! //! Zero is returned if the server didn't provide any attribute type //! description for @[attr]. //! //! @note //! It's the schema applicable at the base DN that is queried. //! //! @note //! LDAPv3 is assumed. { // Don't bother lowercasing numeric oids. Names never start with a digit. if (!(<'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'>)[attr[0]]) attr = lower_case (attr); if (mapping(string:mixed) descr = standard_attrs != 1 &&
cec0932005-03-11Martin Stjernholm  _standard_attr_type_descrs[attr])
6e47642005-03-10Martin Stjernholm  return descr; if (standard_attrs == 2) return 0;
5afbf42005-03-10Martin Stjernholm  if (!attr_type_descrs) { attr_type_descrs = ([]);
6e47642005-03-10Martin Stjernholm  if (mapping(string:array(string)) subschema = query_subschema (ldap_basedn, ({"attributeTypes"}))) if (array(string) attr_types = subschema->attributetypes) { // Note partial code dup with // Protocols.LDAP._standard_attr_type_descrs init. array(mapping(string:mixed)) incomplete = ({}); foreach (attr_types, string attr_type) { mapping(string:mixed) descr = parse_schema_terms ( utf8_to_string (attr_type), (["NAME": "qdescrs", "DESC": "qdstring", "OBSOLETE": "flag", "SUP": "oid", "EQUALITY": "oid", "ORDERING": "oid", "SUBSTR": "oid", "SYNTAX": "oidlen", "SINGLE-VALUE": "flag", "COLLECTIVE": "flag", "NO-USER-MODIFICATION": "flag", "USAGE": (<"userApplications", "directoryOperation", "distributedOperation", "dSAOperation">), "": "qdstrings"]), "Error in attributeTypes when querying schema: "); if (descr->SUP) incomplete += ({descr}); attr_type_descrs[descr->oid] = descr; foreach (descr->NAME, string name) attr_type_descrs[lower_case (name)] = descr; } void complete (mapping(string:mixed) descr) { string sup = lower_case (descr->SUP); mapping(string:mixed) sup_descr = attr_type_descrs[sup] ||
cec0932005-03-11Martin Stjernholm  (standard_attrs != 1 && _standard_attr_type_descrs[sup]);
6e47642005-03-10Martin Stjernholm  if (!sup_descr) ERROR ("Inconsistency in schema: " "Got SUP reference to unknown attribute: %O\n", descr); if (sup_descr->SUP) complete (sup_descr); foreach (indices (sup_descr), string term) if (zero_type (descr[term])) descr[term] = sup_descr[term]; }; foreach (incomplete, mapping(string:mixed) descr) complete (descr); }
5afbf42005-03-10Martin Stjernholm  }
6e47642005-03-10Martin Stjernholm  return attr_type_descrs[attr]; } #ifdef PARSE_RFCS int main (int argc, array(string) argv) { // Try to parse a bit of RFC text to generate _ATD_ constants for // Protocols.LDAP. array(array(string)) sections = ({}); { // Split on section headers. array(string) cont; while (string line = Stdio.stdin->gets()) { if (sscanf (line, "%[0-9.]%*[ \t]%s", string sno, string shdr) == 3 && sno != "" && shdr != "") { if (cont) sections += ({cont}); if (has_suffix (sno, ".")) sno = sno[..<1]; cont = ({sno, shdr}); } else if (cont) cont += ({line}); } if (cont) sections += ({cont}); } foreach (sections, array(string) cont) for (int n = 0; n < sizeof (cont); n++) { if (sscanf (cont[n], "%*[ \t](%*s") == 2 && (n == 0 || String.trim_whites (cont[n-1]) == "")) { string expr = String.trim_whites (cont[n]), s; for (n++; n < sizeof (cont) && (s = String.trim_whites (cont[n])) != ""; n++) expr += " " + s; mapping descr; if (mixed err = catch (descr = parse_schema_terms ( expr, (["NAME": "qdescrs", "DESC": "qdstring", "OBSOLETE": "flag", "SUP": "oid", "EQUALITY": "oid", "ORDERING": "oid", "SUBSTR": "oid", "SYNTAX": "oidlen", "SINGLE-VALUE": "flag", "COLLECTIVE": "flag", "NO-USER-MODIFICATION": "flag", "USAGE": (<"userApplications", "directoryOperation", "distributedOperation", "dSAOperation">), "": "qdstrings"]), ""))) werror (describe_error (err)); write ("constant ATD_%s = ([ // %s, %s\n", replace (cont[1], " ", "_"), argv[1], cont[0]); foreach (({"oid", "NAME", "DESC", "OBSOLETE", "SUP", "EQUALITY", "ORDERING", "SUBSTR", "syntax_oid", "syntax_len", "SINGLE-VALUE", "COLLECTIVE", "NO-USER-MODIFICATION", "USAGE"}), string term) { if (mixed val = descr[term]) { if (arrayp (val)) write (" %O: ({%s}),\n", term, map (val, lambda (string s) {return sprintf ("%O", s);}) * ", "); else { if (string sym = (<"oid", "syntax_oid">)[term] &&
cec0932005-03-11Martin Stjernholm  get_constant_name (val))
6e47642005-03-10Martin Stjernholm  write (" %O: %s,\n", term, sym); else write (" %O: %O,\n", term, val); } } } write ("]);\n"); } } } #endif
ffaf452004-04-14Martin Nilsson #else constant this_program_does_not_exist=1;
92dbd31999-08-24Fredrik Hübinette (Hubbe) #endif