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. // // 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)
49eabb2009-09-08Martin Stjernholm // RFC 4510-4519 (version3 spec)
aa370a1999-04-24Johan Schön // 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?) #include "ldap_globals.h"
e1fb092014-02-14Martin Nilsson #if constant(SSL.Cipher)
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)
7cf0452007-10-08Martin Stjernholm #define ASN1_RESULTCODE(X) (int)((X)->elements[1]->elements[0]->value->cast_to_int()) #define ASN1_RESULTSTRING(X) ((X)->elements[1]->elements[2]->value) #define ASN1_RESULTREFS(X) ((X)->elements[1]->elements[3]->elements)
aa370a1999-04-24Johan Schön 
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.
3f78922007-05-23Martin Stjernholm  string md5_password; // MD5 hash of the bind password, if any.
84e8f02005-04-06Martin Stjernholm  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
8a89ad2007-05-23Martin Stjernholm  // FIXME: Should remove last_rv to avoid ref cycles. The only // problem is the get_referrals function.
f96d1b2004-05-26Henrik Grubbström (Grubba)  }
aa370a1999-04-24Johan Schön 
4b5b452005-04-02Martin Nilsson //! @ignore
9eaf1d2008-06-28Martin Nilsson protected 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;
cd7f232014-03-01Martin Nilsson #ifdef LDAP_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 }
cd7f232014-03-01Martin Nilsson #ifdef LDAP_DEBUG
6e47642005-03-10Martin Stjernholm  else if (!nowarn && !has_suffix (attr, ";binary") && !has_value (attr, ";binary;")) werror ("Warning: Couldn't fetch attribute description for %O - "
0d6b622005-04-25Martin Stjernholm  "binary content assumed.\n", attr);
6e47642005-03-10Martin Stjernholm #endif return 0; }
4b5b452005-04-02Martin Nilsson //! @endignore
6e47642005-03-10Martin Stjernholm 
9eaf1d2008-06-28Martin Nilsson protected function(string:string) get_attr_encoder (string attr)
6e47642005-03-10Martin Stjernholm { 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;
cd7f232014-03-01Martin Nilsson #ifdef LDAP_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 }
cd7f232014-03-01Martin Nilsson #ifdef LDAP_DEBUG
6e47642005-03-10Martin Stjernholm  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; }
0b8d2f2013-06-17Martin Nilsson typedef string|Charset.DecodeError| array(string|Charset.DecodeError) ResultAttributeValue;
6cf03c2007-05-23Martin Stjernholm  typedef mapping(string:ResultAttributeValue) ResultEntry;
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;
6cf03c2007-05-23Martin Stjernholm  private array(ResultEntry) entry = ({});
4066882005-03-11Martin Stjernholm  private int flags;
bf334d2001-08-15Honza Petrous  array(string) referrals;
aa370a1999-04-24Johan Schön 
6cf03c2007-05-23Martin Stjernholm  // All entries up to but not including this one have been decoded // using decode_entry, the rest have not. private int first_undecoded_entry = 0;
7cf0452007-10-08Martin Stjernholm  array(ResultEntry) decode_entries (array(object) entries)
6e47642005-03-10Martin Stjernholm  {
6cf03c2007-05-23Martin Stjernholm  array(ResultEntry) 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) { \
7cf0452007-10-08Martin Stjernholm  foreach (entries, object entry) { \ object derent = entry->elements[1]; \
4729292005-04-06Martin Stjernholm  if (array(object) derattribs = ASN1_GET_ATTR_ARRAY (derent)) { \
e6ec662006-06-22Martin Stjernholm  string dn; \ {SET_DN;} \
6cf03c2007-05-23Martin Stjernholm  ResultEntry attrs = (["dn": dn]); \
4729292005-04-06Martin Stjernholm  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 { \
7cf0452007-10-08Martin Stjernholm  foreach (entries, object entry) { \ object derent = entry->elements[1]; \
4729292005-04-06Martin Stjernholm  if (array(object) derattribs = ASN1_GET_ATTR_ARRAY (derent)) { \
e6ec662006-06-22Martin Stjernholm  string dn; \ {SET_DN;} \
6cf03c2007-05-23Martin Stjernholm  ResultEntry attrs = (["dn": ({dn})]); \
4729292005-04-06Martin Stjernholm  foreach (derattribs, object derattr) { \ string attr; \ {SET_ATTR;} \ } \ res += ({attrs}); \ } \
4066882005-03-11Martin Stjernholm  } \ } \ } while (0)
6cf03c2007-05-23Martin Stjernholm  if (flags & SEARCH_LOWER_ATTRS) DECODE_ENTRIES (dn = ASN1_GET_DN (derent), { attrs[attr = lower_case (ASN1_GET_ATTR_NAME (derattr))] = ASN1_GET_ATTR_VALUES (derattr); }); else DECODE_ENTRIES (dn = ASN1_GET_DN (derent), { attrs[attr = ASN1_GET_ATTR_NAME (derattr)] = ASN1_GET_ATTR_VALUES (derattr); }); #undef DECODE_ENTRIES return res; }
9eaf1d2008-06-28Martin Nilsson  protected void decode_entry (ResultEntry ent)
6cf03c2007-05-23Martin Stjernholm  { // Used in LDAPv3 only: Decode the dn and 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. #define DECODE_DN(DN) do { \ if (mixed err = catch (DN = utf8_to_string (DN))) { \ string errmsg = describe_error (err) + \ "The string is the DN of an entry.\n"; \ if (flags & SEARCH_RETURN_DECODE_ERRORS) \
0b8d2f2013-06-17Martin Nilsson  DN = Charset.DecodeError (DN, -1, 0, errmsg); \
6cf03c2007-05-23Martin Stjernholm  else \
0b8d2f2013-06-17Martin Nilsson  throw (Charset.DecodeError (DN, -1, 0, errmsg)); \
6cf03c2007-05-23Martin Stjernholm  } \ } while (0) ResultAttributeValue dn = m_delete (ent, "dn"); if (stringp (dn)) DECODE_DN (dn); else DECODE_DN (dn[0]); #ifdef LDAP_DECODE_DEBUG #define DECODE_VALUE(ATTR, VALUE, DECODER) do { \ if (mixed err = catch {VALUE = DECODER (VALUE);}) { \
95db942006-05-29Martin Stjernholm  mapping descr1, descr2; \ catch (descr1 = get_attr_type_descr (ATTR)); \ catch (descr2 = get_attr_type_descr (ATTR, 1)); \
6cf03c2007-05-23Martin Stjernholm  string errmsg = \ sprintf ("%s" \ "The string occurred in the value of attribute %O " \ "in entry with DN %O.\n" \ "Used decoder %O for attribute type %O, " \ "server reports %O.\n", \ describe_error (err), ATTR, stringp (dn) ? dn : dn[0], \ decoder, descr1, descr2); \ if (flags & SEARCH_RETURN_DECODE_ERRORS) \
0b8d2f2013-06-17Martin Nilsson  VALUE = Charset.DecodeError (VALUE, -1, 0, errmsg); \
6cf03c2007-05-23Martin Stjernholm  else \
0b8d2f2013-06-17Martin Nilsson  throw (Charset.DecodeError (VALUE, -1, 0, errmsg)); \
95db942006-05-29Martin Stjernholm  } \ } while (0)
6cf03c2007-05-23Martin Stjernholm #else
6e47642005-03-10Martin Stjernholm 
6cf03c2007-05-23Martin Stjernholm #define DECODE_VALUE(ATTR, VALUE, DECODER) do { \ if (mixed err = catch {VALUE = DECODER (VALUE);}) { \ string errmsg = \ sprintf ("%s" \ "The string occurred in the value of attribute %O " \ "in entry with DN %O.\n", \ describe_error (err), ATTR, stringp (dn) ? dn : dn[0]); \ if (flags & SEARCH_RETURN_DECODE_ERRORS) \
0b8d2f2013-06-17Martin Nilsson  VALUE = Charset.DecodeError (VALUE, -1, 0, errmsg); \
6cf03c2007-05-23Martin Stjernholm  else \
0b8d2f2013-06-17Martin Nilsson  throw (Charset.DecodeError (VALUE, -1, 0, errmsg)); \
6cf03c2007-05-23Martin Stjernholm  } \ } while (0) #endif foreach (ent; string attr; ResultAttributeValue vals) { if (function(string:string) decoder =
ad04d42009-12-14Martin Stjernholm  get_attr_decoder (attr, DO_IF_DEBUG (stringp (dn) ? dn == "" : dn[0] == ""))) {
6cf03c2007-05-23Martin Stjernholm  if (stringp (vals)) { DECODE_VALUE (attr, vals, decoder); ent[attr] = vals; } else
0b8d2f2013-06-17Martin Nilsson  foreach (vals; int i; string|Charset.DecodeError val) {
6cf03c2007-05-23Martin Stjernholm  DECODE_VALUE (attr, val, decoder); vals[i] = val; } }
aa370a1999-04-24Johan Schön  }
4ad4cd2000-02-17Honza Petrous 
6cf03c2007-05-23Martin Stjernholm #undef DECODE_VALUE
4066882005-03-11Martin Stjernholm 
6cf03c2007-05-23Martin Stjernholm  ent->dn = dn;
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.
7cf0452007-10-08Martin Stjernholm  object|int create(array(object) entries, int stuff, int flags) { // entries: array of der decoded entries, but WITHOUT LDAP PDU !!!
aa370a1999-04-24Johan Schön  // 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.
7cf0452007-10-08Martin Stjernholm  if (!sizeof (entries)) {
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  }
7cf0452007-10-08Martin Stjernholm  DWRITE(sprintf("result.create: %O\n",entries[-1]));
aa370a1999-04-24Johan Schön 
7cf0452007-10-08Martin Stjernholm  // The last element of 'entries' is result itself resultcode = ASN1_RESULTCODE (entries[-1]);
aa370a1999-04-24Johan Schön  DWRITE(sprintf("result.create: code=%d\n",resultcode));
7cf0452007-10-08Martin Stjernholm  resultstring = ASN1_RESULTSTRING (entries[-1]);
c908692005-04-06Martin Stjernholm  if (resultstring == "") resultstring = 0; else if (ldap_version >= 3)
dd0ca02007-10-08Martin Stjernholm  if (mixed err = catch (resultstring = utf8_to_string (resultstring))) DWRITE (sprintf ("Failed to decode result string %O: %s", resultstring, describe_error (err)));
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 = ({});
7cf0452007-10-08Martin Stjernholm  foreach(ASN1_RESULTREFS (entries[-1]), object ref1)
bf334d2001-08-15Honza Petrous  referrals += ({ ref1->value }); DWRITE(sprintf("result.create: refs=%O\n",referrals)); } #endif
7cf0452007-10-08Martin Stjernholm  DWRITE(sprintf("result.create: elements=%d\n",sizeof (entries)));
cf4e0e2005-03-09Martin Stjernholm #if 0
7cf0452007-10-08Martin Stjernholm  DWRITE(sprintf("result.create: entries=%O\n", entries[..<1]));
cf4e0e2005-03-09Martin Stjernholm #endif
6e47642005-03-10Martin Stjernholm 
7cf0452007-10-08Martin Stjernholm  entry = decode_entries (entries[..<1]);
aa370a1999-04-24Johan Schön  #if 0
7cf0452007-10-08Martin Stjernholm  // Context specific proccessing of 'entries'
aa370a1999-04-24Johan Schön  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  //!
6cf03c2007-05-23Martin Stjernholm  //! Returns the number of entries from the current cursor position //! to the end of the list.
0668612001-09-14Honza Petrous  //! //! @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.
0668612001-09-14Honza Petrous  //!
9acfda2001-10-04Martin Nilsson  //! @param index
6cf03c2007-05-23Martin Stjernholm  //! This optional argument can be used for direct access to an //! entry other than the one currently pointed to by the 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
6cf03c2007-05-23Martin Stjernholm  //! the returned attribute value(s) on string form, or a single //! string if @[Protocols.LDAP.SEARCH_MULTIVAL_ARRAYS_ONLY] was //! given to @[search] and the attribute is typed as single //! valued. //! //! If @[Protocols.LDAP.SEARCH_RETURN_DECODE_ERRORS] was given
0b8d2f2013-06-17Martin Nilsson  //! to @[search] then @[Charset.DecodeError] objects are
6cf03c2007-05-23Martin Stjernholm  //! returned in place of a string whenever an attribute value //! fails to be decoded.
cf4e0e2005-03-09Martin Stjernholm  //! //! @member string "dn" //! This special entry contains the object name of the entry as //! a distinguished name.
6cf03c2007-05-23Martin Stjernholm  //!
0b8d2f2013-06-17Martin Nilsson  //! This might also be a @[Charset.DecodeError] if
6cf03c2007-05-23Martin Stjernholm  //! @[Protocols.LDAP.SEARCH_RETURN_DECODE_ERRORS] was given to //! @[search].
cf4e0e2005-03-09Martin Stjernholm  //! @endmapping //! //! Zero is returned if the cursor is outside the valid range of //! entries. //!
6cf03c2007-05-23Martin Stjernholm  //! @throws //! Unless @[Protocols.LDAP.SEARCH_RETURN_DECODE_ERRORS] was
0b8d2f2013-06-17Martin Nilsson  //! given to @[search], a @[Charset.DecodeError] is thrown if //! there is an error decoding the DN or any attribute value.
6cf03c2007-05-23Martin Stjernholm  //!
cf4e0e2005-03-09Martin Stjernholm  //! @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]
6cf03c2007-05-23Martin Stjernholm  ResultEntry fetch(int|void idx) { if (!zero_type (idx)) actnum = idx; if (actnum >= num_entries() || actnum < 0) return 0; if (ldap_version < 3) return entry[actnum];
0668612001-09-14Honza Petrous 
6cf03c2007-05-23Martin Stjernholm  ResultEntry ent = entry[actnum]; if (actnum == first_undecoded_entry) { decode_entry (ent); first_undecoded_entry++; } else if (actnum > first_undecoded_entry) { ent = copy_value (ent); decode_entry (ent);
aa370a1999-04-24Johan Schön  }
6cf03c2007-05-23Martin Stjernholm  return ent;
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.
0b8d2f2013-06-17Martin Nilsson  string|Charset.DecodeError get_dn()
4729292005-04-06Martin Stjernholm  { 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 
6cf03c2007-05-23Martin Stjernholm  array(ResultEntry) 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. //!
6cf03c2007-05-23Martin Stjernholm  //! @throws //! Unless @[Protocols.LDAP.SEARCH_RETURN_DECODE_ERRORS] was
0b8d2f2013-06-17Martin Nilsson  //! given to @[search], a @[Charset.DecodeError] is thrown if //! there is an error decoding the DN or any attribute value.
6cf03c2007-05-23Martin Stjernholm  //!
cf4e0e2005-03-09Martin Stjernholm  //! @seealso //! @[fetch] {
6cf03c2007-05-23Martin Stjernholm  for (; first_undecoded_entry < sizeof (entry); first_undecoded_entry++) decode_entry (entry[first_undecoded_entry]);
cf4e0e2005-03-09Martin Stjernholm  return entry; }
aa370a1999-04-24Johan Schön  } // end of class 'result' ---------------
f96d1b2004-05-26Henrik Grubbström (Grubba)  // helper functions and macros
7cf0452007-10-08Martin Stjernholm // To make a result from any ldap operation that only returns a plain // LDAPResult. #define SIMPLE_RESULT(STR, STUFF, FLAGS) \ result (({.ldap_privates.ldap_der_decode (STR)}), (STUFF), (FLAGS))
f96d1b2004-05-26Henrik Grubbström (Grubba) #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  {
7aaaf12008-06-20Stephen R. van den Berg  info = ([ "code_revision" : sprintf("%d.%d.%d",(int)__REAL_VERSION__,__REAL_MINOR__,__REAL_BUILD__) ]);
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")
ed77da2014-05-15Martin Nilsson #if constant(SSL.Cipher)
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 ]);
ed77da2014-05-15Martin Nilsson #if constant(SSL.Cipher)
0668612001-09-14Honza Petrous  if(lauth->scheme == "ldaps" && !context) {
dc90a52014-05-15Martin Nilsson  context = SSL.Context();
0668612001-09-14Honza Petrous  }
6c23602002-07-31Martin Nilsson #endif
b3adf32008-10-29Martin Stjernholm  Stdio.File low_fd = Stdio.File();
4cb56b2004-10-13H. William Welliver III  if(!(low_fd->connect(lauth->host, lauth->port))) {
0668612001-09-14Honza Petrous  //errno = ldapfd->errno();
b3adf32008-10-29Martin Stjernholm  seterr (LDAP_SERVER_DOWN, strerror (low_fd->errno()));
0668612001-09-14Honza Petrous  //ldapfd->destroy(); //ldap=0; //ok = 0; //if(con_fail)
563bd72004-01-11Martin Nilsson  // con_fail(this, @extra_args);
b3adf32008-10-29Martin Stjernholm  ERROR ("Failed to connect to LDAP server: %s\n", ldap_rem_errstr);
aa370a1999-04-24Johan Schön  }
0668612001-09-14Honza Petrous 
ed77da2014-05-15Martin Nilsson #if constant(SSL.Cipher)
0668612001-09-14Honza Petrous  if(lauth->scheme == "ldaps") {
fd4fd82014-05-17Henrik Grubbström (Grubba)  SSL.sslfile ssl_fd = SSL.sslfile(low_fd, context); if (!ssl_fd->connect()) { ERROR("Failed to connect to LDAPS server.\n"); } ::create(ssl_fd);
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
4c9e4b2008-01-13Martin Nilsson  object msgval, vers, namedn, auth;
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;
ed77da2014-05-15Martin Nilsson #if constant(SSL.Cipher)
4cb56b2004-10-13H. William Welliver III  // 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);
7cf0452007-10-08Martin Stjernholm  int result = ASN1_RESULTCODE(.ldap_privates.ldap_der_decode (readbuf));
4cb56b2004-10-13H. William Welliver III  if(result!=0) return 0; // otherwise, we can try to negotiate. if(!context) {
dc90a52014-05-15Martin Nilsson  context = SSL.Context();
4cb56b2004-10-13H. William Welliver III  } object _f = ldapfd;
fd4fd82014-05-17Henrik Grubbström (Grubba)  ldapfd = SSL.sslfile(_f, context); return ldapfd->connect();
4cb56b2004-10-13H. William Welliver III #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. //!
dc90a52014-05-15Martin Nilsson  int start_tls (void|SSL.Context context) {
ed77da2014-05-15Martin Nilsson #if constant(SSL.Cipher)
4cb56b2004-10-13H. William Welliver III  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;
ed77da2014-05-15Martin Nilsson #else return 0; #endif
4cb56b2004-10-13H. William Welliver III  } // 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
073a952007-05-23Martin Stjernholm  //! The desired protocol version (current @expr{2@} or @expr{3@}). //! Defaults to @expr{3@} if zero or left out.
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  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  }
3f78922007-05-23Martin Stjernholm  bound_dn = md5_password = 0;
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 1, 0);
3f78922007-05-23Martin Stjernholm  if (!last_rv->error_number()) {
84e8f02005-04-06Martin Stjernholm  bound_dn = dn;
bff4f22009-04-25Martin Stjernholm  md5_password = Crypto.MD5.hash (pass);
3f78922007-05-23Martin Stjernholm  }
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  }
3f78922007-05-23Martin Stjernholm  bound_dn = md5_password = 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) { 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  }
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 0, 0);
bcc2352002-07-12Honza Petrous  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  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  }
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 0, 0);
bcc2352002-07-12Honza Petrous  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) { 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  }
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 0, 0);
bcc2352002-07-12Honza Petrous  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
9eaf1d2008-06-28Martin Nilsson protected 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; }
9eaf1d2008-06-28Martin Nilsson protected mapping(string:array(string)) root_dse;
6e47642005-03-10Martin Stjernholm 
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 }
9eaf1d2008-06-28Martin Nilsson protected object make_control (string control_type, void|string value,
9434432005-02-03Martin Stjernholm  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); }
9eaf1d2008-06-28Martin Nilsson protected multiset(string) supported_controls;
9434432005-02-03Martin Stjernholm  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] {
4525fa2008-05-23Henrik Grubbström (Grubba)  if (!supported_controls) {
2130652005-03-23Martin Stjernholm  if (array(string) res = get_root_dse_attr ("supportedControl")) supported_controls = mkmultiset (res);
4525fa2008-05-23Henrik Grubbström (Grubba)  else supported_controls = (<>); }
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 !!! 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],
86f62a2008-09-08Martin Stjernholm  //! @[Protocols.LDAP.ldap_encode_string], @[Protocols.LDAP.make_filter]
49ae3a2005-04-07Martin Stjernholm  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 
4c9e4b2008-01-13Martin Nilsson  int id;
7cf0452007-10-08Martin Stjernholm  object entry; array(object) entries = ({});
aa370a1999-04-24Johan Schön 
7bda2d2007-04-18Martin Stjernholm  DWRITE_HI(sprintf ("client.SEARCH: %O\n", filter));
aa370a1999-04-24Johan Schö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(""); 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) 
7cf0452007-10-08Martin Stjernholm  string|int raw;
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; }
7cf0452007-10-08Martin Stjernholm  entry = .ldap_privates.ldap_der_decode (raw);
f96d1b2004-05-26Henrik Grubbström (Grubba)  });
aa370a1999-04-24Johan Schön 
7cf0452007-10-08Martin Stjernholm  PROFILE("entries++", { entries += ({entry}); while (ASN1_GET_RESULTAPP(entry) != 5) { string|int raw;
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; }
7cf0452007-10-08Martin Stjernholm  entry = .ldap_privates.ldap_der_decode (raw); entries += ({entry});
4f57c32004-05-25Henrik Grubbström (Grubba)  } // while
f96d1b2004-05-26Henrik Grubbström (Grubba)  });
7cf0452007-10-08Martin Stjernholm  // At this point @[entry] contains a SearchResultDone.
4f57c32004-05-25Henrik Grubbström (Grubba)  cookie = 0;
f96d1b2004-05-26Henrik Grubbström (Grubba)  IF_ELSE_PAGED_SEARCH({
7cf0452007-10-08Martin Stjernholm  if ((ASN1_RESULTCODE(entry) != 10) && (sizeof(entry->elements) > 2)) { object controls = entry->elements[2];
8648672004-06-18Henrik Grubbström (Grubba)  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.
7cf0452007-10-08Martin Stjernholm  entries = entries[..<1];
f96d1b2004-05-26Henrik Grubbström (Grubba)  } }
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) 
7cf0452007-10-08Martin Stjernholm  PROFILE("result", last_rv = result (entries, 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); }
7cf0452007-10-08Martin Stjernholm  object entry;
f75dd42005-03-23Martin Stjernholm  PROFILE ("send_get_op", {
7cf0452007-10-08Martin Stjernholm  string|int raw;
f75dd42005-03-23Martin Stjernholm  if(intp(raw = do_op(search_request, ctrls))) { THROW(({error_string()+"\n",backtrace()})); return 0; }
7cf0452007-10-08Martin Stjernholm  entry = .ldap_privates.ldap_der_decode (raw);
f75dd42005-03-23Martin Stjernholm  });
7cf0452007-10-08Martin Stjernholm  array(object) entries; PROFILE("entries++", { entries = ({entry}); while (ASN1_GET_RESULTAPP(entry) != 5) { string|int raw;
f75dd42005-03-23Martin Stjernholm  // 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; }
7cf0452007-10-08Martin Stjernholm  entry = .ldap_privates.ldap_der_decode (raw); entries += ({entry});
f75dd42005-03-23Martin Stjernholm  } // while });
7cf0452007-10-08Martin Stjernholm  PROFILE ("result", last_rv = result (entries, 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;}
3f78922007-05-23Martin Stjernholm //! Returns an MD5 hash of the password used for the bind operation, //! or zero if the connection isn't bound. If no password was given to //! @[bind] then an empty string was sent as password, and the MD5 //! hash of that is therefore returned. string get_bind_password_hash() {return md5_password;}
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  }
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 0, 0);
bcc2352002-07-12Honza Petrous  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  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  }
7cf0452007-10-08Martin Stjernholm  last_rv = SIMPLE_RESULT (raw, 0, 0);
bcc2352002-07-12Honza Petrous  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.
9eaf1d2008-06-28Martin Nilsson protected mapping(string:array(string)) query_subschema (string dn,
49eabb2009-09-08Martin Stjernholm  array(string) attrs)
6e47642005-03-10Martin Stjernholm // 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; }
9eaf1d2008-06-28Martin Nilsson protected mapping(string:mixed) parse_schema_terms (
6e47642005-03-10Martin Stjernholm  string str,
df36102006-05-11Martin Stjernholm  mapping(string:string|multiset|mapping) known_terms,
6e47642005-03-10Martin Stjernholm  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;
49eabb2009-09-08Martin Stjernholm  // The following parser is much more lax than the ABNF in RFC 4512, // since real-world cases are known to break the RFC in any number // of ways. Basically anything that isn't explicitly used as a // separator char ('(', ')', ' ') or a qdescr/qdstring starter (') // is considered a token constituent. Note that $ is also used as // separator in an oidlist but isn't used as a generic separator // chars since it's known to occur in symbolic oids in some Domino // schemas.
30bf7a2006-05-10Martin Stjernholm  // RFC 2252 mandates a dotted decimal oid here, but some servers // (e.g. iPlanet) might use symbolic strings so we have to do a lax // syntax check. // // Note: Maybe it would be convenient to lowercase the noncompliant // oids in such cases, to make later lookups easier. However there // are no docs saying that it's ok to do so, and I prefer to play // safe. /mast
49eabb2009-09-08Martin Stjernholm  if (!sscanf (str, "(%*[ ]%[^ '()]%*[ ]%s", oid, str))
6e47642005-03-10Martin Stjernholm  ERROR ("%sExpected '(' at beginning: %O\n", errmsg_prefix, orig_str); if (!sizeof (oid))
49eabb2009-09-08Martin Stjernholm  ERROR ("%sObject identifier missing at start: %O\n",
6e47642005-03-10Martin Stjernholm  errmsg_prefix, orig_str); mapping(string:mixed) res = (["oid": oid]);
49eabb2009-09-08Martin Stjernholm  while (!sscanf (str, ")%s", str)) {
6e47642005-03-10Martin Stjernholm  int pos = sizeof (str);
49eabb2009-09-08Martin Stjernholm  // Note: RFC 4512 is not clear on what chars are allowed in a term // identifier. Since all terms should be followed by a space // separator, we read everything up to the next space. sscanf (str, "%[^ '()]%*[ ]%s", string term_id, str);
6e47642005-03-10Martin Stjernholm  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;
49eabb2009-09-08Martin Stjernholm  case "oid": // Numeric oid or name. string parse_oid() { sscanf (str, "%[^ '()]%*[ ]%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); return oid; }; res[term_id] = parse_oid(); break; case "oids": // Numeric oid or name or $-separated list of them. if (sscanf (str, "(%*[ ]%s", str)) { array(string) list = ({}); while (1) { if (str == "") ERROR ("%sUnterminated parenthesis after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); sscanf (str, "%[^ '()$]%*[ ]%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); list += ({oid}); if (sscanf (str, ")%*[ ]%s", str)) break; if (!sscanf (str, "$%*[ ]%s", str)) ERROR ("%sExpected '$' between oids after term %O at pos %d: " "%O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str); } res[term_id] = list; } else res[term_id] = ({parse_oid()});
6e47642005-03-10Martin Stjernholm  break; case "oidlen": { // OID with optional length. // Cope with Microsoft AD which incorrectly quotes this field
49eabb2009-09-08Martin Stjernholm  // and allows a name (RFC 4512 section 4.1.2 specifies a
6e47642005-03-10Martin Stjernholm  // numeric oid only). int ms_kludge; string oid; if (has_prefix (str, "'")) { ms_kludge = 1;
49eabb2009-09-08Martin Stjernholm  sscanf (str, "'%[^'{]%s", oid, str);
6e47642005-03-10Martin Stjernholm  } else {
49eabb2009-09-08Martin Stjernholm  sscanf (str, "%[^ '(){]%s", oid, str);
6e47642005-03-10Martin Stjernholm  } 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",
398e762008-09-01Henrik Grubbström (Grubba)  errmsg_prefix, what, term_id, 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;
49eabb2009-09-08Martin Stjernholm  // RFC 4512 restricts this to a letter followed by letters, // digits or hyphens. However, real world cases shows that // other chars can occur here ('.', at least), so let's be lax. switch (sscanf (str, "'%[^']'%*[ ]%s", name, str)) {
6e47642005-03-10Martin Stjernholm  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:
df36102006-05-11Martin Stjernholm  if (multisetp (term_syntax) || mappingp (term_syntax)) { // One of a set.
49eabb2009-09-08Martin Stjernholm  sscanf (str, "%[^ '()]%*[ ]%s", string choice, str);
6e47642005-03-10Martin Stjernholm  if (!sizeof (choice)) ERROR ("%sExpected keyword after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str);
df36102006-05-11Martin Stjernholm  if (term_syntax[1]) choice = lower_case (choice); string|int lookup = term_syntax[choice]; if (!lookup)
6e47642005-03-10Martin Stjernholm  ERROR ("%sUnknown keyword after term %O at pos %d: %O\n", errmsg_prefix, term_id, sizeof (orig_str) - pos, orig_str);
df36102006-05-11Martin Stjernholm  res[term_id] = stringp (lookup) ? lookup : choice;
6e47642005-03-10Martin Stjernholm  break; } ERROR ("Unknown syntax %O in known_perms.\n", term_syntax); }
49eabb2009-09-08Martin Stjernholm  }
6e47642005-03-10Martin Stjernholm  if (str != "") ERROR ("%sUnexpected data after ending ')' at pos %d: %O\n", errmsg_prefix, sizeof (orig_str) - sizeof (str) - 1, orig_str); return res; }
9eaf1d2008-06-28Martin Nilsson protected constant attr_type_term_syntax = ([
df36102006-05-11Martin Stjernholm  "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": ([ 1: 1, // This flags case insensitive matching. // The alternatives here are not case insensitive according to RFC // 2252, but of course the iPlanet LDAP server manages to return // "dsaOperation" instead of "dSAOperation". :P "userapplications": "userApplications", "directoryoperation": "directoryOperation", "distributedoperation": "distributedOperation", "dsaoperation": "dSAOperation" ]), "": "qdstrings" ]);
9eaf1d2008-06-28Martin Nilsson protected mapping(string:mapping(string:mixed)) attr_type_descrs;
6e47642005-03-10Martin Stjernholm  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"
30bf7a2006-05-10Martin Stjernholm //! The object identifier on string form. According to the RFC, //! this should always be a dotted decimal string. However some //! LDAP servers, e.g. iPlanet, allows registering attributes //! without an assigned OID. In such cases this can be some other //! string. In the case of iPlanet, it uses the attribute name //! with "-oid" appended (c.f. //! http://docs.sun.com/source/816-5606-10/scmacfg.htm).
6e47642005-03-10Martin Stjernholm //! @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),
df36102006-05-11Martin Stjernholm  attr_type_term_syntax,
6e47642005-03-10Martin Stjernholm  "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 (
df36102006-05-11Martin Stjernholm  expr, attr_type_term_syntax, "")))
6e47642005-03-10Martin Stjernholm  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