/* * Copyright (C) 1996-2015 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* * ----------------------------------------------------------------------------- * * Author: Markus Moeller (markus_moeller at compuserve.com) * * Copyright (C) 2007 Markus Moeller. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * ----------------------------------------------------------------------------- */ /* get_attributes is partly from OpenLDAP Software . * * Copyright 1998-2009 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #include "squid.h" #include "util.h" #ifdef HAVE_LDAP #include "support.h" #include char *convert_domain_to_bind_path(char *domain); char *escape_filter(char *filter); int check_AD(struct main_args *margs, LDAP * ld); int ldap_set_defaults(LDAP * ld); int ldap_set_ssl_defaults(struct main_args *margs); LDAP *tool_ldap_open(struct main_args *margs, char *host, int port, char *ssl); #define CONNECT_TIMEOUT 2 #define SEARCH_TIMEOUT 30 #define FILTER "(memberuid=%s)" #define ATTRIBUTE "cn" #define ATTRIBUTE_DN "distinguishedName" #define FILTER_UID "(uid=%s)" #define FILTER_GID "(&(gidNumber=%s)(objectclass=posixgroup))" #define ATTRIBUTE_GID "gidNumber" #define ATTRIBUTE_GID_AD "primaryGroupID" #define ATTRIBUTE_SID "objectSID" #define FILTER_AD "(samaccountname=%s)" #define ATTRIBUTE_AD "memberof" size_t get_attributes(LDAP * ld, LDAPMessage * res, const char *attribute /* IN */ , char ***out_val /* OUT (caller frees) */ ); size_t get_bin_attributes(LDAP * ld, LDAPMessage * res, const char *attribute /* IN */ , char ***out_val , int **out_len /* OUT (caller frees) */ ); int search_group_tree(struct main_args *margs, LDAP * ld, char *bindp, char *ldap_group, char *group, int depth); #if HAVE_SUN_LDAP_SDK || HAVE_MOZILLA_LDAP_SDK #if HAVE_LDAP_REBINDPROC_CALLBACK #if HAVE_SASL_H || HAVE_SASL_SASL_H || HAVE_SASL_DARWIN static LDAP_REBINDPROC_CALLBACK ldap_sasl_rebind; static int LDAP_CALL LDAP_CALLBACK ldap_sasl_rebind( LDAP * ld, char **whop, char **credp, int *methodp, int freeit, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; whop = whop; credp = credp; methodp = methodp; freeit = freeit; return tool_sasl_bind(ld, cp->dn, cp->pw); } #endif static LDAP_REBINDPROC_CALLBACK ldap_simple_rebind; static int LDAP_CALL LDAP_CALLBACK ldap_simple_rebind( LDAP * ld, char **whop, char **credp, int *methodp, int freeit, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; struct berval cred; if (cp->pw) { cred.bv_val=cp->pw; cred.bv_len=strlen(cp->pw); } whop = whop; credp = credp; methodp = methodp; freeit = freeit; return ldap_sasl_bind_s(ld, cp->dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); } #elif HAVE_LDAP_REBIND_PROC #if HAVE_SASL_H || HAVE_SASL_SASL_H || HAVE_SASL_DARWIN static LDAP_REBIND_PROC ldap_sasl_rebind; static int ldap_sasl_rebind( LDAP * ld, LDAP_CONST char *url, ber_tag_t request, ber_int_t msgid, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; return tool_sasl_bind(ld, cp->dn, cp->pw); } #endif static LDAP_REBIND_PROC ldap_simple_rebind; static int ldap_simple_rebind( LDAP * ld, LDAP_CONST char *url, ber_tag_t request, ber_int_t msgid, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; struct berval cred; if (cp->pw) { cred.bv_val=cp->pw; cred.bv_len=strlen(cp->pw); } return ldap_sasl_bind_s(ld, cp->dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); } #elif HAVE_LDAP_REBIND_FUNCTION #ifndef LDAP_REFERRALS #define LDAP_REFERRALS #endif #if HAVE_SASL_H || HAVE_SASL_SASL_H || HAVE_SASL_DARWIN static LDAP_REBIND_FUNCTION ldap_sasl_rebind; static int ldap_sasl_rebind( LDAP * ld, char **whop, char **credp, int *methodp, int freeit, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; whop = whop; credp = credp; methodp = methodp; freeit = freeit; return tool_sasl_bind(ld, cp->dn, cp->pw); } #endif static LDAP_REBIND_FUNCTION ldap_simple_rebind; static int ldap_simple_rebind( LDAP * ld, char **whop, char **credp, int *methodp, int freeit, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; struct berval cred; if (cp->pw) { cred.bv_val=cp->pw; cred.bv_len=strlen(cp->pw); } whop = whop; credp = credp; methodp = methodp; freeit = freeit; return ldap_sasl_bind_s(ld, cp->dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); } #else #error "No rebind functione defined" #endif #else /* HAVE_SUN_LDAP_SDK */ #if HAVE_SASL_H || HAVE_SASL_SASL_H || HAVE_SASL_DARWIN static LDAP_REBIND_PROC ldap_sasl_rebind; static int ldap_sasl_rebind(LDAP *ld, LDAP_CONST char *, ber_tag_t request, ber_int_t msgid, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; return tool_sasl_bind(ld, cp->dn, cp->pw); } #endif static LDAP_REBIND_PROC ldap_simple_rebind; static int ldap_simple_rebind(LDAP *ld, LDAP_CONST char *, ber_tag_t request, ber_int_t msgid, void *params) { struct ldap_creds *cp = (struct ldap_creds *) params; struct berval cred; if (cp->pw) { cred.bv_val=cp->pw; cred.bv_len=strlen(cp->pw); } return ldap_sasl_bind_s(ld, cp->dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); } #endif char * convert_domain_to_bind_path(char *domain) { char *dp, *bindp = NULL, *bp = NULL; size_t i = 0; if (!domain) return NULL; for (dp = domain; *dp; ++dp) { if (*dp == '.') ++i; } /* * add dc= and * replace . with ,dc= => new length = old length + #dots * 3 + 3 */ bindp = (char *) xmalloc(strlen(domain) + 3 + i * 3 + 1); bp = bindp; strcpy(bp, "dc="); bp += 3; for (dp = domain; *dp; ++dp) { if (*dp == '.') { strcpy(bp, ",dc="); bp += 4; } else { *bp = *dp; ++bp; } } *bp = '\0'; return bindp; } char * escape_filter(char *filter) { char *ldap_filter_esc, *ldf; size_t i; i = 0; for (ldap_filter_esc = filter; *ldap_filter_esc; ++ldap_filter_esc) { if ((*ldap_filter_esc == '*') || (*ldap_filter_esc == '(') || (*ldap_filter_esc == ')') || (*ldap_filter_esc == '\\')) i = i + 3; } ldap_filter_esc = (char *) xcalloc(strlen(filter) + i + 1, sizeof(char)); ldf = ldap_filter_esc; for (; *filter; ++filter) { if (*filter == '*') { strcpy(ldf, "\\2a"); ldf = ldf + 3; } else if (*filter == '(') { strcpy(ldf, "\\28"); ldf = ldf + 3; } else if (*filter == ')') { strcpy(ldf, "\\29"); ldf = ldf + 3; } else if (*filter == '\\') { strcpy(ldf, "\\5c"); ldf = ldf + 3; } else { *ldf = *filter; ++ldf; } } *ldf = '\0'; return ldap_filter_esc; } int check_AD(struct main_args *margs, LDAP * ld) { LDAPMessage *res; char **attr_value = NULL; struct timeval searchtime; size_t max_attr = 0; int rc = 0; #define FILTER_SCHEMA "(objectclass=*)" #define ATTRIBUTE_SCHEMA "schemaNamingContext" #define FILTER_SAM "(ldapdisplayname=samaccountname)" searchtime.tv_sec = SEARCH_TIMEOUT; searchtime.tv_usec = 0; debug((char *) "%s| %s: DEBUG: Search ldap server with bind path \"\" and filter: %s\n", LogTime(), PROGRAM, FILTER_SCHEMA); rc = ldap_search_ext_s(ld, (char *) "", LDAP_SCOPE_BASE, (char *) FILTER_SCHEMA, NULL, 0, NULL, NULL, &searchtime, 0, &res); if (rc == LDAP_SUCCESS) max_attr = get_attributes(ld, res, ATTRIBUTE_SCHEMA, &attr_value); if (max_attr == 1) { ldap_msgfree(res); debug((char *) "%s| %s: DEBUG: Search ldap server with bind path %s and filter: %s\n", LogTime(), PROGRAM, attr_value[0], FILTER_SAM); rc = ldap_search_ext_s(ld, attr_value[0], LDAP_SCOPE_SUBTREE, (char *) FILTER_SAM, NULL, 0, NULL, NULL, &searchtime, 0, &res); debug((char *) "%s| %s: DEBUG: Found %d ldap entr%s\n", LogTime(), PROGRAM, ldap_count_entries(ld, res), ldap_count_entries(ld, res) > 1 || ldap_count_entries(ld, res) == 0 ? "ies" : "y"); if (ldap_count_entries(ld, res) > 0) margs->AD = 1; } else debug((char *) "%s| %s: DEBUG: Did not find ldap entry for subschemasubentry\n", LogTime(), PROGRAM); debug((char *) "%s| %s: DEBUG: Determined ldap server %sas an Active Directory server\n", LogTime(), PROGRAM, margs->AD ? "" : "not "); /* * Cleanup */ if (attr_value) { size_t j; for (j = 0; j < max_attr; ++j) { xfree(attr_value[j]); } safe_free(attr_value); } ldap_msgfree(res); return rc; } int search_group_tree(struct main_args *margs, LDAP * ld, char *bindp, char *ldap_group, char *group, int depth) { LDAPMessage *res = NULL; char **attr_value = NULL; size_t max_attr = 0; char *filter = NULL; char *search_exp = NULL; size_t se_len = 0; int rc = 0, retval = 0; int ldepth; char *ldap_filter_esc = NULL; struct timeval searchtime; #define FILTER_GROUP_AD "(&(%s)(objectclass=group))" #define FILTER_GROUP "(&(memberuid=%s)(objectclass=posixgroup))" searchtime.tv_sec = SEARCH_TIMEOUT; searchtime.tv_usec = 0; if (margs->AD) filter = (char *) FILTER_GROUP_AD; else filter = (char *) FILTER_GROUP; ldap_filter_esc = escape_filter(ldap_group); se_len = strlen(filter) + strlen(ldap_filter_esc) + 1; search_exp = (char *) xmalloc(se_len); snprintf(search_exp, se_len, filter, ldap_filter_esc); xfree(ldap_filter_esc); if (depth > margs->mdepth) { debug((char *) "%s| %s: DEBUG: Max search depth reached %d>%d\n", LogTime(), PROGRAM, depth, margs->mdepth); xfree(search_exp); return 0; } debug((char *) "%s| %s: DEBUG: Search ldap server with bind path %s and filter : %s\n", LogTime(), PROGRAM, bindp, search_exp); rc = ldap_search_ext_s(ld, bindp, LDAP_SCOPE_SUBTREE, search_exp, NULL, 0, NULL, NULL, &searchtime, 0, &res); xfree(search_exp); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error searching ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return 0; } debug((char *) "%s| %s: DEBUG: Found %d ldap entr%s\n", LogTime(), PROGRAM, ldap_count_entries(ld, res), ldap_count_entries(ld, res) > 1 || ldap_count_entries(ld, res) == 0 ? "ies" : "y"); if (margs->AD) max_attr = get_attributes(ld, res, ATTRIBUTE_AD, &attr_value); else max_attr = get_attributes(ld, res, ATTRIBUTE, &attr_value); /* * Compare group names */ retval = 0; ldepth = depth + 1; for (size_t j = 0; j < max_attr; ++j) { char *av = NULL; /* Compare first CN= value assuming it is the same as the group name itself */ av = attr_value[j]; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (debug_enabled) { int n; debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" in hex UTF-8 is ", LogTime(), PROGRAM, j + 1, av); for (n = 0; av[n] != '\0'; ++n) fprintf(stderr, "%02x", (unsigned char) av[n]); fprintf(stderr, "\n"); } if (!strcasecmp(group, av)) { retval = 1; debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" matches group name \"%s\"\n", LogTime(), PROGRAM, j + 1, av, group); break; } else debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" does not match group name \"%s\"\n", LogTime(), PROGRAM, j + 1, av, group); /* * Do recursive group search */ debug((char *) "%s| %s: DEBUG: Perform recursive group search for group \"%s\"\n", LogTime(), PROGRAM, av); av = attr_value[j]; if (search_group_tree(margs, ld, bindp, av, group, ldepth)) { retval = 1; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (debug_enabled) debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" is member of group named \"%s\"\n", LogTime(), PROGRAM, j + 1, av, group); else break; } } /* * Cleanup */ if (attr_value) { for (size_t j = 0; j < max_attr; ++j) { xfree(attr_value[j]); } safe_free(attr_value); } ldap_msgfree(res); return retval; } int ldap_set_defaults(LDAP * ld) { int val, rc = 0; #if LDAP_OPT_NETWORK_TIMEOUT struct timeval tv; #endif val = LDAP_VERSION3; rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &val); if (rc != LDAP_SUCCESS) { debug((char *) "%s| %s: DEBUG: Error while setting protocol version: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } rc = ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); if (rc != LDAP_SUCCESS) { debug((char *) "%s| %s: DEBUG: Error while setting referrals off: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } #if LDAP_OPT_NETWORK_TIMEOUT tv.tv_sec = CONNECT_TIMEOUT; tv.tv_usec = 0; rc = ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); if (rc != LDAP_SUCCESS) { debug((char *) "%s| %s: DEBUG: Error while setting network timeout: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } #endif /* LDAP_OPT_NETWORK_TIMEOUT */ return LDAP_SUCCESS; } int ldap_set_ssl_defaults(struct main_args *margs) { #if HAVE_OPENLDAP || HAVE_LDAPSSL_CLIENT_INIT int rc = 0; #endif #if HAVE_OPENLDAP int val; #elif HAVE_LDAPSSL_CLIENT_INIT char *ssl_certdbpath = NULL; #endif #if HAVE_OPENLDAP if (!margs->rc_allow) { char *ssl_cacertfile = NULL; int free_path; debug((char *) "%s| %s: DEBUG: Enable server certificate check for ldap server.\n", LogTime(), PROGRAM); val = LDAP_OPT_X_TLS_DEMAND; rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &val); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting LDAP_OPT_X_TLS_REQUIRE_CERT DEMAND for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } ssl_cacertfile = getenv("TLS_CACERTFILE"); free_path = 0; if (!ssl_cacertfile) { ssl_cacertfile = xstrdup("/etc/ssl/certs/cert.pem"); free_path = 1; } debug((char *) "%s| %s: DEBUG: Set certificate file for ldap server to %s.(Changeable through setting environment variable TLS_CACERTFILE)\n", LogTime(), PROGRAM, ssl_cacertfile); rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ssl_cacertfile); if (ssl_cacertfile && free_path) { xfree(ssl_cacertfile); } if (rc != LDAP_OPT_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting LDAP_OPT_X_TLS_CACERTFILE for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } } else { debug((char *) "%s| %s: DEBUG: Disable server certificate check for ldap server.\n", LogTime(), PROGRAM); val = LDAP_OPT_X_TLS_ALLOW; rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &val); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting LDAP_OPT_X_TLS_REQUIRE_CERT ALLOW for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); return rc; } } #elif HAVE_LDAPSSL_CLIENT_INIT /* * Solaris SSL ldap calls require path to certificate database */ /* * rc = ldapssl_client_init( ssl_certdbpath, NULL ); * rc = ldapssl_advclientauth_init( ssl_certdbpath, NULL , 0 , NULL, NULL, 0, NULL, 2); */ ssl_certdbpath = getenv("SSL_CERTDBPATH"); if (!ssl_certdbpath) { ssl_certdbpath = xstrdup("/etc/certs"); } debug((char *) "%s| %s: DEBUG: Set certificate database path for ldap server to %s.(Changeable through setting environment variable SSL_CERTDBPATH)\n", LogTime(), PROGRAM, ssl_certdbpath); if (!margs->rc_allow) { rc = ldapssl_advclientauth_init(ssl_certdbpath, NULL, 0, NULL, NULL, 0, NULL, 2); } else { rc = ldapssl_advclientauth_init(ssl_certdbpath, NULL, 0, NULL, NULL, 0, NULL, 0); debug((char *) "%s| %s: DEBUG: Disable server certificate check for ldap server.\n", LogTime(), PROGRAM); } xfree(ssl_certdbpath); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting SSL for ldap server: %s\n", LogTime(), PROGRAM, ldapssl_err2string(rc)); return rc; } #else error((char *) "%s| %s: ERROR: SSL not supported by ldap library\n", LogTime(), PROGRAM); #endif return LDAP_SUCCESS; } size_t get_attributes(LDAP * ld, LDAPMessage * res, const char *attribute, char ***ret_value) { char **attr_value = *ret_value; size_t max_attr = 0; /* * loop over attributes */ debug((char *) "%s| %s: DEBUG: Search ldap entries for attribute : %s\n", LogTime(), PROGRAM, attribute); for (LDAPMessage *msg = ldap_first_entry(ld, res); msg; msg = ldap_next_entry(ld, msg)) { switch (ldap_msgtype(msg)) { case LDAP_RES_SEARCH_ENTRY: { BerElement *b = NULL; for (char *attr = ldap_first_attribute(ld, msg, &b); attr; attr = ldap_next_attribute(ld, msg, b)) { if (strcasecmp(attr, attribute) == 0) { struct berval **values; if ((values = ldap_get_values_len(ld, msg, attr)) != NULL) { for (int il = 0; values[il] != NULL; ++il) { attr_value = (char **) xrealloc(attr_value, (max_attr + 1) * sizeof(char *)); if (!attr_value) break; attr_value[max_attr] = (char *) xmalloc(values[il]->bv_len + 1); memcpy(attr_value[max_attr], values[il]->bv_val, values[il]->bv_len); attr_value[max_attr][values[il]->bv_len] = 0; max_attr++; } } ber_bvecfree(values); } ldap_memfree(attr); } ber_free(b, 0); } break; case LDAP_RES_SEARCH_REFERENCE: debug((char *) "%s| %s: DEBUG: Received a search reference message\n", LogTime(), PROGRAM); break; case LDAP_RES_SEARCH_RESULT: debug((char *) "%s| %s: DEBUG: Received a search result message\n", LogTime(), PROGRAM); break; default: break; } } debug((char *) "%s| %s: DEBUG: %" PRIuSIZE " ldap entr%s found with attribute : %s\n", LogTime(), PROGRAM, max_attr, max_attr > 1 || max_attr == 0 ? "ies" : "y", attribute); *ret_value = attr_value; return max_attr; } size_t get_bin_attributes(LDAP * ld, LDAPMessage * res, const char *attribute, char ***ret_value, int **ret_len) { char **attr_value = *ret_value; int *attr_len = *ret_len; size_t max_attr = 0; /* * loop over attributes */ debug((char *) "%s| %s: DEBUG: Search ldap entries for attribute : %s\n", LogTime(), PROGRAM, attribute); for ( LDAPMessage *msg = ldap_first_entry(ld, res); msg; msg = ldap_next_entry(ld, msg)) { switch (ldap_msgtype(msg)) { case LDAP_RES_SEARCH_ENTRY: { BerElement *b = NULL; for (char *attr = ldap_first_attribute(ld, msg, &b); attr; attr = ldap_next_attribute(ld, msg, b)) { if (strcasecmp(attr, attribute) == 0) { struct berval **values; if ((values = ldap_get_values_len(ld, msg, attr)) != NULL) { for (int il = 0; values[il] != NULL; ++il) { attr_value = (char **) xrealloc(attr_value, (max_attr + 1) * sizeof(char *)); if (!attr_value) break; attr_len = (int *) xrealloc(attr_len, (max_attr + 1) * sizeof(int)); if (!attr_len) break; attr_value[max_attr] = (char *) xmalloc(values[il]->bv_len + 1); memcpy(attr_value[max_attr], values[il]->bv_val, values[il]->bv_len); attr_value[max_attr][values[il]->bv_len] = 0; attr_len[max_attr]=values[il]->bv_len; max_attr++; } } ber_bvecfree(values); } ldap_memfree(attr); } ber_free(b, 0); } break; case LDAP_RES_SEARCH_REFERENCE: debug((char *) "%s| %s: DEBUG: Received a search reference message\n", LogTime(), PROGRAM); break; case LDAP_RES_SEARCH_RESULT: debug((char *) "%s| %s: DEBUG: Received a search result message\n", LogTime(), PROGRAM); break; default: break; } } debug((char *) "%s| %s: DEBUG: %" PRIuSIZE " ldap entr%s found with attribute : %s\n", LogTime(), PROGRAM, max_attr, max_attr > 1 || max_attr == 0 ? "ies" : "y", attribute); *ret_value = attr_value; *ret_len = attr_len; return max_attr; } /* * call to open ldap server with or without SSL */ LDAP * tool_ldap_open(struct main_args * margs, char *host, int port, char *ssl) { LDAP *ld; #if HAVE_OPENLDAP LDAPURLDesc *url = NULL; char *ldapuri = NULL; #endif int rc = 0; /* * Use ldap open here to check if TCP connection is possible. If possible use it. * (Not sure if this is the best way) */ #if HAVE_OPENLDAP url = (LDAPURLDesc *) xmalloc(sizeof(*url)); memset(url, 0, sizeof(*url)); #if HAVE_LDAP_URL_LUD_SCHEME if (ssl) url->lud_scheme = xstrdup("ldaps"); else url->lud_scheme = xstrdup("ldap"); #endif url->lud_host = xstrdup(host); url->lud_port = port; #if HAVE_LDAP_SCOPE_DEFAULT url->lud_scope = LDAP_SCOPE_DEFAULT; #else url->lud_scope = LDAP_SCOPE_SUBTREE; #endif #if HAVE_LDAP_URL_DESC2STR ldapuri = ldap_url_desc2str(url); #elif HAVE_LDAP_URL_PARSE rc = ldap_url_parse(ldapuri, &url); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while parsing url: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); xfree(ldapuri); ldap_free_urldesc(url); return NULL; } #else #error "No URL parsing function" #endif ldap_free_urldesc(url); rc = ldap_initialize(&ld, ldapuri); xfree(ldapuri); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while initialising connection to ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld,NULL,NULL); ld = NULL; return NULL; } #else ld = ldap_init(host, port); #endif rc = ldap_set_defaults(ld); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting default options for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } if (ssl) { /* * Try Start TLS first */ debug((char *) "%s| %s: DEBUG: Set SSL defaults\n", LogTime(), PROGRAM); rc = ldap_set_ssl_defaults(margs); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting SSL default options for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } #if HAVE_OPENLDAP /* * Use tls if possible */ rc = ldap_start_tls_s(ld, NULL, NULL); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting start_tls for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; url = (LDAPURLDesc *) xmalloc(sizeof(*url)); memset(url, 0, sizeof(*url)); #if HAVE_LDAP_URL_LUD_SCHEME url->lud_scheme = xstrdup("ldaps"); #endif url->lud_host = xstrdup(host); url->lud_port = port; #if HAVE_LDAP_SCOPE_DEFAULT url->lud_scope = LDAP_SCOPE_DEFAULT; #else url->lud_scope = LDAP_SCOPE_SUBTREE; #endif #if HAVE_LDAP_URL_DESC2STR ldapuri = ldap_url_desc2str(url); #elif HAVE_LDAP_URL_PARSE rc = ldap_url_parse(ldapuri, &url); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while parsing url: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); xfree(ldapuri); ldap_free_urldesc(url); return NULL; } #else #error "No URL parsing function" #endif ldap_free_urldesc(url); rc = ldap_initialize(&ld, ldapuri); xfree(ldapuri); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while initialising connection to ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } rc = ldap_set_defaults(ld); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting default options for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } } #elif HAVE_LDAPSSL_CLIENT_INIT ld = ldapssl_init(host, port, 1); if (!ld) { error((char *) "%s| %s: ERROR: Error while setting SSL for ldap server: %s\n", LogTime(), PROGRAM, ldapssl_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } rc = ldap_set_defaults(ld); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while setting default options for ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; return NULL; } #else error((char *) "%s| %s: ERROR: SSL not supported by ldap library\n", LogTime(), PROGRAM); #endif } return ld; } /* * ldap calls to get attribute from Ldap Directory Server */ int get_memberof(struct main_args *margs, char *user, char *domain, char *group) { LDAP *ld = NULL; LDAPMessage *res; #if !HAVE_SUN_LDAP_SDK int ldap_debug = 0; #endif struct ldap_creds *lcreds = NULL; char *bindp = NULL; char *filter = NULL; char *search_exp; size_t se_len = 0; struct timeval searchtime; int rc = 0, kc = 1; int retval; char **attr_value = NULL; size_t max_attr = 0; struct hstruct *hlist = NULL; size_t nhosts = 0; char *ldap_filter_esc = NULL; searchtime.tv_sec = SEARCH_TIMEOUT; searchtime.tv_usec = 0; /* * Fill Kerberos memory cache with credential from keytab for SASL/GSSAPI */ if (domain) { debug((char *) "%s| %s: DEBUG: Setup Kerberos credential cache\n", LogTime(), PROGRAM); #if HAVE_KRB5 kc = krb5_create_cache(domain); if (kc) { error((char *) "%s| %s: ERROR: Error during setup of Kerberos credential cache\n", LogTime(), PROGRAM); } #else kc = 1; debug((char *) "%s| %s: DEBUG: Kerberos is not supported. Use username/password with ldap url instead\n", LogTime(), PROGRAM); #endif } if (kc && (!margs->lurl || !margs->luser || !margs->lpass)) { /* * If Kerberos fails and no url given exit here */ retval = 0; goto cleanup; } #if !HAVE_SUN_LDAP_SDK /* * Initialise ldap */ ldap_debug = 127 /* LDAP_DEBUG_TRACE */ ; ldap_debug = -1 /* LDAP_DEBUG_ANY */ ; ldap_debug = 0; (void) ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldap_debug); #endif debug((char *) "%s| %s: DEBUG: Initialise ldap connection\n", LogTime(), PROGRAM); if (domain && !kc) { if (margs->ssl) { debug((char *) "%s| %s: DEBUG: Enable SSL to ldap servers\n", LogTime(), PROGRAM); } debug((char *) "%s| %s: DEBUG: Canonicalise ldap server name for domain %s\n", LogTime(), PROGRAM, domain); /* * Loop over list of ldap servers of users domain */ nhosts = get_ldap_hostname_list(margs, &hlist, 0, domain); for (size_t i = 0; i < nhosts; ++i) { int port = 389; if (hlist[i].port != -1) port = hlist[i].port; debug((char *) "%s| %s: DEBUG: Setting up connection to ldap server %s:%d\n", LogTime(), PROGRAM, hlist[i].host, port); ld = tool_ldap_open(margs, hlist[i].host, port, margs->ssl); if (!ld) continue; /* * ldap bind with SASL/GSSAPI authentication (only possible if a domain was part of the username) */ #if HAVE_SASL_H || HAVE_SASL_SASL_H || HAVE_SASL_DARWIN debug((char *) "%s| %s: DEBUG: Bind to ldap server with SASL/GSSAPI\n", LogTime(), PROGRAM); rc = tool_sasl_bind(ld, bindp, margs->ssl); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while binding to ldap server with SASL/GSSAPI: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; continue; } lcreds = (struct ldap_creds *) xmalloc(sizeof(struct ldap_creds)); lcreds->dn = NULL; lcreds->pw = margs->ssl ? xstrdup(margs->ssl) : NULL; ldap_set_rebind_proc(ld, ldap_sasl_rebind, (char *) lcreds); if (ld != NULL) { debug((char *) "%s| %s: DEBUG: %s initialised %sconnection to ldap server %s:%d\n", LogTime(), PROGRAM, ld ? "Successfully" : "Failed to", margs->ssl ? "SSL protected " : "", hlist[i].host, port); break; } #else ldap_unbind_ext(ld, NULL, NULL); ld = NULL; error((char *) "%s| %s: ERROR: SASL not supported on system\n", LogTime(), PROGRAM); continue; #endif } nhosts = free_hostname_list(&hlist, nhosts); if (ld == NULL) { debug((char *) "%s| %s: DEBUG: Error during initialisation of ldap connection: %s\n", LogTime(), PROGRAM, strerror(errno)); } bindp = convert_domain_to_bind_path(domain); } if ((!domain || !ld) && margs->lurl && strstr(margs->lurl, "://")) { char *hostname; char *host; int port; char *ssl = NULL; char *p; /* * If username does not contain a domain and a url was given then try it */ hostname = strstr(margs->lurl, "://") + 3; ssl = strstr(margs->lurl, "ldaps://"); if (ssl) { debug((char *) "%s| %s: DEBUG: Enable SSL to ldap servers\n", LogTime(), PROGRAM); } debug((char *) "%s| %s: DEBUG: Canonicalise ldap server name %s\n", LogTime(), PROGRAM, hostname); /* * Loop over list of ldap servers */ host = xstrdup(hostname); port = 389; if ((p = strchr(host, ':'))) { *p = '\0'; ++p; port = atoi(p); } nhosts = get_hostname_list(&hlist, 0, host); xfree(host); for (size_t i = 0; i < nhosts; ++i) { struct berval cred; if (margs->lpass) { cred.bv_val=margs->lpass; cred.bv_len=strlen(margs->lpass); } ld = tool_ldap_open(margs, hlist[i].host, port, ssl); if (!ld) continue; /* * ldap bind with username/password authentication */ debug((char *) "%s| %s: DEBUG: Bind to ldap server with Username/Password\n", LogTime(), PROGRAM); rc = ldap_sasl_bind_s(ld, margs->luser, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error while binding to ldap server with Username/Password: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; continue; } lcreds = (struct ldap_creds *) xmalloc(sizeof(struct ldap_creds)); lcreds->dn = xstrdup(margs->luser); lcreds->pw = xstrdup(margs->lpass); ldap_set_rebind_proc(ld, ldap_simple_rebind, (char *) lcreds); debug((char *) "%s| %s: DEBUG: %s set up %sconnection to ldap server %s:%d\n", LogTime(), PROGRAM, ld ? "Successfully" : "Failed to", ssl ? "SSL protected " : "", hlist[i].host, port); break; } nhosts = free_hostname_list(&hlist, nhosts); xfree(bindp); if (margs->lbind) { bindp = xstrdup(margs->lbind); } else { bindp = convert_domain_to_bind_path(domain); } } if (ld == NULL) { debug((char *) "%s| %s: DEBUG: Error during initialisation of ldap connection: %s\n", LogTime(), PROGRAM, strerror(errno)); retval = 0; goto cleanup; } /* * ldap search for user */ /* * Check if server is AD by querying for attribute samaccountname */ margs->AD = 0; rc = check_AD(margs, ld); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error determining ldap server type: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; retval = 0; goto cleanup; } if (margs->AD) filter = (char *) FILTER_AD; else filter = (char *) FILTER; ldap_filter_esc = escape_filter(user); se_len = strlen(filter) + strlen(ldap_filter_esc) + 1; search_exp = (char *) xmalloc(se_len); snprintf(search_exp, se_len, filter, ldap_filter_esc); xfree(ldap_filter_esc); debug((char *) "%s| %s: DEBUG: Search ldap server with bind path %s and filter : %s\n", LogTime(), PROGRAM, bindp, search_exp); rc = ldap_search_ext_s(ld, bindp, LDAP_SCOPE_SUBTREE, search_exp, NULL, 0, NULL, NULL, &searchtime, 0, &res); xfree(search_exp); if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error searching ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; retval = 0; goto cleanup; } debug((char *) "%s| %s: DEBUG: Found %d ldap entr%s\n", LogTime(), PROGRAM, ldap_count_entries(ld, res), ldap_count_entries(ld, res) > 1 || ldap_count_entries(ld, res) == 0 ? "ies" : "y"); if (ldap_count_entries(ld, res) != 0) { if (margs->AD) max_attr = get_attributes(ld, res, ATTRIBUTE_AD, &attr_value); else { max_attr = get_attributes(ld, res, ATTRIBUTE, &attr_value); } /* * Compare group names */ retval = 0; for (size_t k = 0; k < max_attr; ++k) { char *av = NULL; /* Compare first CN= value assuming it is the same as the group name itself */ av = attr_value[k]; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (debug_enabled) { debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" in hex UTF-8 is ", LogTime(), PROGRAM, k + 1, av); for (unsigned int n = 0; av[n] != '\0'; ++n) fprintf(stderr, "%02x", (unsigned char) av[n]); fprintf(stderr, "\n"); } if (!strcasecmp(group, av)) { retval = 1; if (debug_enabled) debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" matches group name \"%s\"\n", LogTime(), PROGRAM, k + 1, av, group); else break; } else debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " \"%s\" does not match group name \"%s\"\n", LogTime(), PROGRAM, k + 1, av, group); } /* * Do recursive group search for AD only since posixgroups can not contain other groups */ if (!retval && margs->AD) { if (debug_enabled && max_attr > 0) { debug((char *) "%s| %s: DEBUG: Perform recursive group search\n", LogTime(), PROGRAM); } for (size_t j = 0; j < max_attr; ++j) { char *av = NULL; av = attr_value[j]; if (search_group_tree(margs, ld, bindp, av, group, 1)) { retval = 1; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (debug_enabled) debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " group \"%s\" is (in)direct member of group \"%s\"\n", LogTime(), PROGRAM, j + 1, av, group); else break; } } } /* * Cleanup */ if (attr_value) { for (size_t j = 0; j < max_attr; ++j) { xfree(attr_value[j]); } safe_free(attr_value); } ldap_msgfree(res); } else if (ldap_count_entries(ld, res) == 0 && margs->AD) { ldap_msgfree(res); ldap_unbind_ext(ld, NULL, NULL); ld = NULL; retval = 0; goto cleanup; } else { ldap_msgfree(res); retval = 0; } if (retval == 0) { /* * Check for primary Group membership */ debug((char *) "%s| %s: DEBUG: Search for primary group membership: \"%s\"\n", LogTime(), PROGRAM, group); if (margs->AD) filter = (char *) FILTER_AD; else filter = (char *) FILTER_UID; ldap_filter_esc = escape_filter(user); se_len = strlen(filter) + strlen(ldap_filter_esc) + 1; search_exp = (char *) xmalloc(se_len); snprintf(search_exp, se_len, filter, ldap_filter_esc); xfree(ldap_filter_esc); debug((char *) "%s| %s: DEBUG: Search ldap server with bind path %s and filter: %s\n", LogTime(), PROGRAM, bindp, search_exp); rc = ldap_search_ext_s(ld, bindp, LDAP_SCOPE_SUBTREE, search_exp, NULL, 0, NULL, NULL, &searchtime, 0, &res); xfree(search_exp); debug((char *) "%s| %s: DEBUG: Found %d ldap entr%s\n", LogTime(), PROGRAM, ldap_count_entries(ld, res), ldap_count_entries(ld, res) > 1 || ldap_count_entries(ld, res) == 0 ? "ies" : "y"); max_attr = 0; if (!rc) { if (margs->AD) max_attr = get_attributes(ld, res, ATTRIBUTE_GID_AD, &attr_value); else max_attr = get_attributes(ld, res, ATTRIBUTE_GID, &attr_value); } if (max_attr == 1) { char **attr_value_2 = NULL; size_t max_attr_2 = 0; if (margs->AD) { char **attr_value_3 = NULL; int *attr_len_3 = NULL; size_t max_attr_3 = 0; uint32_t gid=atoi(attr_value[0]); /* Get objectsid and search for group * with objectsid = domain(objectsid) + primarygroupid */ debug((char *) "%s| %s: DEBUG: Got primaryGroupID %u\n", LogTime(), PROGRAM, gid); max_attr_3 = get_bin_attributes(ld, res, ATTRIBUTE_SID, &attr_value_3, &attr_len_3); ldap_msgfree(res); if (max_attr_3 == 1) { int len=attr_len_3[0]; if (len < 4) { debug((char *) "%s| %s: ERROR: Length %d is too short for objectSID\n", LogTime(), PROGRAM, len); rc = 1; } else { char *se=NULL; attr_value_3[0][len-1]=((gid>>24) & 0xff); attr_value_3[0][len-2]=((gid>>16) & 0xff); attr_value_3[0][len-3]=((gid>>8) & 0xff); attr_value_3[0][len-4]=((gid>>0) & 0xff); #define FILTER_SID_1 "(objectSID=" #define FILTER_SID_2 ")" se_len = strlen(FILTER_SID_1) + len*3 + strlen(FILTER_SID_2) + 1; search_exp = (char *) xmalloc(se_len); snprintf(search_exp, se_len, "%s", FILTER_SID_1 ); for (int j=0; j 1 || ldap_count_entries(ld, res) == 0 ? "ies" : "y"); } } else { rc = 1; } if (attr_value_3) { size_t j; for (j = 0; j < max_attr_3; ++j) { xfree(attr_value_3[j]); } safe_free(attr_value_3); } if (attr_len_3) { xfree(attr_len_3); } } else { ldap_msgfree(res); filter = (char *) FILTER_GID; ldap_filter_esc = escape_filter(attr_value[0]); se_len = strlen(filter) + strlen(ldap_filter_esc) + 1; search_exp = (char *) xmalloc(se_len); snprintf(search_exp, se_len, filter, ldap_filter_esc); xfree(ldap_filter_esc); debug((char *) "%s| %s: DEBUG: Search ldap server with bind path %s and filter: %s\n", LogTime(), PROGRAM, bindp, search_exp); rc = ldap_search_ext_s(ld, bindp, LDAP_SCOPE_SUBTREE, search_exp, NULL, 0, NULL, NULL, &searchtime, 0, &res); xfree(search_exp); } if (!rc) { if (margs->AD) max_attr_2 = get_attributes(ld, res, ATTRIBUTE_DN, &attr_value_2); else max_attr_2 = get_attributes(ld, res, ATTRIBUTE, &attr_value_2); ldap_msgfree(res); } else { ldap_msgfree(res); } /* * Compare group names */ retval = 0; if (max_attr_2 == 1) { /* Compare first CN= value assuming it is the same as the group name itself */ char *av = attr_value_2[0]; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (!strcasecmp(group, av)) { retval = 1; debug((char *) "%s| %s: DEBUG: \"%s\" matches group name \"%s\"\n", LogTime(), PROGRAM, av, group); } else debug((char *) "%s| %s: DEBUG: \"%s\" does not match group name \"%s\"\n", LogTime(), PROGRAM, av, group); } /* * Do recursive group search for AD only since posixgroups can not contain other groups */ if (!retval && margs->AD) { if (debug_enabled && max_attr_2 > 0) { debug((char *) "%s| %s: DEBUG: Perform recursive group search\n", LogTime(), PROGRAM); } for (size_t j = 0; j < max_attr_2; ++j) { char *av = NULL; av = attr_value_2[j]; if (search_group_tree(margs, ld, bindp, av, group, 1)) { retval = 1; if (!strncasecmp("CN=", av, 3)) { char *avp = NULL; av += 3; if ((avp = strchr(av, ','))) { *avp = '\0'; } } if (debug_enabled) { debug((char *) "%s| %s: DEBUG: Entry %" PRIuSIZE " group \"%s\" is (in)direct member of group \"%s\"\n", LogTime(), PROGRAM, j + 1, av, group); } else { break; } } } } /* * Cleanup */ if (attr_value_2) { size_t j; for (j = 0; j < max_attr_2; ++j) { xfree(attr_value_2[j]); } safe_free(attr_value_2); } debug((char *) "%s| %s: DEBUG: Users primary group %s %s\n", LogTime(), PROGRAM, retval ? "matches" : "does not match", group); } else { ldap_msgfree(res); debug((char *) "%s| %s: DEBUG: Did not find ldap entry for group %s\n", LogTime(), PROGRAM, group); } /* * Cleanup */ if (attr_value) { for (size_t j = 0; j < max_attr; ++j) { xfree(attr_value[j]); } safe_free(attr_value); } } rc = ldap_unbind_ext(ld, NULL, NULL); ld = NULL; if (rc != LDAP_SUCCESS) { error((char *) "%s| %s: ERROR: Error unbind ldap server: %s\n", LogTime(), PROGRAM, ldap_err2string(rc)); } debug((char *) "%s| %s: DEBUG: Unbind ldap server\n", LogTime(), PROGRAM); cleanup: #if HAVE_KRB5 if (domain) krb5_cleanup(); #endif if (lcreds) { xfree(lcreds->dn); xfree(lcreds->pw); xfree(lcreds); } xfree(bindp); return (retval); } #endif