1 #include "first.h" 2 3 #include <ldap.h> 4 5 #include "base.h" 6 #include "http_auth.h" 7 #include "log.h" 8 #include "plugin.h" 9 10 #include <stdlib.h> 11 #include <string.h> 12 13 typedef struct { 14 LDAP *ldap; 15 log_error_st *errh; 16 const char *auth_ldap_hostname; 17 const char *auth_ldap_binddn; 18 const char *auth_ldap_bindpw; 19 const char *auth_ldap_cafile; 20 int auth_ldap_starttls; 21 } plugin_config_ldap; 22 23 typedef struct { 24 plugin_config_ldap *ldc; 25 const char *auth_ldap_basedn; 26 const buffer *auth_ldap_filter; 27 const buffer *auth_ldap_groupmember; 28 int auth_ldap_allow_empty_pw; 29 30 int auth_ldap_starttls; 31 const char *auth_ldap_binddn; 32 const char *auth_ldap_bindpw; 33 const char *auth_ldap_cafile; 34 } plugin_config; 35 36 typedef struct { 37 PLUGIN_DATA; 38 plugin_config defaults; 39 plugin_config conf; 40 41 buffer ldap_filter; 42 } plugin_data; 43 44 static handler_t mod_authn_ldap_basic(request_st * const r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 45 46 INIT_FUNC(mod_authn_ldap_init) { 47 static http_auth_backend_t http_auth_backend_ldap = 48 { "ldap", mod_authn_ldap_basic, NULL, NULL }; 49 plugin_data *p = calloc(1, sizeof(*p)); 50 51 /* register http_auth_backend_ldap */ 52 http_auth_backend_ldap.p_d = p; 53 http_auth_backend_set(&http_auth_backend_ldap); 54 55 return p; 56 } 57 58 FREE_FUNC(mod_authn_ldap_free) { 59 plugin_data * const p = p_d; 60 if (NULL == p->cvlist) return; 61 /* (init i to 0 if global context; to 1 to skip empty global context) */ 62 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 63 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 64 for (; -1 != cpv->k_id; ++cpv) { 65 switch (cpv->k_id) { 66 case 0: /* auth.backend.ldap.hostname */ 67 if (cpv->vtype == T_CONFIG_LOCAL) { 68 plugin_config_ldap *s = cpv->v.v; 69 if (NULL != s->ldap) ldap_unbind_ext_s(s->ldap, NULL, NULL); 70 free(s); 71 } 72 break; 73 default: 74 break; 75 } 76 } 77 } 78 79 free(p->ldap_filter.ptr); 80 } 81 82 static void mod_authn_ldap_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 83 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 84 case 0: /* auth.backend.ldap.hostname */ 85 if (cpv->vtype == T_CONFIG_LOCAL) 86 pconf->ldc = cpv->v.v; 87 break; 88 case 1: /* auth.backend.ldap.base-dn */ 89 if (cpv->vtype == T_CONFIG_LOCAL) 90 pconf->auth_ldap_basedn = cpv->v.v; 91 break; 92 case 2: /* auth.backend.ldap.filter */ 93 pconf->auth_ldap_filter = cpv->v.v; 94 break; 95 case 3: /* auth.backend.ldap.ca-file */ 96 pconf->auth_ldap_cafile = cpv->v.v; 97 break; 98 case 4: /* auth.backend.ldap.starttls */ 99 pconf->auth_ldap_starttls = (int)cpv->v.u; 100 break; 101 case 5: /* auth.backend.ldap.bind-dn */ 102 pconf->auth_ldap_binddn = cpv->v.v; 103 break; 104 case 6: /* auth.backend.ldap.bind-pw */ 105 pconf->auth_ldap_bindpw = cpv->v.v; 106 break; 107 case 7: /* auth.backend.ldap.allow-empty-pw */ 108 pconf->auth_ldap_allow_empty_pw = (int)cpv->v.u; 109 break; 110 case 8: /* auth.backend.ldap.groupmember */ 111 pconf->auth_ldap_groupmember = cpv->v.b; 112 break; 113 default:/* should not happen */ 114 return; 115 } 116 } 117 118 static void mod_authn_ldap_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 119 do { 120 mod_authn_ldap_merge_config_cpv(pconf, cpv); 121 } while ((++cpv)->k_id != -1); 122 } 123 124 static void mod_authn_ldap_patch_config(request_st * const r, plugin_data * const p) { 125 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 126 for (int i = 1, used = p->nconfig; i < used; ++i) { 127 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 128 mod_authn_ldap_merge_config(&p->conf, 129 p->cvlist + p->cvlist[i].v.u2[0]); 130 } 131 } 132 133 /*(copied from mod_vhostdb_ldap.c)*/ 134 static void mod_authn_add_scheme (server *srv, buffer *host) 135 { 136 if (!buffer_string_is_empty(host)) { 137 /* reformat hostname(s) as LDAP URIs (scheme://host:port) */ 138 static const char *schemes[] = { 139 "ldap://", "ldaps://", "ldapi://", "cldap://" 140 }; 141 char *b, *e = host->ptr; 142 buffer * const tb = srv->tmp_buf; 143 buffer_clear(tb); 144 while (*(b = e)) { 145 unsigned int j; 146 while (*b==' '||*b=='\t'||*b=='\r'||*b=='\n'||*b==',') ++b; 147 if (*b == '\0') break; 148 e = b; 149 while (*e!=' '&&*e!='\t'&&*e!='\r'&&*e!='\n'&&*e!=','&&*e!='\0') 150 ++e; 151 if (!buffer_string_is_empty(tb)) 152 buffer_append_string_len(tb, CONST_STR_LEN(",")); 153 for (j = 0; j < sizeof(schemes)/sizeof(char *); ++j) { 154 if (buffer_eq_icase_ssn(b, schemes[j], strlen(schemes[j]))) { 155 break; 156 } 157 } 158 if (j == sizeof(schemes)/sizeof(char *)) 159 buffer_append_string_len(tb, CONST_STR_LEN("ldap://")); 160 buffer_append_string_len(tb, b, (size_t)(e - b)); 161 } 162 buffer_copy_buffer(host, tb); 163 } 164 } 165 166 SETDEFAULTS_FUNC(mod_authn_ldap_set_defaults) { 167 static const config_plugin_keys_t cpk[] = { 168 { CONST_STR_LEN("auth.backend.ldap.hostname"), 169 T_CONFIG_STRING, 170 T_CONFIG_SCOPE_CONNECTION } 171 ,{ CONST_STR_LEN("auth.backend.ldap.base-dn"), 172 T_CONFIG_STRING, 173 T_CONFIG_SCOPE_CONNECTION } 174 ,{ CONST_STR_LEN("auth.backend.ldap.filter"), 175 T_CONFIG_STRING, 176 T_CONFIG_SCOPE_CONNECTION } 177 ,{ CONST_STR_LEN("auth.backend.ldap.ca-file"), 178 T_CONFIG_STRING, 179 T_CONFIG_SCOPE_CONNECTION } 180 ,{ CONST_STR_LEN("auth.backend.ldap.starttls"), 181 T_CONFIG_BOOL, 182 T_CONFIG_SCOPE_CONNECTION } 183 ,{ CONST_STR_LEN("auth.backend.ldap.bind-dn"), 184 T_CONFIG_STRING, 185 T_CONFIG_SCOPE_CONNECTION } 186 ,{ CONST_STR_LEN("auth.backend.ldap.bind-pw"), 187 T_CONFIG_STRING, 188 T_CONFIG_SCOPE_CONNECTION } 189 ,{ CONST_STR_LEN("auth.backend.ldap.allow-empty-pw"), 190 T_CONFIG_BOOL, 191 T_CONFIG_SCOPE_CONNECTION } 192 ,{ CONST_STR_LEN("auth.backend.ldap.groupmember"), 193 T_CONFIG_STRING, 194 T_CONFIG_SCOPE_CONNECTION } 195 ,{ NULL, 0, 196 T_CONFIG_UNSET, 197 T_CONFIG_SCOPE_UNSET } 198 }; 199 200 plugin_data * const p = p_d; 201 if (!config_plugin_values_init(srv, p, cpk, "mod_authn_ldap")) 202 return HANDLER_ERROR; 203 204 /* process and validate config directives 205 * (init i to 0 if global context; to 1 to skip empty global context) */ 206 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 207 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 208 plugin_config_ldap *ldc = NULL; 209 char *binddn = NULL, *bindpw = NULL, *cafile = NULL; 210 int starttls = 0; 211 for (; -1 != cpv->k_id; ++cpv) { 212 switch (cpv->k_id) { 213 case 0: /* auth.backend.ldap.hostname */ 214 if (!buffer_string_is_empty(cpv->v.b)) { 215 buffer *b; 216 *(const buffer **)&b = cpv->v.b; 217 mod_authn_add_scheme(srv, b); 218 ldc = malloc(sizeof(plugin_config_ldap)); 219 force_assert(ldc); 220 ldc->errh = srv->errh; 221 ldc->auth_ldap_hostname = b->ptr; 222 cpv->v.v = ldc; 223 } 224 else { 225 cpv->v.v = NULL; 226 } 227 cpv->vtype = T_CONFIG_LOCAL; 228 break; 229 case 1: /* auth.backend.ldap.base-dn */ 230 cpv->vtype = T_CONFIG_LOCAL; 231 cpv->v.v = !buffer_string_is_empty(cpv->v.b) 232 ? cpv->v.b->ptr 233 : NULL; 234 break; 235 case 2: /* auth.backend.ldap.filter */ 236 if (!buffer_string_is_empty(cpv->v.b)) { 237 buffer *b; 238 *(const buffer **)&b = cpv->v.b; 239 if (*b->ptr != ',') { 240 /*(translate $ to ? for consistency w/ other modules)*/ 241 char *d = b->ptr; 242 for (; NULL != (d = strchr(d, '$')); ++d) *d = '?'; 243 if (NULL == strchr(b->ptr, '?')) { 244 log_error(srv->errh, __FILE__, __LINE__, 245 "ldap: %s is missing a replace-operator '?'", 246 cpk[cpv->k_id].k); 247 return HANDLER_ERROR; 248 } 249 } 250 cpv->v.v = b; 251 } 252 else { 253 cpv->v.v = NULL; 254 } 255 cpv->vtype = T_CONFIG_LOCAL; 256 break; 257 case 3: /* auth.backend.ldap.ca-file */ 258 cafile = !buffer_string_is_empty(cpv->v.b) 259 ? cpv->v.b->ptr 260 : NULL; 261 cpv->vtype = T_CONFIG_LOCAL; 262 cpv->v.v = cafile; 263 break; 264 case 4: /* auth.backend.ldap.starttls */ 265 starttls = (int)cpv->v.u; 266 break; 267 case 5: /* auth.backend.ldap.bind-dn */ 268 binddn = !buffer_string_is_empty(cpv->v.b) 269 ? cpv->v.b->ptr 270 : NULL; 271 cpv->vtype = T_CONFIG_LOCAL; 272 cpv->v.v = binddn; 273 break; 274 case 6: /* auth.backend.ldap.bind-pw */ 275 cpv->vtype = T_CONFIG_LOCAL; 276 cpv->v.v = bindpw = cpv->v.b->ptr; 277 break; 278 case 7: /* auth.backend.ldap.allow-empty-pw */ 279 case 8: /* auth.backend.ldap.groupmember */ 280 break; 281 default:/* should not happen */ 282 break; 283 } 284 } 285 286 if (ldc) { 287 ldc->auth_ldap_binddn = binddn; 288 ldc->auth_ldap_bindpw = bindpw; 289 ldc->auth_ldap_cafile = cafile; 290 ldc->auth_ldap_starttls = starttls; 291 } 292 } 293 294 static const struct { const char *ptr; uint32_t used; uint32_t size; } 295 memberUid = { "memberUid", sizeof("memberUid"), 0 }; 296 *(const buffer **)&p->defaults.auth_ldap_groupmember = 297 (const buffer *)&memberUid; 298 299 /* initialize p->defaults from global config context */ 300 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 301 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 302 if (-1 != cpv->k_id) 303 mod_authn_ldap_merge_config(&p->defaults, cpv); 304 } 305 306 return HANDLER_GO_ON; 307 } 308 309 __attribute_cold__ 310 static void mod_authn_ldap_err(log_error_st *errh, const char *file, unsigned long line, const char *fn, int err) 311 { 312 log_error(errh, file, line, "ldap: %s: %s", fn, ldap_err2string(err)); 313 } 314 315 __attribute_cold__ 316 static void mod_authn_ldap_opt_err(log_error_st *errh, const char *file, unsigned long line, const char *fn, LDAP *ld) 317 { 318 int err; 319 ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err); 320 mod_authn_ldap_err(errh, file, line, fn, err); 321 } 322 323 static void mod_authn_append_ldap_dn_escape(buffer * const filter, const buffer * const raw) { 324 /* [RFC4514] 2.4 Converting an AttributeValue from ASN.1 to a String 325 * 326 * https://www.ldap.com/ldap-dns-and-rdns 327 * http://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx 328 */ 329 const char * const b = raw->ptr; 330 const size_t rlen = buffer_string_length(raw); 331 if (0 == rlen) return; 332 333 if (b[0] == ' ') { /* || b[0] == '#' handled below for MS Active Directory*/ 334 /* escape leading ' ' */ 335 buffer_append_string_len(filter, CONST_STR_LEN("\\")); 336 } 337 338 for (size_t i = 0; i < rlen; ++i) { 339 size_t len = i; 340 int bs = 0; 341 do { 342 /* encode all UTF-8 chars with high bit set 343 * (instead of validating UTF-8 and escaping only invalid UTF-8) */ 344 if (((unsigned char *)b)[len] > 0x7f) 345 break; 346 switch (b[len]) { 347 default: 348 continue; 349 case '"': case '+': case ',': case ';': case '\\': 350 case '<': case '>': 351 case '=': case '#': /* (for MS Active Directory) */ 352 bs = 1; 353 break; 354 case '\0': 355 break; 356 } 357 break; 358 } while (++len < rlen); 359 len -= i; 360 361 if (len) { 362 buffer_append_string_len(filter, b+i, len); 363 if ((i += len) == rlen) break; 364 } 365 366 if (bs) { 367 buffer_append_string_len(filter, CONST_STR_LEN("\\")); 368 buffer_append_string_len(filter, b+i, 1); 369 } 370 else { 371 /* escape NUL ('\0') (and all UTF-8 chars with high bit set) */ 372 char *f; 373 buffer_string_prepare_append(filter, 3); 374 f = filter->ptr + buffer_string_length(filter); 375 f[0] = '\\'; 376 f[1] = "0123456789abcdef"[(((unsigned char *)b)[i] >> 4) & 0xf]; 377 f[2] = "0123456789abcdef"[(((unsigned char *)b)[i] ) & 0xf]; 378 buffer_commit(filter, 3); 379 } 380 } 381 382 if (rlen > 1 && b[rlen-1] == ' ') { 383 /* escape trailing ' ' */ 384 filter->ptr[buffer_string_length(filter)-1] = '\\'; 385 buffer_append_string_len(filter, CONST_STR_LEN(" ")); 386 } 387 } 388 389 static void mod_authn_append_ldap_filter_escape(buffer * const filter, const buffer * const raw) { 390 /* [RFC4515] 3. String Search Filter Definition 391 * 392 * [...] 393 * 394 * The <valueencoding> rule ensures that the entire filter string is a 395 * valid UTF-8 string and provides that the octets that represent the 396 * ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII 397 * 0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a 398 * backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits 399 * representing the value of the encoded octet. 400 * 401 * [...] 402 * 403 * As indicated by the <valueencoding> rule, implementations MUST escape 404 * all octets greater than 0x7F that are not part of a valid UTF-8 405 * encoding sequence when they generate a string representation of a 406 * search filter. Implementations SHOULD accept as input strings that 407 * are not valid UTF-8 strings. This is necessary because RFC 2254 did 408 * not clearly define the term "string representation" (and in 409 * particular did not mention that the string representation of an LDAP 410 * search filter is a string of UTF-8-encoded Unicode characters). 411 * 412 * 413 * https://www.ldap.com/ldap-filters 414 * Although not required, you may escape any other characters that you want 415 * in the assertion value (or substring component) of a filter. This may be 416 * accomplished by prefixing the hexadecimal representation of each byte of 417 * the UTF-8 encoding of the character to escape with a backslash character. 418 */ 419 const char * const b = raw->ptr; 420 const size_t rlen = buffer_string_length(raw); 421 for (size_t i = 0; i < rlen; ++i) { 422 size_t len = i; 423 char *f; 424 do { 425 /* encode all UTF-8 chars with high bit set 426 * (instead of validating UTF-8 and escaping only invalid UTF-8) */ 427 if (((unsigned char *)b)[len] > 0x7f) 428 break; 429 switch (b[len]) { 430 default: 431 continue; 432 case '\0': case '(': case ')': case '*': case '\\': 433 break; 434 } 435 break; 436 } while (++len < rlen); 437 len -= i; 438 439 if (len) { 440 buffer_append_string_len(filter, b+i, len); 441 if ((i += len) == rlen) break; 442 } 443 444 /* escape * ( ) \ NUL ('\0') (and all UTF-8 chars with high bit set) */ 445 buffer_string_prepare_append(filter, 3); 446 f = filter->ptr + buffer_string_length(filter); 447 f[0] = '\\'; 448 f[1] = "0123456789abcdef"[(((unsigned char *)b)[i] >> 4) & 0xf]; 449 f[2] = "0123456789abcdef"[(((unsigned char *)b)[i] ) & 0xf]; 450 buffer_commit(filter, 3); 451 } 452 } 453 454 static LDAP * mod_authn_ldap_host_init(log_error_st *errh, plugin_config_ldap *s) { 455 LDAP *ld; 456 int ret; 457 458 if (NULL == s->auth_ldap_hostname) return NULL; 459 460 if (LDAP_SUCCESS != ldap_initialize(&ld, s->auth_ldap_hostname)) { 461 log_perror(errh, __FILE__, __LINE__, "ldap: ldap_initialize()"); 462 return NULL; 463 } 464 465 ret = LDAP_VERSION3; 466 ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ret); 467 if (LDAP_OPT_SUCCESS != ret) { 468 mod_authn_ldap_err(errh, __FILE__, __LINE__, "ldap_set_option()", ret); 469 ldap_destroy(ld); 470 return NULL; 471 } 472 473 /* restart ldap functions if interrupted by a signal, e.g. SIGCHLD */ 474 ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON); 475 476 if (s->auth_ldap_starttls) { 477 /* if no CA file is given, it is ok, as we will use encryption 478 * if the server requires a CAfile it will tell us */ 479 if (s->auth_ldap_cafile) { 480 ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, 481 s->auth_ldap_cafile); 482 if (LDAP_OPT_SUCCESS != ret) { 483 mod_authn_ldap_err(errh, __FILE__, __LINE__, 484 "ldap_set_option(LDAP_OPT_X_TLS_CACERTFILE)", 485 ret); 486 ldap_destroy(ld); 487 return NULL; 488 } 489 } 490 491 ret = ldap_start_tls_s(ld, NULL, NULL); 492 if (LDAP_OPT_SUCCESS != ret) { 493 mod_authn_ldap_err(errh,__FILE__,__LINE__,"ldap_start_tls_s()",ret); 494 ldap_destroy(ld); 495 return NULL; 496 } 497 } 498 499 return ld; 500 } 501 502 static int mod_authn_ldap_bind(log_error_st *errh, LDAP *ld, const char *dn, const char *pw) { 503 struct berval creds; 504 int ret; 505 506 if (NULL != pw) { 507 *((const char **)&creds.bv_val) = pw; /*(cast away const)*/ 508 creds.bv_len = strlen(pw); 509 } else { 510 creds.bv_val = NULL; 511 creds.bv_len = 0; 512 } 513 514 /* RFE: add functionality: LDAP_SASL_EXTERNAL (or GSS-SPNEGO, etc.) */ 515 516 ret = ldap_sasl_bind_s(ld,dn,LDAP_SASL_SIMPLE,&creds,NULL,NULL,NULL); 517 if (ret != LDAP_SUCCESS) { 518 mod_authn_ldap_err(errh, __FILE__, __LINE__, "ldap_sasl_bind_s()", ret); 519 } 520 521 return ret; 522 } 523 524 static int mod_authn_ldap_rebind_proc (LDAP *ld, LDAP_CONST char *url, ber_tag_t ldap_request, ber_int_t msgid, void *params) { 525 const plugin_config_ldap *s = (const plugin_config_ldap *)params; 526 UNUSED(url); 527 UNUSED(ldap_request); 528 UNUSED(msgid); 529 return s->auth_ldap_binddn 530 ? mod_authn_ldap_bind(s->errh, ld, 531 s->auth_ldap_binddn, 532 s->auth_ldap_bindpw) 533 : mod_authn_ldap_bind(s->errh, ld, NULL, NULL); 534 } 535 536 static LDAPMessage * mod_authn_ldap_search(log_error_st *errh, plugin_config_ldap *s, const char *base, const char *filter) { 537 LDAPMessage *lm = NULL; 538 char *attrs[] = { LDAP_NO_ATTRS, NULL }; 539 int ret; 540 541 /* 542 * 1. connect anonymously (if not already connected) 543 * (ldap connection is kept open unless connection-level error occurs) 544 * 2. issue search using filter 545 */ 546 547 if (s->ldap != NULL) { 548 ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter, 549 attrs, 0, NULL, NULL, NULL, 0, &lm); 550 if (LDAP_SUCCESS == ret) { 551 return lm; 552 } else if (LDAP_SERVER_DOWN != ret) { 553 /* try again (or initial request); 554 * ldap lib sometimes fails for the first call but reconnects */ 555 ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter, 556 attrs, 0, NULL, NULL, NULL, 0, &lm); 557 if (LDAP_SUCCESS == ret) { 558 return lm; 559 } 560 } 561 562 ldap_unbind_ext_s(s->ldap, NULL, NULL); 563 } 564 565 s->ldap = mod_authn_ldap_host_init(errh, s); 566 if (NULL == s->ldap) { 567 return NULL; 568 } 569 570 ldap_set_rebind_proc(s->ldap, mod_authn_ldap_rebind_proc, s); 571 ret = mod_authn_ldap_rebind_proc(s->ldap, NULL, 0, 0, s); 572 if (LDAP_SUCCESS != ret) { 573 ldap_destroy(s->ldap); 574 s->ldap = NULL; 575 return NULL; 576 } 577 578 ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter, 579 attrs, 0, NULL, NULL, NULL, 0, &lm); 580 if (LDAP_SUCCESS != ret) { 581 log_error(errh, __FILE__, __LINE__, 582 "ldap: %s; filter: %s", ldap_err2string(ret), filter); 583 ldap_unbind_ext_s(s->ldap, NULL, NULL); 584 s->ldap = NULL; 585 return NULL; 586 } 587 588 return lm; 589 } 590 591 static char * mod_authn_ldap_get_dn(log_error_st *errh, plugin_config_ldap *s, const char *base, const char *filter) { 592 LDAP *ld; 593 LDAPMessage *lm, *first; 594 char *dn; 595 int count; 596 597 lm = mod_authn_ldap_search(errh, s, base, filter); 598 if (NULL == lm) { 599 return NULL; 600 } 601 602 ld = s->ldap; /*(must be after mod_authn_ldap_search(); might reconnect)*/ 603 604 count = ldap_count_entries(ld, lm); 605 if (0 == count) { /*(no entires found)*/ 606 ldap_msgfree(lm); 607 return NULL; 608 } else if (count > 1) { 609 log_error(errh, __FILE__, __LINE__, 610 "ldap: more than one record returned. " 611 "you might have to refine the filter: %s", filter); 612 } 613 614 if (NULL == (first = ldap_first_entry(ld, lm))) { 615 mod_authn_ldap_opt_err(errh,__FILE__,__LINE__,"ldap_first_entry()",ld); 616 ldap_msgfree(lm); 617 return NULL; 618 } 619 620 if (NULL == (dn = ldap_get_dn(ld, first))) { 621 mod_authn_ldap_opt_err(errh,__FILE__,__LINE__,"ldap_get_dn()",ld); 622 ldap_msgfree(lm); 623 return NULL; 624 } 625 626 ldap_msgfree(lm); 627 return dn; 628 } 629 630 static handler_t mod_authn_ldap_memberOf(log_error_st *errh, plugin_config *s, const http_auth_require_t *require, const buffer *username, const char *userdn) { 631 const array *groups = &require->group; 632 buffer *filter = buffer_init(); 633 handler_t rc = HANDLER_ERROR; 634 635 buffer_copy_string_len(filter, CONST_STR_LEN("(")); 636 buffer_append_string_buffer(filter, s->auth_ldap_groupmember); 637 buffer_append_string_len(filter, CONST_STR_LEN("=")); 638 if (buffer_is_equal_string(s->auth_ldap_groupmember, 639 CONST_STR_LEN("member"))) { 640 buffer_append_string(filter, userdn); 641 } else { /*(assume "memberUid"; consider validating in SETDEFAULTS_FUNC)*/ 642 mod_authn_append_ldap_filter_escape(filter, username); 643 } 644 buffer_append_string_len(filter, CONST_STR_LEN(")")); 645 646 plugin_config_ldap * const ldc = s->ldc; 647 for (size_t i = 0; i < groups->used; ++i) { 648 const char *base = groups->data[i]->key.ptr; 649 LDAPMessage *lm = mod_authn_ldap_search(errh, ldc, base, filter->ptr); 650 if (NULL != lm) { 651 int count = ldap_count_entries(ldc->ldap, lm); 652 ldap_msgfree(lm); 653 if (count > 0) { 654 rc = HANDLER_GO_ON; 655 break; 656 } 657 } 658 } 659 660 buffer_free(filter); 661 return rc; 662 } 663 664 static handler_t mod_authn_ldap_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) { 665 plugin_data *p = (plugin_data *)p_d; 666 LDAP *ld; 667 char *dn; 668 669 mod_authn_ldap_patch_config(r, p); 670 671 if (pw[0] == '\0' && !p->conf.auth_ldap_allow_empty_pw) 672 return HANDLER_ERROR; 673 674 const buffer * const template = p->conf.auth_ldap_filter; 675 if (NULL == template) 676 return HANDLER_ERROR; 677 678 log_error_st * const errh = r->conf.errh; 679 680 /* build filter to get DN for uid = username */ 681 buffer * const ldap_filter = &p->ldap_filter; 682 buffer_clear(ldap_filter); 683 if (*template->ptr == ',') { 684 /* special-case filter template beginning with ',' to be explicit DN */ 685 buffer_append_string_len(ldap_filter, CONST_STR_LEN("uid=")); 686 mod_authn_append_ldap_dn_escape(ldap_filter, username); 687 buffer_append_string_buffer(ldap_filter, template); 688 dn = ldap_filter->ptr; 689 } 690 else { 691 for (const char *b = template->ptr, *d; *b; b = d+1) { 692 if (NULL != (d = strchr(b, '?'))) { 693 buffer_append_string_len(ldap_filter, b, (size_t)(d - b)); 694 mod_authn_append_ldap_filter_escape(ldap_filter, username); 695 } 696 else { 697 d = template->ptr + buffer_string_length(template); 698 buffer_append_string_len(ldap_filter, b, (size_t)(d - b)); 699 break; 700 } 701 } 702 703 /* ldap_search for DN (synchronous; blocking) */ 704 dn = mod_authn_ldap_get_dn(errh, p->conf.ldc, 705 p->conf.auth_ldap_basedn, ldap_filter->ptr); 706 if (NULL == dn) return HANDLER_ERROR; 707 } 708 709 /*(Check ldc here rather than further up to preserve historical behavior 710 * where p->conf.ldc above (was p->anon_conf above) is set of directives in 711 * same context as auth_ldap_hostname. Preference: admin intentions are 712 * clearer if directives are always together in a set in same context)*/ 713 714 plugin_config_ldap * const ldc_base = p->conf.ldc; 715 plugin_config_ldap ldc_custom; 716 717 if ( p->conf.ldc->auth_ldap_starttls != p->conf.auth_ldap_starttls 718 || p->conf.ldc->auth_ldap_binddn != p->conf.auth_ldap_binddn 719 || p->conf.ldc->auth_ldap_bindpw != p->conf.auth_ldap_bindpw 720 || p->conf.ldc->auth_ldap_cafile != p->conf.auth_ldap_cafile ) { 721 ldc_custom.ldap = NULL; 722 ldc_custom.errh = errh; 723 ldc_custom.auth_ldap_hostname = ldc_base->auth_ldap_hostname; 724 ldc_custom.auth_ldap_starttls = p->conf.auth_ldap_starttls; 725 ldc_custom.auth_ldap_binddn = p->conf.auth_ldap_binddn; 726 ldc_custom.auth_ldap_bindpw = p->conf.auth_ldap_bindpw; 727 ldc_custom.auth_ldap_cafile = p->conf.auth_ldap_cafile; 728 p->conf.ldc = &ldc_custom; 729 } 730 731 handler_t rc = HANDLER_ERROR; 732 do { 733 /* auth against LDAP server (synchronous; blocking) */ 734 735 ld = mod_authn_ldap_host_init(errh, p->conf.ldc); 736 if (NULL == ld) 737 break; 738 739 /* Disable referral tracking; target user should be in provided scope */ 740 int ret = ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); 741 if (LDAP_OPT_SUCCESS != ret) { 742 mod_authn_ldap_err(errh,__FILE__,__LINE__,"ldap_set_option()",ret); 743 break; 744 } 745 746 if (LDAP_SUCCESS != mod_authn_ldap_bind(errh, ld, dn, pw)) 747 break; 748 749 ldap_unbind_ext_s(ld, NULL, NULL); /* disconnect */ 750 ld = NULL; 751 752 if (http_auth_match_rules(require, username->ptr, NULL, NULL)) { 753 rc = HANDLER_GO_ON; /* access granted */ 754 } 755 else if (require->group.used) { 756 /*(must not re-use ldap_filter, since it might be used for dn)*/ 757 rc = mod_authn_ldap_memberOf(errh,&p->conf,require,username,dn); 758 } 759 } while (0); 760 761 if (NULL != ld) ldap_destroy(ld); 762 if (ldc_base != p->conf.ldc && NULL != p->conf.ldc->ldap) 763 ldap_unbind_ext_s(p->conf.ldc->ldap, NULL, NULL); 764 if (dn != ldap_filter->ptr) ldap_memfree(dn); 765 return rc; 766 } 767 768 int mod_authn_ldap_plugin_init(plugin *p); 769 int mod_authn_ldap_plugin_init(plugin *p) { 770 p->version = LIGHTTPD_VERSION_ID; 771 p->name = "authn_ldap"; 772 p->init = mod_authn_ldap_init; 773 p->set_defaults = mod_authn_ldap_set_defaults; 774 p->cleanup = mod_authn_ldap_free; 775 776 return 0; 777 } 778