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