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