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 }