/* $NetBSD: ldapauth.c,v 1.7.14.1 2023/12/25 12:31:04 martin Exp $ */ /* * * Copyright (c) 2005, Eric AUGE * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * 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. * * */ #include "includes.h" __RCSID("$NetBSD: ldapauth.c,v 1.7.14.1 2023/12/25 12:31:04 martin Exp $"); #ifdef WITH_LDAP_PUBKEY #include #include #include #include #include #include "ldapauth.h" #include "log.h" /* filter building infos */ #define FILTER_GROUP_PREFIX "(&(objectclass=posixGroup)" #define FILTER_OR_PREFIX "(|" #define FILTER_OR_SUFFIX ")" #define FILTER_CN_PREFIX "(cn=" #define FILTER_CN_SUFFIX ")" #define FILTER_UID_FORMAT "(memberUid=%s)" #define FILTER_GROUP_SUFFIX ")" #define FILTER_GROUP_SIZE(group) (size_t) (strlen(group)+(ldap_count_group(group)*5)+52) /* just filter building stuff */ #define REQUEST_GROUP_SIZE(filter, uid) (size_t) (strlen(filter)+strlen(uid)+1) #define REQUEST_GROUP(buffer, prefilter, pwname) \ buffer = (char *) calloc(REQUEST_GROUP_SIZE(prefilter, pwname), sizeof(char)); \ if (!buffer) { \ perror("calloc()"); \ return FAILURE; \ } \ snprintf(buffer, REQUEST_GROUP_SIZE(prefilter,pwname), prefilter, pwname) /* XXX OLD group building macros #define REQUEST_GROUP_SIZE(grp, uid) (size_t) (strlen(grp)+strlen(uid)+46) #define REQUEST_GROUP(buffer,pwname,grp) \ buffer = (char *) calloc(REQUEST_GROUP_SIZE(grp, pwname), sizeof(char)); \ if (!buffer) { \ perror("calloc()"); \ return FAILURE; \ } \ snprintf(buffer,REQUEST_GROUP_SIZE(grp,pwname),"(&(objectclass=posixGroup)(cn=%s)(memberUid=%s))",grp,pwname) */ /* XXX stock upstream version without extra filter support #define REQUEST_USER_SIZE(uid) (size_t) (strlen(uid)+64) #define REQUEST_USER(buffer, pwname) \ buffer = (char *) calloc(REQUEST_USER_SIZE(pwname), sizeof(char)); \ if (!buffer) { \ perror("calloc()"); \ return NULL; \ } \ snprintf(buffer,REQUEST_USER_SIZE(pwname),"(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s))",pwname) */ #define REQUEST_USER_SIZE(uid, filter) (size_t) (strlen(uid)+64+(filter != NULL ? strlen(filter) : 0)) #define REQUEST_USER(buffer, pwname, customfilter) \ buffer = (char *) calloc(REQUEST_USER_SIZE(pwname, customfilter), sizeof(char)); \ if (!buffer) { \ perror("calloc()"); \ return NULL; \ } \ snprintf(buffer, REQUEST_USER_SIZE(pwname, customfilter), \ "(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s)%s)", \ pwname, (customfilter != NULL ? customfilter : "")) /* some portable and working tokenizer, lame though */ static int tokenize(char ** o, size_t size, char * input) { unsigned int i = 0, num; const char * charset = " \t"; char * ptr = input; /* leading white spaces are ignored */ num = strspn(ptr, charset); ptr += num; while ((num = strcspn(ptr, charset))) { if (i < size-1) { o[i++] = ptr; ptr += num; if (*ptr) *ptr++ = '\0'; } } o[i] = NULL; return SUCCESS; } void ldap_close(ldap_opt_t * ldap) { if (!ldap) return; if ( ldap_unbind_ext(ldap->ld, NULL, NULL) < 0) ldap_perror(ldap->ld, "ldap_unbind()"); ldap->ld = NULL; FLAG_SET_DISCONNECTED(ldap->flags); return; } /* init && bind */ int ldap_xconnect(ldap_opt_t * ldap) { int version = LDAP_VERSION3; if (!ldap->servers) return FAILURE; /* Connection Init and setup */ ldap->ld = ldap_init(ldap->servers, LDAP_PORT); if (!ldap->ld) { ldap_perror(ldap->ld, "ldap_init()"); return FAILURE; } if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)"); return FAILURE; } /* Timeouts setup */ if (ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &ldap->b_timeout) != LDAP_SUCCESS) { ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT)"); } if (ldap_set_option(ldap->ld, LDAP_OPT_TIMEOUT, &ldap->s_timeout) != LDAP_SUCCESS) { ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_TIMEOUT)"); } /* TLS support */ if ( (ldap->tls == -1) || (ldap->tls == 1) ) { if (ldap_start_tls_s(ldap->ld, NULL, NULL ) != LDAP_SUCCESS) { /* failed then reinit the initial connect */ ldap_perror(ldap->ld, "ldap_xconnect: (TLS) ldap_start_tls()"); if (ldap->tls == 1) return FAILURE; ldap->ld = ldap_init(ldap->servers, LDAP_PORT); if (!ldap->ld) { ldap_perror(ldap->ld, "ldap_init()"); return FAILURE; } if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { ldap_perror(ldap->ld, "ldap_set_option()"); return FAILURE; } } } if ( ldap_simple_bind_s(ldap->ld, ldap->binddn, ldap->bindpw) != LDAP_SUCCESS) { ldap_perror(ldap->ld, "ldap_simple_bind_s()"); return FAILURE; } /* says it is connected */ FLAG_SET_CONNECTED(ldap->flags); return SUCCESS; } /* must free allocated ressource */ static char * ldap_build_host(char *host, int port) { unsigned int size = strlen(host)+11; char * h = (char *) calloc (size, sizeof(char)); int rc; if (!h) return NULL; rc = snprintf(h, size, "%s:%d ", host, port); if (rc == -1) return NULL; return h; } static int ldap_count_group(const char * input) { const char * charset = " \t"; const char * ptr = input; unsigned int count = 0; unsigned int num; num = strspn(ptr, charset); ptr += num; while ((num = strcspn(ptr, charset))) { count++; ptr += num; ptr++; } return count; } /* format filter */ char * ldap_parse_groups(const char * groups) { unsigned int buffer_size = FILTER_GROUP_SIZE(groups); char * buffer = (char *) calloc(buffer_size, sizeof(char)); char * g = NULL; char * garray[32]; unsigned int i = 0; if ((!groups)||(!buffer)) return NULL; g = strdup(groups); if (!g) { free(buffer); return NULL; } /* first separate into n tokens */ if ( tokenize(garray, sizeof(garray)/sizeof(*garray), g) < 0) { free(g); free(buffer); return NULL; } /* build the final filter format */ strlcat(buffer, FILTER_GROUP_PREFIX, buffer_size); strlcat(buffer, FILTER_OR_PREFIX, buffer_size); i = 0; while (garray[i]) { strlcat(buffer, FILTER_CN_PREFIX, buffer_size); strlcat(buffer, garray[i], buffer_size); strlcat(buffer, FILTER_CN_SUFFIX, buffer_size); i++; } strlcat(buffer, FILTER_OR_SUFFIX, buffer_size); strlcat(buffer, FILTER_UID_FORMAT, buffer_size); strlcat(buffer, FILTER_GROUP_SUFFIX, buffer_size); free(g); return buffer; } /* a bit dirty but leak free */ char * ldap_parse_servers(const char * servers) { char * s = NULL; char * tmp = NULL, *urls[32]; unsigned int num = 0 , i = 0 , asize = 0; LDAPURLDesc *urld[32]; if (!servers) return NULL; /* local copy of the arg */ s = strdup(servers); if (!s) return NULL; /* first separate into URL tokens */ if ( tokenize(urls, sizeof(urls)/sizeof(*urls), s) < 0) return NULL; i = 0; while (urls[i]) { if (! ldap_is_ldap_url(urls[i]) || (ldap_url_parse(urls[i], &urld[i]) != 0)) { return NULL; } i++; } /* now free(s) */ free (s); /* how much memory do we need */ num = i; for (i = 0 ; i < num ; i++) asize += strlen(urld[i]->lud_host)+11; /* alloc */ s = (char *) calloc( asize+1 , sizeof(char)); if (!s) { for (i = 0 ; i < num ; i++) ldap_free_urldesc(urld[i]); return NULL; } /* then build the final host string */ for (i = 0 ; i < num ; i++) { /* built host part */ tmp = ldap_build_host(urld[i]->lud_host, urld[i]->lud_port); strncat(s, tmp, strlen(tmp)); ldap_free_urldesc(urld[i]); free(tmp); } return s; } void ldap_options_print(ldap_opt_t * ldap) { debug("ldap options:"); debug("servers: %s", ldap->servers); if (ldap->u_basedn) debug("user basedn: %s", ldap->u_basedn); if (ldap->g_basedn) debug("group basedn: %s", ldap->g_basedn); if (ldap->binddn) debug("binddn: %s", ldap->binddn); if (ldap->bindpw) debug("bindpw: %s", ldap->bindpw); if (ldap->sgroup) debug("group: %s", ldap->sgroup); if (ldap->filter) debug("filter: %s", ldap->filter); } void ldap_options_free(ldap_opt_t * l) { if (!l) return; if (l->servers) free(l->servers); if (l->u_basedn) free(l->u_basedn); if (l->g_basedn) free(l->g_basedn); if (l->binddn) free(l->binddn); if (l->bindpw) free(l->bindpw); if (l->sgroup) free(l->sgroup); if (l->fgroup) free(l->fgroup); if (l->filter) free(l->filter); if (l->l_conf) free(l->l_conf); free(l); } /* free keys */ void ldap_keys_free(ldap_key_t * k) { ldap_value_free_len(k->keys); free(k); return; } ldap_key_t * ldap_getuserkey(ldap_opt_t *l, const char * user) { ldap_key_t * k = (ldap_key_t *) calloc (1, sizeof(ldap_key_t)); LDAPMessage *res, *e; char * filter; int i; char *attrs[] = { l->pub_key_attr, NULL }; if ((!k) || (!l)) return NULL; /* Am i still connected ? RETRY n times */ /* XXX TODO: setup some conf value for retrying */ if (!(l->flags & FLAG_CONNECTED)) for (i = 0 ; i < 2 ; i++) if (ldap_xconnect(l) == 0) break; /* quick check for attempts to be evil */ if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) return NULL; /* build filter for LDAP request */ REQUEST_USER(filter, user, l->filter); if ( ldap_search_st( l->ld, l->u_basedn, LDAP_SCOPE_SUBTREE, filter, attrs, 0, &l->s_timeout, &res ) != LDAP_SUCCESS) { ldap_perror(l->ld, "ldap_search_st()"); free(filter); free(k); /* XXX error on search, timeout etc.. close ask for reconnect */ ldap_close(l); return NULL; } /* free */ free(filter); /* check if any results */ i = ldap_count_entries(l->ld,res); if (i <= 0) { ldap_msgfree(res); free(k); return NULL; } if (i > 1) debug("[LDAP] duplicate entries, using the FIRST entry returned"); e = ldap_first_entry(l->ld, res); k->keys = ldap_get_values_len(l->ld, e, l->pub_key_attr); k->num = ldap_count_values_len(k->keys); ldap_msgfree(res); return k; } /* -1 if trouble 0 if user is NOT member of current server group 1 if user IS MEMBER of current server group */ int ldap_ismember(ldap_opt_t * l, const char * user) { LDAPMessage *res; char * filter; int i; if ((!l->sgroup) || !(l->g_basedn)) return 1; /* Am i still connected ? RETRY n times */ /* XXX TODO: setup some conf value for retrying */ if (!(l->flags & FLAG_CONNECTED)) for (i = 0 ; i < 2 ; i++) if (ldap_xconnect(l) == 0) break; /* quick check for attempts to be evil */ if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) return FAILURE; /* build filter for LDAP request */ REQUEST_GROUP(filter, l->fgroup, user); if (ldap_search_st( l->ld, l->g_basedn, LDAP_SCOPE_SUBTREE, filter, NULL, 0, &l->s_timeout, &res) != LDAP_SUCCESS) { ldap_perror(l->ld, "ldap_search_st()"); free(filter); /* XXX error on search, timeout etc.. close ask for reconnect */ ldap_close(l); return FAILURE; } free(filter); /* check if any results */ if (ldap_count_entries(l->ld, res) > 0) { ldap_msgfree(res); return 1; } ldap_msgfree(res); return 0; } /* * ldap.conf simple parser * XXX TODO: sanity checks * must either * - free the previous ldap_opt_before replacing entries * - free each necessary previously parsed elements * ret: * -1 on FAILURE, 0 on SUCCESS */ int ldap_parse_lconf(ldap_opt_t * l) { FILE * lcd; /* ldap.conf descriptor */ char buf[BUFSIZ]; char * s = NULL, * k = NULL, * v = NULL; int li, len; lcd = fopen (l->l_conf, "r"); if (lcd == NULL) { /* debug("Cannot open %s", l->l_conf); */ perror("ldap_parse_lconf()"); return FAILURE; } while (fgets (buf, sizeof (buf), lcd) != NULL) { if (*buf == '\n' || *buf == '#') continue; k = buf; v = k; while (*v != '\0' && *v != ' ' && *v != '\t') v++; if (*v == '\0') continue; *(v++) = '\0'; while (*v == ' ' || *v == '\t') v++; li = strlen (v) - 1; while (v[li] == ' ' || v[li] == '\t' || v[li] == '\n') --li; v[li + 1] = '\0'; if (!strcasecmp (k, "uri")) { if ((l->servers = ldap_parse_servers(v)) == NULL) { fatal("error in ldap servers"); return FAILURE; } } else if (!strcasecmp (k, "base")) { s = strchr (v, '?'); if (s != NULL) { len = s - v; l->u_basedn = malloc (len + 1); strncpy (l->u_basedn, v, len); l->u_basedn[len] = '\0'; } else { l->u_basedn = strdup (v); } } else if (!strcasecmp (k, "binddn")) { l->binddn = strdup (v); } else if (!strcasecmp (k, "bindpw")) { l->bindpw = strdup (v); } else if (!strcasecmp (k, "timelimit")) { l->s_timeout.tv_sec = atoi (v); } else if (!strcasecmp (k, "bind_timelimit")) { l->b_timeout.tv_sec = atoi (v); } else if (!strcasecmp (k, "ssl")) { if (!strcasecmp (v, "start_tls")) l->tls = 1; } } fclose (lcd); return SUCCESS; } #endif /* WITH_LDAP_PUBKEY */