1 module openldap; 2 import std.format; 3 import std.string; 4 import core.sys.posix.sys.time; 5 import core.time; 6 import std.datetime; 7 import std.conv; 8 import std.typecons; 9 import std.c.stdlib; 10 import std.algorithm; 11 import std.experimental.logger; 12 13 struct berval { 14 int bv_len; 15 char *bv_val; 16 } 17 alias BerValue = berval; 18 struct ldapcontrol { 19 char * ldctl_oid; /* numericoid of control */ 20 berval ldctl_value; /* encoded value of control */ 21 char ldctl_iscritical; /* criticality */ 22 }; 23 alias LDAPControl = ldapcontrol; 24 25 extern (C) void ber_bvarray_free(berval *bvarray); 26 extern (C) void ber_bvfree(berval *bv); 27 extern (C) void ldap_msgfree(void*); 28 extern (C) void ldap_memfree(void *); 29 extern (C) void ldap_value_free_len(berval**); 30 extern (C) void ldap_value_free(berval**); 31 32 extern (C) int ldap_get_option(void *ld, int option, void *outvalue); 33 extern (C) int ldap_set_option(void *ld, int option, const void *invalue); 34 35 36 extern (C) int ldap_initialize(void**, const char*); 37 extern (C) char* ldap_err2string(int); 38 extern (C) void* ldap_first_entry(void *ld, void *chain ); 39 extern (C) void* ldap_next_entry(void *ld, void *entry); 40 extern (C) char* ldap_get_dn(void* ld, void* entry); 41 extern (C) int ldap_search_ext_s(void *ld, const char *base, int _scope, const char *filter, 42 char **attrs, 43 int attrsonly, 44 LDAPControl **serverctrls, 45 LDAPControl **clientctrls, 46 timeval *timeout, 47 int sizelimit, 48 void **res); // LDAPMessage 49 extern (C) char* ldap_first_attribute( 50 void *ld, 51 void *entry, 52 void **ber); 53 extern (C) char* ldap_next_attribute( 54 void *ld, 55 void *entry, 56 void *ber ); 57 extern (C) berval** ldap_get_values_len( 58 void *ld, 59 void *entry, 60 char *attr); 61 62 extern (C) int ldap_bind_s(void *ld, const char *who, const char *cred, int method); 63 extern (C) int ldap_unbind(void *ld); 64 65 66 enum LDAP_SCOPE_BASE = 0x0000, 67 LDAP_SCOPE_BASEOBJECT = LDAP_SCOPE_BASE, 68 LDAP_SCOPE_ONELEVEL = 0x0001, 69 LDAP_SCOPE_ONE = LDAP_SCOPE_ONELEVEL, 70 LDAP_SCOPE_SUBTREE = 0x0002, 71 LDAP_SCOPE_SUB = LDAP_SCOPE_SUBTREE, 72 LDAP_SCOPE_SUBORDINATE = 0x0003, /* OpenLDAP extension */ 73 LDAP_SCOPE_CHILDREN = LDAP_SCOPE_SUBORDINATE, 74 LDAP_SCOPE_DEFAULT = -1; /* OpenLDAP extension */ 75 76 enum LDAP_AUTH_SIMPLE = 0x80U; 77 78 enum LDAP_OPT_PROTOCOL_VERSION = 0x0011U; 79 80 alias SearchEntry = Tuple!(string, "dn", string[][string], "entry"); 81 alias SearchResult = SearchEntry[]; 82 83 class LDAPException: Exception { 84 this(string msg, string file = __FILE__, size_t line = __LINE__) { 85 super(msg, file, line); 86 } 87 } 88 89 struct LDAP { 90 void *ldap; 91 this(string uri) { 92 auto r = ldap_initialize(&ldap, uri.toStringz); 93 if ( r != 0 ) { 94 throw new LDAPException("Can't initialize LDAP: "~to!string(ldap_err2string(r))); 95 } 96 } 97 /// 98 /// Params: 99 /// search_base = string, search base 100 /// search_scope = int, search scope 101 /// searc_filter = string, search filter 102 /// search_attre = string[], list of attributes to return. All attributes returned if null 103 /// Returns: 104 /// SearchResult - array of tuples(dn, entry) where dn - distinguished name, entry - dictionart of attribute and values 105 /// 106 SearchResult search_s( 107 string search_base, 108 int search_scope, 109 string search_filter = "(objectClass=*)", 110 string[] search_attrs = null, 111 int attrsonly = 0 ) { 112 return search_ext_s(search_base, search_scope, search_filter, search_attrs, 113 attrsonly, null, null, null, 0); 114 } 115 116 SearchResult search_ext_s( 117 string search_base, 118 int search_scope, 119 string search_filter="(objectClass=*)", 120 string[] search_attrs = null, 121 int attrsonly = 0, 122 LDAPControl *serverctrls = null, 123 LDAPControl *clientctrls = null, 124 timeval *timeout = null, 125 int sizelimit = 0 ) { 126 127 SearchResult result; 128 129 void* res; 130 int r = ldap_search_ext_s(ldap, search_base.toStringz, search_scope, search_filter.toStringz, 131 null, // attrs 132 attrsonly, 133 &serverctrls, &clientctrls, 134 null, sizelimit, &res 135 ); 136 scope(exit) { 137 if (res) { 138 ldap_msgfree(res); 139 } 140 } 141 if ( r != 0 ) { 142 throw new LDAPException("Can't search: "~to!string(ldap_err2string(r))); 143 } 144 // loop over dn's 145 for(auto entry = ldap_first_entry(ldap, res); 146 entry != null; 147 entry = ldap_next_entry(ldap, entry) ) 148 { 149 char* c_dn = ldap_get_dn(ldap, entry); 150 string[][string] attrs; 151 auto dn = to!string(c_dn); 152 153 std.c.stdlib.free(c_dn); 154 tracef("dn: %s", dn); 155 void* berp; 156 // loop over attrs 157 for (char * attr = ldap_first_attribute(ldap, entry, &berp); 158 attr != null; 159 attr = ldap_next_attribute(ldap, entry, berp) ) 160 { 161 string attr_name = to!string(attr); 162 163 if ( !search_attrs || canFind(search_attrs, attr_name) ) { 164 berval** values = ldap_get_values_len(ldap, entry, attr); 165 for( auto bvp = values; bvp && *bvp; bvp++) { 166 berval *bv = *bvp; 167 attrs[attr_name] ~= to!string(bv.bv_val); 168 ber_bvfree(bv); 169 } 170 // XXX here should be something like ldap_values_free_len() 171 // but valgrind indicate some errors 172 ldap_memfree(values); 173 } 174 ldap_memfree(attr); 175 } 176 ber_bvarray_free(cast(berval*)berp); 177 trace(attrs); 178 179 SearchEntry e = SearchEntry(dn, attrs); 180 result ~= e; 181 } 182 return result; 183 } 184 /// 185 /// LDAP synchronous bind 186 /// Params: 187 /// who = string, DN to bind 188 /// cred = string, credentials (password in case of LDAP_AUTH_SIPLE) 189 /// Returns: 190 /// 0 if case of success, LDAP error otherwise 191 /// 192 int bind_s(string who, string cred, int method = LDAP_AUTH_SIMPLE) { 193 int r = ldap_bind_s(ldap, who.toStringz, cred.toStringz, method); 194 if ( r != 0 ) { 195 errorf("Can't bind: %s", to!string(ldap_err2string(r))); 196 } 197 return r; 198 } 199 int unbind() { 200 return ldap_unbind(ldap); 201 } 202 int get_option(int option, void* outvalue) { 203 return ldap_get_option(ldap, option, outvalue); 204 } 205 int set_option(int option, void* invalue) { 206 return ldap_set_option(ldap, option, invalue); 207 } 208 }; 209 210 unittest { 211 globalLogLevel(LogLevel.info); 212 int proto_version; 213 214 auto ldap = LDAP("ldap://ldap.forumsys.com"); 215 ldap.get_option(LDAP_OPT_PROTOCOL_VERSION, &proto_version); 216 if ( proto_version == 2) { 217 proto_version = 3; 218 ldap.set_option(LDAP_OPT_PROTOCOL_VERSION, &proto_version); 219 info("Switched to protocol version 3"); 220 } 221 222 auto r = ldap.search_s("dc=example,dc=com", 223 LDAP_SCOPE_SUBTREE, "(uid=%s)".format("einstein")); 224 225 infof("Found dn: %s", r[0].dn); 226 foreach(k,v; r[0].entry) { 227 infof("%s = %s", k, v); 228 } 229 230 int b = ldap.bind_s(r[0].dn, "password"); 231 infof("Bind using 'password': %s", b==0?"OK":"Fail"); 232 ldap.unbind(); 233 }