1 #include <uwsgi.h>
2 
3 /*
4 	Authors:
5 
6 	Roberto De Ioris <roberto@unbit.it> - general LDAP support, reading uWSGI configuration from LDAP servers
7 
8 	Łukasz Mierzwa <l.mierzwa@gmail.com> - LDAP auth router support
9 */
10 
11 extern struct uwsgi_server uwsgi;
12 
13 #include <ldap.h>
14 
15 #ifndef LDAP_OPT_SUCCESS
16 #define LDAP_OPT_SUCCESS LDAP_SUCCESS
17 #endif
18 
19 #ifndef ldap_unbind_ext_s
20 #define ldap_unbind_ext_s ldap_unbind_ext
21 #endif
22 
23 static void uwsgi_opt_ldap_dump(char *, char *, void *);
24 static void uwsgi_opt_ldap_dump_ldif(char *, char *, void *);
25 static void uwsgi_ldap_config(char *);
26 
uwsgi_opt_load_ldap(char * opt,char * url,void * none)27 static void uwsgi_opt_load_ldap(char *opt, char *url, void *none) {
28         uwsgi_ldap_config(url);
29 }
30 
31 static struct uwsgi_option uwsgi_ldap_options[] = {
32 	{"ldap", required_argument, 0, "load configuration from ldap server", uwsgi_opt_load_ldap, NULL, UWSGI_OPT_IMMEDIATE},
33         {"ldap-schema", no_argument, 0, "dump uWSGI ldap schema", uwsgi_opt_ldap_dump, NULL, UWSGI_OPT_IMMEDIATE},
34         {"ldap-schema-ldif", no_argument, 0, "dump uWSGI ldap schema in ldif format", uwsgi_opt_ldap_dump_ldif, NULL, UWSGI_OPT_IMMEDIATE},
35         {0, 0, 0, 0, 0, 0, 0},
36 };
37 
ldap2uwsgi(char * ldapname,char * uwsginame)38 static void ldap2uwsgi(char *ldapname, char *uwsginame) {
39 	char *ptr = uwsginame;
40 
41 	int i;
42 
43 	for (i = 0; i < (int) strlen(ldapname); i++) {
44 		if (isupper((int) ldapname[i])) {
45 			*ptr++ = '-';
46 			*ptr++ = tolower((int) ldapname[i]);
47 		}
48 		else {
49 			*ptr++ = ldapname[i];
50 		}
51 	}
52 
53 	*ptr++ = 0;
54 }
55 
calc_ldap_name(char * name)56 static int calc_ldap_name(char *name) {
57 	int i;
58 	int counter = 0;
59 
60 	for (i = 0; i < (int) strlen(name); i++) {
61 		if (isupper((int) name[i])) {
62 			counter++;
63 		}
64 	}
65 
66 	return strlen(name) + counter;
67 }
68 
69 struct uwsgi_ldap_entry {
70 	int num;
71 	char names[1024];
72 	int has_arg;
73 };
74 
75 
uwsgi_name_to_ldap(char * src,char * dst)76 static void uwsgi_name_to_ldap(char *src, char *dst) {
77 
78 	int i;
79 	char *ptr = dst;
80 
81 	memset(dst, 0, 1024);
82 
83 	strcat(dst, " 'uWSGI");
84 
85 	ptr += 7;
86 
87 	for (i = 0; i < (int) strlen(src); i++) {
88 		if (src[i] == '-') {
89 			i++;
90 			*ptr++ = toupper((int) src[i]);
91 		}
92 		else {
93 			*ptr++ = src[i];
94 		}
95 	}
96 
97 	strcat(dst, "'");
98 
99 }
100 
search_ldap_cache(struct uwsgi_ldap_entry * root,char * name,int count)101 static struct uwsgi_ldap_entry *search_ldap_cache(struct uwsgi_ldap_entry *root, char *name, int count) {
102 	int i;
103 	struct uwsgi_ldap_entry *ule;
104 
105 	for (i = 0; i < count; i++) {
106 		ule = &root[i];
107 		if (uwsgi_list_has_str(ule->names, name + 1)) {
108 			return ule;
109 		}
110 	}
111 
112 	return NULL;
113 }
114 
get_ldap_names(int * count)115 static struct uwsgi_ldap_entry *get_ldap_names(int *count) {
116 
117 	struct uwsgi_option *op = uwsgi.options;
118 	struct uwsgi_ldap_entry *ule, *entry;
119 	char ldap_name[1024];
120 
121 	*count = 0;
122 
123 	ule = uwsgi_malloc(sizeof(struct uwsgi_ldap_entry) * uwsgi_count_options(op));
124 
125 	while (op && op->name) {
126 
127 		uwsgi_name_to_ldap((char *) op->name, ldap_name);
128 
129 		entry = search_ldap_cache(ule, ldap_name, *count);
130 
131 		if (entry)
132 			goto next;
133 
134 		entry = &ule[*count];
135 		entry->num = *count;
136 		strcpy(entry->names, ldap_name);
137 		*count = *count + 1;
138 
139 		entry->has_arg = op->type;
140 
141 next:
142 		op++;
143 	}
144 
145 	return ule;
146 }
147 
uwsgi_opt_ldap_dump_ldif(char * opt,char * foo,void * bar)148 static void uwsgi_opt_ldap_dump_ldif(char *opt, char *foo, void *bar) {
149 
150 	int i;
151 	int items;
152 
153 	uwsgi_log("\n");
154 	uwsgi_log("dn: cn=uwsgi,cn=schema,cn=config\n");
155 	uwsgi_log("objectClass: olcSchemaConfig\n");
156 	uwsgi_log("cn: uwsgi\n");
157 
158 	struct uwsgi_ldap_entry *entry, *ule = get_ldap_names(&items);
159 
160 	for (i = 0; i < items; i++) {
161 
162 		entry = &ule[i];
163 		uwsgi_log("olcAttributeTypes: ( 1.3.6.1.4.1.35156.17.4.%d NAME (%s", entry->num, entry->names);
164 
165 		if (entry->has_arg) {
166 			uwsgi_log(" ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )\n");
167 		}
168 		else {
169 			uwsgi_log(" ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )\n");
170 		}
171 	}
172 
173 	uwsgi_log("olcAttributeTypes: ( 1.3.6.1.4.1.35156.17.4.50000 NAME 'uWSGInull' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )\n");
174 
175 
176 	uwsgi_log("olcObjectClasses: ( 1.3.6.1.4.1.35156.17.3.1 NAME 'uWSGIConfig' SUP top AUXILIARY DESC 'uWSGI configuration' MAY ( ");
177 
178 
179 	for (i = 0; i < items; i++) {
180 
181 		entry = &ule[i];
182 
183 		char *list2 = uwsgi_concat2(entry->names + 1, "");
184 		char *p, *ctx = NULL;
185 		uwsgi_foreach_token(list2, " ", p, ctx) {
186 			uwsgi_log("%.*s $ ", strlen(p) - 2, p + 1);
187 		}
188 
189 		free(list2);
190 
191 	}
192 
193 	uwsgi_log("uWSGInull ))\n");
194 
195 	uwsgi_log("\n");
196 
197 	exit(0);
198 }
199 
uwsgi_opt_ldap_dump(char * opt,char * foo,void * bar)200 static void uwsgi_opt_ldap_dump(char *opt, char *foo, void *bar) {
201 
202 	int i;
203 	int items;
204 
205 	struct uwsgi_ldap_entry *entry, *ule = get_ldap_names(&items);
206 
207 	for (i = 0; i < items; i++) {
208 
209 		entry = &ule[i];
210 		uwsgi_log("attributetype ( 1.3.6.1.4.1.35156.17.4.%d NAME (%s", entry->num, entry->names);
211 
212 		if (entry->has_arg) {
213 			uwsgi_log(" ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )\n");
214 		}
215 		else {
216 			uwsgi_log(" ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )\n");
217 		}
218 	}
219 
220 
221 	uwsgi_log("attributetype ( 1.3.6.1.4.1.35156.17.4.50000 NAME 'uWSGInull' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )\n");
222 
223 	uwsgi_log("objectclass ( 1.3.6.1.4.1.35156.17.3.1 NAME 'uWSGIConfig' SUP top AUXILIARY DESC 'uWSGI configuration' MAY ( ");
224 
225 	for (i = 0; i < items; i++) {
226 
227 		entry = &ule[i];
228 
229 		char *list2 = uwsgi_concat2(entry->names + 1, "");
230 		char *p, *ctx = NULL;
231 		uwsgi_foreach_token(list2, " ", p, ctx) {
232 			uwsgi_log("%.*s $ ", strlen(p) - 2, p + 1);
233 		}
234 
235 		free(list2);
236 
237 	}
238 
239 
240 
241 
242 	uwsgi_log("uWSGInull ))\n");
243 
244 	exit(0);
245 }
246 
uwsgi_ldap_config(char * url)247 static void uwsgi_ldap_config(char *url) {
248 
249 	LDAP *ldp;
250 	LDAPMessage *results, *entry;
251 	BerElement *ber;
252 	struct berval **bervalues;
253 	char *attr;
254 	char *uwsgi_attr;
255 
256 	char *url_slash;
257 
258 	int desired_version = LDAP_VERSION3;
259 	int ret;
260 
261 	LDAPURLDesc *ldap_url;
262 
263 	if (!ldap_is_ldap_url(url)) {
264 		uwsgi_log("invalid LDAP url.\n");
265 		exit(1);
266 	}
267 
268 	if (ldap_url_parse(url, &ldap_url) != LDAP_SUCCESS) {
269 		uwsgi_log("unable to parse LDAP url.\n");
270 		exit(1);
271 	}
272 
273 	uwsgi_log("[uWSGI] getting LDAP configuration from %s\n", url);
274 
275 	url_slash = strchr(url, '/');
276 	url_slash = strchr(url_slash + 1, '/');
277 
278 	url_slash = strchr(url_slash + 1, '/');
279 	if (url_slash) {
280 		url_slash[0] = 0;
281 	}
282 
283 #ifdef UWSGI_DEBUG
284 	uwsgi_debug("LDAP URL: %s\n", url);
285 	uwsgi_debug("LDAP BASE DN: %s\n", ldap_url->lud_dn);
286 #endif
287 
288 #if LDAP_API_VERSION >= 3000
289 	if ((ret = ldap_initialize(&ldp, url)) != LDAP_SUCCESS) {
290 		uwsgi_log("LDAP: %s\n", ldap_err2string(ret));
291 		exit(1);
292 	}
293 #else
294 	if ((ldp = ldap_init(ldap_url->lud_host, ldap_url->lud_port)) == NULL) {
295 		uwsgi_error("ldap_init()");
296 		exit(1);
297 	}
298 #endif
299 
300 
301 	if ((ret = ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_OPT_SUCCESS) {
302 		uwsgi_log("LDAP: %s\n", ldap_err2string(ret));
303 		exit(1);
304 	}
305 
306 
307 	if ((ret = ldap_search_ext_s(ldp, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter, NULL, 0, NULL, NULL, NULL, 1, &results)) != LDAP_SUCCESS) {
308 		uwsgi_log("LDAP: %s\n", ldap_err2string(ret));
309 		exit(1);
310 	}
311 
312 #ifdef UWSGI_DEBUG
313 	uwsgi_debug("LDAP connection initialized %p\n", ldp);
314 #endif
315 
316 	free(ldap_url);
317 
318 	if (ldap_count_entries(ldp, results) < 1) {
319 		uwsgi_log("no LDAP entry found\n");
320 		exit(1);
321 	}
322 
323 	entry = ldap_first_entry(ldp, results);
324 
325 	int found = 0;
326 	for (attr = ldap_first_attribute(ldp, entry, &ber); attr != NULL; attr = ldap_next_attribute(ldp, entry, ber)) {
327 		if (!strncmp(attr, "uWSGI", 5)) {
328 
329 			found = 1;
330 			uwsgi_attr = malloc(calc_ldap_name(attr) + 1);
331 			if (!uwsgi_attr) {
332 				uwsgi_error("malloc()");
333 				exit(1);
334 			}
335 
336 			ldap2uwsgi(attr + 5, uwsgi_attr);
337 
338 #ifdef UWSGI_DEBUG
339 			uwsgi_debug("LDAP attribute: %s = --%s\n", attr, uwsgi_attr);
340 #endif
341 			bervalues = ldap_get_values_len(ldp, entry, attr);
342 			if (bervalues) {
343 				// do not free uwsgi_attr/uwsgi_val;
344 				char *uwsgi_val = malloc(bervalues[0]->bv_len + 1);
345 				if (!uwsgi_val) {
346 					uwsgi_error("malloc()");
347 					exit(1);
348 				}
349 
350 				memcpy(uwsgi_val, bervalues[0]->bv_val, bervalues[0]->bv_len);
351 				uwsgi_val[bervalues[0]->bv_len] = 0;
352 
353 				add_exported_option((char *) uwsgi_attr, uwsgi_val, 0);
354 				free(bervalues);
355 			}
356 			else {
357 				free(uwsgi_attr);
358 			}
359 		}
360 		free(attr);
361 	}
362 
363 	if (!found) {
364 		uwsgi_log("no uWSGI LDAP entry found\n");
365 		exit(1);
366 	}
367 
368 	free(ber);
369 	free(results);
370 
371 	ldap_unbind_ext_s(ldp, NULL, NULL);
372 
373 }
374 
375 #ifdef UWSGI_ROUTING
376 
377 struct uwsgi_ldapauth_config {
378 	char *url;
379 	LDAPURLDesc *ldap_url;
380 	char *binddn;
381 	char *bindpw;
382 	char *basedn;
383 	char *filter;
384 	char *login_attr;
385 	int loglevel;
386 };
387 
ldap_passwd_check(struct uwsgi_ldapauth_config * ulc,char * auth)388 static uint16_t ldap_passwd_check(struct uwsgi_ldapauth_config *ulc, char *auth) {
389 
390 	char *colon = strchr(auth, ':');
391 	if (!colon) return 0;
392 
393 	int ret;
394 	uint16_t ulen = 0;
395 	LDAP *ldp;
396 	int desired_version = LDAP_VERSION3;
397 
398 #if LDAP_API_VERSION >= 3000
399         if ((ret = ldap_initialize(&ldp, ulc->url)) != LDAP_SUCCESS) {
400 		uwsgi_log("[router-ldapauth] can't connect to LDAP server at %s\n", ulc->url);
401 		return 0;
402         }
403 #else
404 	if ((ldp = ldap_init(ulc->ldap_url->lud_host, ulc->ldap_url->lud_port)) == NULL) {
405 		uwsgi_log("[router-ldapauth] can't connect to LDAP server at %s\n", ulc->url);
406 		return 0;
407 	}
408 #endif
409 
410 	if ((ret = ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_OPT_SUCCESS) {
411 		uwsgi_log("[router-ldapauth] LDAP protocol version mismatch: %s\n", ldap_err2string(ret));
412 		goto close;
413 	}
414 
415 	// first bind if needed
416 	if (ulc->binddn && ulc->bindpw) {
417 #if LDAP_API_VERSION >= 3000
418 		struct berval bval;
419 		bval.bv_val = ulc->bindpw;
420 		bval.bv_len = strlen(bval.bv_val);
421 		if ((ret = ldap_sasl_bind_s(ldp, ulc->binddn, LDAP_SASL_SIMPLE, &bval, NULL, NULL, NULL)) != LDAP_OPT_SUCCESS) {
422 #else
423 		if ((ret = ldap_bind_s(ldp, ulc->binddn, ulc->bindpw, LDAP_AUTH_SIMPLE)) != LDAP_OPT_SUCCESS) {
424 #endif
425 			uwsgi_log("[router-ldapauth] can't bind as user '%s' to '%s': %s\n", ulc->binddn, ulc->url, ldap_err2string(ret));
426 			goto close;
427 		}
428 	}
429 
430 	// search for user
431 	char *userdn = NULL;
432 	LDAPMessage *msg, *entry;
433 	// use the minimal amount of memory
434 	char *filter = uwsgi_malloc( strlen(ulc->login_attr) + (colon-auth) + strlen(ulc->filter) + 7);
435 	ret = snprintf(filter, 1024, "(&(%s=%.*s)%s)", ulc->login_attr, (int) (colon-auth), auth, ulc->filter);
436 	if (ret <= 0 || ret >= 1024) {
437 		free(filter);
438 		uwsgi_error("ldap_passwd_check()/sprintfn(filter)");
439 		goto close;
440 	}
441 
442 	if ((ret = ldap_search_ext_s(ldp, ulc->basedn, LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS) {
443 		free(filter);
444 		uwsgi_log("[router-ldapauth] search error on '%s': %s\n", ulc->url, ldap_err2string(ret));
445 		goto close;
446 	}
447 	else {
448 		free(filter);
449 		entry = ldap_first_entry(ldp, msg);
450 		while (entry) {
451 			struct berval **vals = ldap_get_values_len(ldp, entry, ulc->login_attr);
452 			if (!uwsgi_strncmp(auth, colon-auth, vals[0]->bv_val, vals[0]->bv_len)) {
453 				userdn = ldap_get_dn(ldp, entry);
454 				free(vals);
455 				break;
456 			}
457 			free(vals);
458 			entry = ldap_next_entry(ldp, entry);
459 		}
460 		ldap_msgfree(msg);
461 	}
462 
463 	if (userdn) {
464 		// user found in ldap, try to bind
465 
466 #if LDAP_API_VERSION >= 3000
467                 struct berval bval;
468                 bval.bv_val = colon+1;
469                 bval.bv_len = strlen(bval.bv_val);
470                 if ((ret = ldap_sasl_bind_s(ldp, userdn, LDAP_SASL_SIMPLE, &bval, NULL, NULL, NULL)) != LDAP_OPT_SUCCESS) {
471 #else
472 		if ((ret = ldap_bind_s(ldp, userdn, colon+1, LDAP_AUTH_SIMPLE)) != LDAP_OPT_SUCCESS) {
473 #endif
474 			if (ulc->loglevel)
475 				uwsgi_log("[router-ldapauth] can't bind as user '%s' to '%s': %s\n", userdn, ulc->url, ldap_err2string(ret));
476 		}
477 		else {
478 			if (ulc->loglevel > 1)
479 				uwsgi_log("[router-ldapauth] successful bind as user '%s' to '%s'\n", userdn, ulc->url);
480 			ulen = colon-auth;
481 		}
482 
483 		ldap_memfree(userdn);
484 	}
485 	else if (ulc->loglevel) {
486 		uwsgi_log("router-ldapauth] user '%.*s' not found in LDAP server at '%s'\n", colon-auth, auth, ulc->url);
487 	}
488 
489 close:
490 	if ((ret = ldap_unbind_ext_s(ldp, NULL, NULL)) != LDAP_OPT_SUCCESS) {
491 		uwsgi_log("[router-ldapauth] LDAP unbind error: %s\n", ldap_err2string(ret));
492 	}
493 
494 	return ulen;
495 }
496 
497 int uwsgi_routing_func_ldapauth(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
498 
499 	// skip if already authenticated
500 	if (wsgi_req->remote_user_len > 0) {
501 		return UWSGI_ROUTE_NEXT;
502 	}
503 
504 	if (wsgi_req->authorization_len > 7 && ur->data2) {
505 		if (strncmp(wsgi_req->authorization, "Basic ", 6))
506 			goto forbidden;
507 
508 		size_t auth_len = 0;
509 		char *auth = uwsgi_base64_decode(wsgi_req->authorization+6, wsgi_req->authorization_len-6, &auth_len);
510 		if (auth) {
511 			if (!ur->custom) {
512 				uint16_t ulen = ldap_passwd_check(ur->data2, auth);
513 				if (ulen > 0) {
514 					wsgi_req->remote_user = uwsgi_req_append(wsgi_req, "REMOTE_USER", 11, auth, ulen);
515 					if (wsgi_req->remote_user)
516 						wsgi_req->remote_user_len = ulen;
517 				}
518 				else if (ur->data3_len == 0) {
519 					free(auth);
520 					goto forbidden;
521 				}
522 			}
523 			free(auth);
524 			return UWSGI_ROUTE_NEXT;
525 		}
526 	}
527 
528 forbidden:
529 	if (uwsgi_response_prepare_headers(wsgi_req, "401 Authorization Required", 26)) goto end;
530 	char *realm = uwsgi_concat3n("Basic realm=\"", 13, ur->data, ur->data_len, "\"", 1);
531 	int ret = uwsgi_response_add_header(wsgi_req, "WWW-Authenticate", 16, realm, 13 + ur->data_len + 1);
532 	free(realm);
533 	if (ret) goto end;
534 	uwsgi_response_write_body_do(wsgi_req, "Unauthorized", 12);
535 end:
536 	return UWSGI_ROUTE_BREAK;
537 }
538 
539 static int uwsgi_router_ldapauth(struct uwsgi_route *ur, char *args) {
540 
541 	ur->func = uwsgi_routing_func_ldapauth;
542 
543 	char *comma = strchr(args, ',');
544 	if (!comma) {
545 		uwsgi_log("invalid route syntax: %s\n", args);
546 		exit(1);
547 	}
548 	*comma = 0;
549 
550 	ur->data = args;
551 	ur->data_len = strlen(args);
552 
553 	char *url = NULL;
554 	char *binddn = NULL;
555 	char *bindpw = NULL;
556 	char *basedn = NULL;
557 	char *filter = NULL;
558 	char *attr = NULL;
559 	char *loglevel = NULL;
560 	if (uwsgi_kvlist_parse(comma+1, strlen(comma+1), ';', '=',
561 		"url", &url,
562 		"binddn", &binddn,
563 		"bindpw", &bindpw,
564 		"basedn", &basedn,
565 		"filter", &filter,
566 		"attr", &attr,
567 		"loglevel", &loglevel,
568 		NULL)) {
569 			uwsgi_log("[router-ldapauth] unable to parse options: %s\n", comma+1);
570 			exit(1);
571 	}
572 	else {
573 		struct uwsgi_ldapauth_config *ulc = uwsgi_malloc(sizeof(struct uwsgi_ldapauth_config));
574 
575 		if (!basedn) {
576 			uwsgi_log("[router-ldapauth] missing LDAP base dn (basedn option) on line: %s\n", comma+1);
577 			exit(1);
578 		}
579 		else {
580 			ulc->basedn = basedn;
581 		}
582 
583 		if (!url) {
584 			uwsgi_log("[router-ldapauth] missing LDAP server url (url option) on line: %s\n", comma+1);
585 			exit(1);
586 		}
587 		else {
588 			if (!ldap_is_ldap_url(url)) {
589 				uwsgi_log("[router-ldapauth] invalid LDAP url: %s\n", url);
590 				exit(1);
591 			}
592 			if (ldap_url_parse(url, &ulc->ldap_url) != LDAP_SUCCESS) {
593 				uwsgi_log("[router-ldapauth] unable to parse LDAP url: %s\n", url);
594 				exit(1);
595 				}
596 		}
597 
598 		if (!filter) {
599 			ulc->filter = uwsgi_str("(objectClass=*)");
600 		}
601 		else {
602 			ulc->filter = filter;
603 		}
604 
605 		if (!attr) {
606 			ulc->login_attr = uwsgi_str("uid");
607 		}
608 		else {
609 			ulc->login_attr = attr;
610 		}
611 
612 		ulc->url = url;
613 
614 		ulc->binddn = binddn;
615 		ulc->bindpw = bindpw;
616 
617 		if (loglevel) {
618 			ulc->loglevel = atoi(loglevel);
619 		}
620 		else {
621 			ulc->loglevel = 0;
622 		}
623 
624 		ur->data2 = ulc;
625 	}
626 
627 	return 0;
628 }
629 
630 static int uwsgi_router_ldapauth_next(struct uwsgi_route *ur, char *args) {
631 	ur->data3_len = 1;
632 	return uwsgi_router_ldapauth(ur, args);
633 }
634 #endif
635 
636 void uwsgi_ldap_register(void) {
637 #ifdef UWSGI_ROUTING
638 	uwsgi_register_router("ldapauth", uwsgi_router_ldapauth);
639 	uwsgi_register_router("ldapauth-next", uwsgi_router_ldapauth_next);
640 #endif
641 }
642 
643 struct uwsgi_plugin ldap_plugin = {
644 	.name = "ldap",
645 	.options = uwsgi_ldap_options,
646 	.on_load = uwsgi_ldap_register,
647 };
648 
649