1 module ldap;
2 
3 import std.stdio;
4 import std.format : format;
5 import std.exception : enforce;
6 import std.string : fromStringz;
7 
8 import ldapd;
9 
10 private void tool_unbind_local(LDAP *ld) @trusted {
11 	int err = ldap_set_option(ld, LDAP_OPT_SERVER_CONTROLS, null);
12 
13 	if(err != LDAP_OPT_SUCCESS) {
14 		throw new Exception("Could not unset controls");
15 	}
16 
17 	ldap_unbind_ext( ld, null, null );
18 }
19 
20 private int tool_exit_local( LDAP *ld, int status ) @trusted {
21 	if(ld != null) {
22 		tool_unbind_local(ld);
23 	}
24 	return status;
25 }
26 
27 private LDAP* tool_conn_setup_local(char* lhost, int lport ) @trusted {
28 	LDAP *ld = null;
29 
30 	int rc;
31 	char *ldapuri;
32 
33 	scope(exit) {
34 		ldap_memfree(ldapuri);
35 	}
36 
37 	//if( ( lhost != null || lport ) && ( ldapuri == null ) ) {
38 	assert(lhost !is null && lport );
39 	/* construct URL */
40 	LDAPURLDesc url;
41 
42 	url.lud_scheme = cast(char*)"ldap".ptr;
43 	url.lud_host = lhost;
44 	url.lud_port = lport;
45 	url.lud_scope = LDAP_SCOPE_DEFAULT;
46 
47 	ldapuri = ldap_url_desc2str(&url);
48 
49 	//writefln("ldap_initialize( %s )", ldapuri !is null ? ldapuri : "<DEFAULT>" );
50 
51 	// BUG
52 	// BUG ldap_initialize leaks 40 byte of memory
53 	// BUG
54 	rc = ldap_initialize(&ld, ldapuri);
55 
56 	if(rc != LDAP_SUCCESS) {
57 		throw new Exception(format(
58 			"Could not create LDAP session handle for URI=%s (%d): %s",
59 			ldapuri, rc, ldap_err2string(rc)));
60 	}
61 
62 	/* referrals: obsolete */
63 	if(ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON) != LDAP_OPT_SUCCESS) {
64 		throw new Exception(format("Could not set LDAP_OPT_REFERRALS on"));
65 	}
66 
67 	int lprotocol = LDAP_VERSION3;
68 	if(ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &lprotocol)
69 		!= LDAP_OPT_SUCCESS )
70 	{
71 		throw new Exception(format("Could not set LDAP_OPT_PROTOCOL_VERSION %d",
72 			lprotocol) );
73 	}
74 
75 	rc = ldap_start_tls_s( ld, null, null );
76 	if(rc != LDAP_SUCCESS) {
77 		char* msg;
78 		scope(exit) {
79 			ldap_memfree(msg);
80 		}
81 		ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &msg);
82 		throw new Exception(
83 				format("ldap_start_tls %d %s", rc, fromStringz(msg).idup));
84 	}
85 
86 	timeval nettimeout;
87 	if(nettimeout.tv_sec > 0) {
88 		if(ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &nettimeout)
89 			!= LDAP_OPT_SUCCESS)
90 		{
91 			throw new Exception(format("Could not set LDAP_OPT_NETWORK_TIMEOUT %s",
92 				cast(long)nettimeout.tv_sec));
93 		}
94 	}
95 
96 	return ld;
97 }
98 
99 private int tool_exit(LDAP *ld, int status) @trusted {
100 	if(ld !is null) {
101 		tool_unbind_local(ld);
102 	}
103 	return status;
104 }
105 
106 private int tool_bind_local(LDAP *ld, char* passLocal, char* binddn) @trusted {
107 	LDAPControl	**sctrlsp;
108 	LDAPControl[4] *sctrls;
109 	int	nsctrls = 0;
110 
111 	int rc;
112 	int msgid;
113 	LDAPMessage *result;
114 
115 	int err;
116 	char *matched;
117 	char *info;
118 	char **refs;
119 	LDAPControl **ctrls;
120 	char[256] msgbuf;
121 
122 	msgbuf[0] = 0;
123 
124 	if(nsctrls) {
125 		sctrlsp = cast(LDAPControl**)sctrls.ptr;
126 	}
127 
128 	//assert( nsctrls < cast(int) (sctrls.sizeof / sctrls[0].sizeof) );
129 
130 	char *pw = passLocal;
131 
132 	berval passwd;
133 	passwd.bv_val = ber_strdup(pw);
134 	passwd.bv_len = strlen(passwd.bv_val);
135 
136 	scope(exit) {
137 		ber_memfree(passwd.bv_val);
138 		ber_memfree(matched);
139 		ber_memfree(info);
140 		ber_memvfree(cast(void **)refs);
141 	}
142 
143 	/* simple bind */
144 	rc = ldap_sasl_bind(ld, binddn, LDAP_SASL_SIMPLE, &passwd,
145 		sctrlsp, null, &msgid);
146 	if(msgid == -1) {
147 		tool_exit(ld, rc);
148 		throw new Exception(format("ldap_sasl_bind: %s (code: 0x%x)", fromStringz(ldap_err2string(rc)), rc));
149 	}
150 
151 	scope(failure) {
152 		tool_exit(ld, LDAP_LOCAL_ERROR);
153 	}
154 
155 	rc = ldap_result(ld, msgid, LDAP_MSG_ALL, null, &result);
156 	enforce(rc != -1, format("ldap_result %d", -1));
157 	enforce(rc != 0, format("ldap_result %d", LDAP_TIMEOUT));
158 
159 	if(result) {
160 		rc = ldap_parse_result( ld, result, &err, &matched, &info, &refs,
161 		                        &ctrls, 1 );
162 		if(rc != LDAP_SUCCESS) {
163 			return tool_exit(ld, LDAP_LOCAL_ERROR);
164 		}
165 	}
166 
167 	if(err != LDAP_SUCCESS
168 		|| msgbuf[0]
169 		|| (matched && matched[ 0 ])
170 		|| (info && info[ 0 ])
171 		|| refs)
172 	{
173 		if(err != LDAP_SUCCESS) {
174 			return tool_exit(ld, err);
175 		}
176 	}
177 	return 0;
178 }
179 
180 /*
181 int main(string[] args) {
182 	string un = "testuser@host.com";
183 	string pwd = "$ymm3try86!";
184 
185 	string host = "ad.host.com";
186 	LDAPLoginResult ret = login(host, un, pwd);
187 	writeln(ret);
188 	return ret.returnCode;
189 }*/
190 
191 struct LDAPLoginResult {
192 	int returnCode;
193 	string userId;
194 }
195 
196 LDAPLoginResult login(string host, string username, string password) @trusted {
197 	import std.string : toStringz;
198 	import std.conv : to;
199 	int	rc;
200 	LDAP* ld;
201 	char* matcheddn;
202 	char* text;
203 	char** refs;
204 	berval* authzid;
205 	int	id;
206 	int code = 0;
207 	LDAPMessage* res;
208 	LDAPControl** ctrls;
209 
210 	char* binddn = ber_strdup(toStringz(username));
211 	char* ldaphost = ber_strdup(toStringz(host));
212 
213 	scope(exit) {
214 		ldap_msgfree(res);
215 		ber_memfree(text);
216 		ber_memfree(matcheddn);
217 		ber_memvfree(cast(void **) refs);
218 		ber_bvfree(authzid);
219 		ber_memfree(binddn);
220 		ber_memfree(ldaphost);
221 	}
222 
223 	/* LDAPv3 only */
224 
225 	ld = tool_conn_setup_local(ldaphost, 389);
226 	enforce(ld != null, format("Failed to connect to LDAP server"));
227 
228 	int ret = tool_bind_local( ld, cast(char*)toStringz(password), binddn);
229 
230 	enforce(ret == LDAP_SUCCESS, format("LDAP bind failed: %s (code: 0x%x)", fromStringz(ldap_err2string(ret)), ret));
231 
232 	rc = ldap_whoami( ld, null, null, &id );
233 
234 	enforce(ret == LDAP_SUCCESS, format("ldap_whoami failed: %s (code: 0x%x)", fromStringz(ldap_err2string(rc)), rc));
235 
236 	for( ; ; ) {
237 		scope(failure) {
238 			rc = tool_exit_local( ld, rc );
239 		}
240 
241 		timeval tv;
242 
243 		tv.tv_sec = 0;
244 		tv.tv_usec = 100000;
245 
246 		rc = ldap_result( ld, LDAP_RES_ANY, LDAP_MSG_ALL, &tv, &res );
247 		enforce(rc >= 0, format("ldap_result: %s (code: 0x%x)", fromStringz(ldap_err2string(rc)), rc));
248 
249 		if(rc != 0) {
250 			break;
251 		}
252 	}
253 
254 	rc = ldap_parse_result( ld, res,
255 		&code, &matcheddn, &text, &refs, &ctrls, 0 );
256 
257 	if(rc == LDAP_SUCCESS) {
258 		rc = code;
259 	}
260 
261 	enforce(rc == LDAP_SUCCESS, format("ldap_parse_result: %s (code: 0x%x)", fromStringz(ldap_err2string(rc)), rc));
262 
263 	rc = ldap_parse_whoami( ld, res, &authzid );
264 	enforce(rc == LDAP_SUCCESS, format("ldap_parse_whoami: %s (code: 0x%x)", fromStringz(ldap_err2string(rc)), rc));
265 
266 	LDAPLoginResult rslt;
267 	if(authzid != null) {
268 		if(authzid.bv_len == 0) {
269 			rslt.userId = "anonymous";
270 		} else {
271 			rslt.userId = to!string(fromStringz(authzid.bv_val));
272 		}
273 	}
274 
275 	/* disconnect from server */
276 	rslt.returnCode = tool_exit_local(ld, code == LDAP_SUCCESS ? 0 : 1 );
277 	return rslt;
278 }