1 /* $NetBSD: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $ */ 2 /* $Id: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $ 3 */ 4 5 /* 6 * 7 * Copyright (c) 2005, Eric AUGE <eau@phear.org> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 11 * 12 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 13 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 14 * Neither the name of the phear.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 17 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 19 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 * 23 * 24 */ 25 #include "includes.h" 26 __RCSID("$NetBSD: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $"); 27 28 #ifdef WITH_LDAP_PUBKEY 29 #include <stdarg.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <unistd.h> 33 #include <string.h> 34 35 #include "ldapauth.h" 36 #include "log.h" 37 38 /* filter building infos */ 39 #define FILTER_GROUP_PREFIX "(&(objectclass=posixGroup)" 40 #define FILTER_OR_PREFIX "(|" 41 #define FILTER_OR_SUFFIX ")" 42 #define FILTER_CN_PREFIX "(cn=" 43 #define FILTER_CN_SUFFIX ")" 44 #define FILTER_UID_FORMAT "(memberUid=%s)" 45 #define FILTER_GROUP_SUFFIX ")" 46 #define FILTER_GROUP_SIZE(group) (size_t) (strlen(group)+(ldap_count_group(group)*5)+52) 47 48 /* just filter building stuff */ 49 #define REQUEST_GROUP_SIZE(filter, uid) (size_t) (strlen(filter)+strlen(uid)+1) 50 #define REQUEST_GROUP(buffer, prefilter, pwname) \ 51 buffer = (char *) calloc(REQUEST_GROUP_SIZE(prefilter, pwname), sizeof(char)); \ 52 if (!buffer) { \ 53 perror("calloc()"); \ 54 return FAILURE; \ 55 } \ 56 snprintf(buffer, REQUEST_GROUP_SIZE(prefilter,pwname), prefilter, pwname) 57 /* 58 XXX OLD group building macros 59 #define REQUEST_GROUP_SIZE(grp, uid) (size_t) (strlen(grp)+strlen(uid)+46) 60 #define REQUEST_GROUP(buffer,pwname,grp) \ 61 buffer = (char *) calloc(REQUEST_GROUP_SIZE(grp, pwname), sizeof(char)); \ 62 if (!buffer) { \ 63 perror("calloc()"); \ 64 return FAILURE; \ 65 } \ 66 snprintf(buffer,REQUEST_GROUP_SIZE(grp,pwname),"(&(objectclass=posixGroup)(cn=%s)(memberUid=%s))",grp,pwname) 67 */ 68 69 /* 70 XXX stock upstream version without extra filter support 71 #define REQUEST_USER_SIZE(uid) (size_t) (strlen(uid)+64) 72 #define REQUEST_USER(buffer, pwname) \ 73 buffer = (char *) calloc(REQUEST_USER_SIZE(pwname), sizeof(char)); \ 74 if (!buffer) { \ 75 perror("calloc()"); \ 76 return NULL; \ 77 } \ 78 snprintf(buffer,REQUEST_USER_SIZE(pwname),"(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s))",pwname) 79 */ 80 81 #define REQUEST_USER_SIZE(uid, filter) (size_t) (strlen(uid)+64+(filter != NULL ? strlen(filter) : 0)) 82 #define REQUEST_USER(buffer, pwname, customfilter) \ 83 buffer = (char *) calloc(REQUEST_USER_SIZE(pwname, customfilter), sizeof(char)); \ 84 if (!buffer) { \ 85 perror("calloc()"); \ 86 return NULL; \ 87 } \ 88 snprintf(buffer, REQUEST_USER_SIZE(pwname, customfilter), \ 89 "(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s)%s)", \ 90 pwname, (customfilter != NULL ? customfilter : "")) 91 92 /* some portable and working tokenizer, lame though */ 93 static int tokenize(char ** o, size_t size, char * input) { 94 unsigned int i = 0, num; 95 const char * charset = " \t"; 96 char * ptr = input; 97 98 /* leading white spaces are ignored */ 99 num = strspn(ptr, charset); 100 ptr += num; 101 102 while ((num = strcspn(ptr, charset))) { 103 if (i < size-1) { 104 o[i++] = ptr; 105 ptr += num; 106 if (*ptr) 107 *ptr++ = '\0'; 108 } 109 } 110 o[i] = NULL; 111 return SUCCESS; 112 } 113 114 void ldap_close(ldap_opt_t * ldap) { 115 116 if (!ldap) 117 return; 118 119 if ( ldap_unbind_ext(ldap->ld, NULL, NULL) < 0) 120 ldap_perror(ldap->ld, "ldap_unbind()"); 121 122 ldap->ld = NULL; 123 FLAG_SET_DISCONNECTED(ldap->flags); 124 125 return; 126 } 127 128 /* init && bind */ 129 int ldap_connect(ldap_opt_t * ldap) { 130 int version = LDAP_VERSION3; 131 132 if (!ldap->servers) 133 return FAILURE; 134 135 /* Connection Init and setup */ 136 ldap->ld = ldap_init(ldap->servers, LDAP_PORT); 137 if (!ldap->ld) { 138 ldap_perror(ldap->ld, "ldap_init()"); 139 return FAILURE; 140 } 141 142 if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { 143 ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)"); 144 return FAILURE; 145 } 146 147 /* Timeouts setup */ 148 if (ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &ldap->b_timeout) != LDAP_SUCCESS) { 149 ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT)"); 150 } 151 if (ldap_set_option(ldap->ld, LDAP_OPT_TIMEOUT, &ldap->s_timeout) != LDAP_SUCCESS) { 152 ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_TIMEOUT)"); 153 } 154 155 /* TLS support */ 156 if ( (ldap->tls == -1) || (ldap->tls == 1) ) { 157 if (ldap_start_tls_s(ldap->ld, NULL, NULL ) != LDAP_SUCCESS) { 158 /* failed then reinit the initial connect */ 159 ldap_perror(ldap->ld, "ldap_connect: (TLS) ldap_start_tls()"); 160 if (ldap->tls == 1) 161 return FAILURE; 162 163 ldap->ld = ldap_init(ldap->servers, LDAP_PORT); 164 if (!ldap->ld) { 165 ldap_perror(ldap->ld, "ldap_init()"); 166 return FAILURE; 167 } 168 169 if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { 170 ldap_perror(ldap->ld, "ldap_set_option()"); 171 return FAILURE; 172 } 173 } 174 } 175 176 177 if ( ldap_simple_bind_s(ldap->ld, ldap->binddn, ldap->bindpw) != LDAP_SUCCESS) { 178 ldap_perror(ldap->ld, "ldap_simple_bind_s()"); 179 return FAILURE; 180 } 181 182 /* says it is connected */ 183 FLAG_SET_CONNECTED(ldap->flags); 184 185 return SUCCESS; 186 } 187 188 /* must free allocated ressource */ 189 static char * ldap_build_host(char *host, int port) { 190 unsigned int size = strlen(host)+11; 191 char * h = (char *) calloc (size, sizeof(char)); 192 int rc; 193 if (!h) 194 return NULL; 195 196 rc = snprintf(h, size, "%s:%d ", host, port); 197 if (rc == -1) 198 return NULL; 199 return h; 200 } 201 202 static int ldap_count_group(const char * input) { 203 const char * charset = " \t"; 204 const char * ptr = input; 205 unsigned int count = 0; 206 unsigned int num; 207 208 num = strspn(ptr, charset); 209 ptr += num; 210 211 while ((num = strcspn(ptr, charset))) { 212 count++; 213 ptr += num; 214 ptr++; 215 } 216 217 return count; 218 } 219 220 /* format filter */ 221 char * ldap_parse_groups(const char * groups) { 222 unsigned int buffer_size = FILTER_GROUP_SIZE(groups); 223 char * buffer = (char *) calloc(buffer_size, sizeof(char)); 224 char * g = NULL; 225 char * garray[32]; 226 unsigned int i = 0; 227 228 if ((!groups)||(!buffer)) 229 return NULL; 230 231 g = strdup(groups); 232 if (!g) { 233 free(buffer); 234 return NULL; 235 } 236 237 /* first separate into n tokens */ 238 if ( tokenize(garray, sizeof(garray)/sizeof(*garray), g) < 0) { 239 free(g); 240 free(buffer); 241 return NULL; 242 } 243 244 /* build the final filter format */ 245 strlcat(buffer, FILTER_GROUP_PREFIX, buffer_size); 246 strlcat(buffer, FILTER_OR_PREFIX, buffer_size); 247 i = 0; 248 while (garray[i]) { 249 strlcat(buffer, FILTER_CN_PREFIX, buffer_size); 250 strlcat(buffer, garray[i], buffer_size); 251 strlcat(buffer, FILTER_CN_SUFFIX, buffer_size); 252 i++; 253 } 254 strlcat(buffer, FILTER_OR_SUFFIX, buffer_size); 255 strlcat(buffer, FILTER_UID_FORMAT, buffer_size); 256 strlcat(buffer, FILTER_GROUP_SUFFIX, buffer_size); 257 258 free(g); 259 return buffer; 260 } 261 262 /* a bit dirty but leak free */ 263 char * ldap_parse_servers(const char * servers) { 264 char * s = NULL; 265 char * tmp = NULL, *urls[32]; 266 unsigned int num = 0 , i = 0 , asize = 0; 267 LDAPURLDesc *urld[32]; 268 269 if (!servers) 270 return NULL; 271 272 /* local copy of the arg */ 273 s = strdup(servers); 274 if (!s) 275 return NULL; 276 277 /* first separate into URL tokens */ 278 if ( tokenize(urls, sizeof(urls)/sizeof(*urls), s) < 0) 279 return NULL; 280 281 i = 0; 282 while (urls[i]) { 283 if (! ldap_is_ldap_url(urls[i]) || 284 (ldap_url_parse(urls[i], &urld[i]) != 0)) { 285 return NULL; 286 } 287 i++; 288 } 289 290 /* now free(s) */ 291 free (s); 292 293 /* how much memory do we need */ 294 num = i; 295 for (i = 0 ; i < num ; i++) 296 asize += strlen(urld[i]->lud_host)+11; 297 298 /* alloc */ 299 s = (char *) calloc( asize+1 , sizeof(char)); 300 if (!s) { 301 for (i = 0 ; i < num ; i++) 302 ldap_free_urldesc(urld[i]); 303 return NULL; 304 } 305 306 /* then build the final host string */ 307 for (i = 0 ; i < num ; i++) { 308 /* built host part */ 309 tmp = ldap_build_host(urld[i]->lud_host, urld[i]->lud_port); 310 strncat(s, tmp, strlen(tmp)); 311 ldap_free_urldesc(urld[i]); 312 free(tmp); 313 } 314 315 return s; 316 } 317 318 void ldap_options_print(ldap_opt_t * ldap) { 319 debug("ldap options:"); 320 debug("servers: %s", ldap->servers); 321 if (ldap->u_basedn) 322 debug("user basedn: %s", ldap->u_basedn); 323 if (ldap->g_basedn) 324 debug("group basedn: %s", ldap->g_basedn); 325 if (ldap->binddn) 326 debug("binddn: %s", ldap->binddn); 327 if (ldap->bindpw) 328 debug("bindpw: %s", ldap->bindpw); 329 if (ldap->sgroup) 330 debug("group: %s", ldap->sgroup); 331 if (ldap->filter) 332 debug("filter: %s", ldap->filter); 333 } 334 335 void ldap_options_free(ldap_opt_t * l) { 336 if (!l) 337 return; 338 if (l->servers) 339 free(l->servers); 340 if (l->u_basedn) 341 free(l->u_basedn); 342 if (l->g_basedn) 343 free(l->g_basedn); 344 if (l->binddn) 345 free(l->binddn); 346 if (l->bindpw) 347 free(l->bindpw); 348 if (l->sgroup) 349 free(l->sgroup); 350 if (l->fgroup) 351 free(l->fgroup); 352 if (l->filter) 353 free(l->filter); 354 if (l->l_conf) 355 free(l->l_conf); 356 free(l); 357 } 358 359 /* free keys */ 360 void ldap_keys_free(ldap_key_t * k) { 361 ldap_value_free_len(k->keys); 362 free(k); 363 return; 364 } 365 366 ldap_key_t * ldap_getuserkey(ldap_opt_t *l, const char * user) { 367 ldap_key_t * k = (ldap_key_t *) calloc (1, sizeof(ldap_key_t)); 368 LDAPMessage *res, *e; 369 char * filter; 370 int i; 371 char *attrs[] = { 372 l->pub_key_attr, 373 NULL 374 }; 375 376 if ((!k) || (!l)) 377 return NULL; 378 379 /* Am i still connected ? RETRY n times */ 380 /* XXX TODO: setup some conf value for retrying */ 381 if (!(l->flags & FLAG_CONNECTED)) 382 for (i = 0 ; i < 2 ; i++) 383 if (ldap_connect(l) == 0) 384 break; 385 386 /* quick check for attempts to be evil */ 387 if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || 388 (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) 389 return NULL; 390 391 /* build filter for LDAP request */ 392 REQUEST_USER(filter, user, l->filter); 393 394 if ( ldap_search_st( l->ld, 395 l->u_basedn, 396 LDAP_SCOPE_SUBTREE, 397 filter, 398 attrs, 0, &l->s_timeout, &res ) != LDAP_SUCCESS) { 399 400 ldap_perror(l->ld, "ldap_search_st()"); 401 402 free(filter); 403 free(k); 404 405 /* XXX error on search, timeout etc.. close ask for reconnect */ 406 ldap_close(l); 407 408 return NULL; 409 } 410 411 /* free */ 412 free(filter); 413 414 /* check if any results */ 415 i = ldap_count_entries(l->ld,res); 416 if (i <= 0) { 417 ldap_msgfree(res); 418 free(k); 419 return NULL; 420 } 421 422 if (i > 1) 423 debug("[LDAP] duplicate entries, using the FIRST entry returned"); 424 425 e = ldap_first_entry(l->ld, res); 426 k->keys = ldap_get_values_len(l->ld, e, l->pub_key_attr); 427 k->num = ldap_count_values_len(k->keys); 428 429 ldap_msgfree(res); 430 return k; 431 } 432 433 434 /* -1 if trouble 435 0 if user is NOT member of current server group 436 1 if user IS MEMBER of current server group 437 */ 438 int ldap_ismember(ldap_opt_t * l, const char * user) { 439 LDAPMessage *res; 440 char * filter; 441 int i; 442 443 if ((!l->sgroup) || !(l->g_basedn)) 444 return 1; 445 446 /* Am i still connected ? RETRY n times */ 447 /* XXX TODO: setup some conf value for retrying */ 448 if (!(l->flags & FLAG_CONNECTED)) 449 for (i = 0 ; i < 2 ; i++) 450 if (ldap_connect(l) == 0) 451 break; 452 453 /* quick check for attempts to be evil */ 454 if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || 455 (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) 456 return FAILURE; 457 458 /* build filter for LDAP request */ 459 REQUEST_GROUP(filter, l->fgroup, user); 460 461 if (ldap_search_st( l->ld, 462 l->g_basedn, 463 LDAP_SCOPE_SUBTREE, 464 filter, 465 NULL, 0, &l->s_timeout, &res) != LDAP_SUCCESS) { 466 467 ldap_perror(l->ld, "ldap_search_st()"); 468 469 free(filter); 470 471 /* XXX error on search, timeout etc.. close ask for reconnect */ 472 ldap_close(l); 473 474 return FAILURE; 475 } 476 477 free(filter); 478 479 /* check if any results */ 480 if (ldap_count_entries(l->ld, res) > 0) { 481 ldap_msgfree(res); 482 return 1; 483 } 484 485 ldap_msgfree(res); 486 return 0; 487 } 488 489 /* 490 * ldap.conf simple parser 491 * XXX TODO: sanity checks 492 * must either 493 * - free the previous ldap_opt_before replacing entries 494 * - free each necessary previously parsed elements 495 * ret: 496 * -1 on FAILURE, 0 on SUCCESS 497 */ 498 int ldap_parse_lconf(ldap_opt_t * l) { 499 FILE * lcd; /* ldap.conf descriptor */ 500 char buf[BUFSIZ]; 501 char * s = NULL, * k = NULL, * v = NULL; 502 int li, len; 503 504 lcd = fopen (l->l_conf, "r"); 505 if (lcd == NULL) { 506 /* debug("Cannot open %s", l->l_conf); */ 507 perror("ldap_parse_lconf()"); 508 return FAILURE; 509 } 510 511 while (fgets (buf, sizeof (buf), lcd) != NULL) { 512 513 if (*buf == '\n' || *buf == '#') 514 continue; 515 516 k = buf; 517 v = k; 518 while (*v != '\0' && *v != ' ' && *v != '\t') 519 v++; 520 521 if (*v == '\0') 522 continue; 523 524 *(v++) = '\0'; 525 526 while (*v == ' ' || *v == '\t') 527 v++; 528 529 li = strlen (v) - 1; 530 while (v[li] == ' ' || v[li] == '\t' || v[li] == '\n') 531 --li; 532 v[li + 1] = '\0'; 533 534 if (!strcasecmp (k, "uri")) { 535 if ((l->servers = ldap_parse_servers(v)) == NULL) { 536 fatal("error in ldap servers"); 537 return FAILURE; 538 } 539 540 } 541 else if (!strcasecmp (k, "base")) { 542 s = strchr (v, '?'); 543 if (s != NULL) { 544 len = s - v; 545 l->u_basedn = malloc (len + 1); 546 strncpy (l->u_basedn, v, len); 547 l->u_basedn[len] = '\0'; 548 } else { 549 l->u_basedn = strdup (v); 550 } 551 } 552 else if (!strcasecmp (k, "binddn")) { 553 l->binddn = strdup (v); 554 } 555 else if (!strcasecmp (k, "bindpw")) { 556 l->bindpw = strdup (v); 557 } 558 else if (!strcasecmp (k, "timelimit")) { 559 l->s_timeout.tv_sec = atoi (v); 560 } 561 else if (!strcasecmp (k, "bind_timelimit")) { 562 l->b_timeout.tv_sec = atoi (v); 563 } 564 else if (!strcasecmp (k, "ssl")) { 565 if (!strcasecmp (v, "start_tls")) 566 l->tls = 1; 567 } 568 } 569 570 fclose (lcd); 571 return SUCCESS; 572 } 573 574 #endif /* WITH_LDAP_PUBKEY */ 575