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