/* * Copyright (C) 1996-2023 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. */ /* * squid_ldap_auth: authentication via ldap for squid proxy server * * Authors: * Henrik Nordstrom * hno@squid-cache.org * * Glen Newton * glen.newton@nrc.ca * Advanced Services * CISTI * National Research Council * * with contributions from others mentioned in the Changes section below * * Usage: squid_ldap_auth -b basedn [-s searchscope] * [-f searchfilter] [-D binddn -w bindpasswd] * [-u attr] [-h host] [-p port] [-P] [-R] [ldap_server_name[:port]] ... * * Dependencies: You need to get the OpenLDAP libraries * from http://www.openldap.org or another compatible LDAP C-API * implementation. * * If you want to make a TLS enabled connection you will also need the * OpenSSL libraries linked into openldap. See http://www.openssl.org/ * * License: squid_ldap_auth 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, * or (at your option) any later version. * * Changes: * 2019-01-02: Amish * - Use SEND_*() macro and support for BH error * 2005-01-07: Henrik Nordstrom * - Added some sanity checks on login names to avoid * users bypassing equality checks by exploring the * overly helpful match capabilities of LDAP * 2004-07-17: Henrik Nordstrom * - Corrected non-persistent mode to only issue one * ldap_bind per connection. * - -U option to compare the users password rather * than binding. * 2004-03-01: Henrik Nordstrom * - corrected building of search filters to escape * unsafe input * - -d option for "debug" like squid_ldap_group * 2004-01-05: Henrik Nordstrom * - Corrected TLS mode * 2003-03-01: David J N Begley * - Support for Netscape API method of ldap over SSL * connections * - Timeout option for better recovery when using * multiple LDAP servers * 2003-03-01: Christoph Lechleitner * - Added -W option to read bindpasswd from file * 2003-03-01: Juerg Michel * - Added support for ldap URI via the -H option * (requires OpenLDAP) * 2001-12-12: Michael Cunningham * - Added TLS support and partial ldap version 3 support. * 2001-10-04: Henrik Nordstrom * - Be consistent with the other helpers in how * spaces are managed. If there is space characters * then these are assumed to be part of the password * 2001-09-05: Henrik Nordstrom * - Added ability to specify another default LDAP port to * connect to. Persistent connections moved to -P * 2001-05-02: Henrik Nordstrom * - Support newer OpenLDAP 2.x libraries using the * revised Internet Draft API which unfortunately * is not backwards compatible with RFC1823.. * 2001-04-15: Henrik Nordstrom * - Added command line option for basedn * - Added the ability to search for the user DN * 2001-04-16: Henrik Nordstrom * - Added -D binddn -w bindpasswd. * 2001-04-17: Henrik Nordstrom * - Added -R to disable referrals * - Added -a to control alias dereferencing * 2001-04-17: Henrik Nordstrom * - Added -u, DN username attribute name * 2001-04-18: Henrik Nordstrom * - Allow full filter specifications in -f */ #include "squid.h" #include "helper/protocol_defines.h" #define LDAP_DEPRECATED 1 #include "rfc1738.h" #include "util.h" #include #include #if _SQUID_WINDOWS_ && !_SQUID_CYGWIN_ #define snprintf _snprintf #include #include #ifndef LDAPAPI #define LDAPAPI __cdecl #endif #ifdef LDAP_VERSION3 #ifndef LDAP_OPT_X_TLS #define LDAP_OPT_X_TLS 0x6000 #endif /* Some tricks to allow dynamic bind with ldap_start_tls_s entry point at * run time. */ #undef ldap_start_tls_s #if LDAP_UNICODE #define LDAP_START_TLS_S "ldap_start_tls_sW" typedef WINLDAPAPI ULONG(LDAPAPI * PFldap_start_tls_s) (IN PLDAP, OUT PULONG, OUT LDAPMessage **, IN PLDAPControlW *, IN PLDAPControlW *); #else #define LDAP_START_TLS_S "ldap_start_tls_sA" typedef WINLDAPAPI ULONG(LDAPAPI * PFldap_start_tls_s) (IN PLDAP, OUT PULONG, OUT LDAPMessage **, IN PLDAPControlA *, IN PLDAPControlA *); #endif /* LDAP_UNICODE */ PFldap_start_tls_s Win32_ldap_start_tls_s; #define ldap_start_tls_s(l,s,c) Win32_ldap_start_tls_s(l,NULL,NULL,s,c) #endif /* LDAP_VERSION3 */ #else #include #include #ifndef LDAP_SECURITY_ERROR #define LDAP_SECURITY_ERROR(err) (0x2f <= (err) && (err) <= 0x32) // [47, 50] #endif #endif #define PROGRAM_NAME "basic_ldap_auth" /* Global options */ static const char *basedn; static const char *searchfilter = NULL; static const char *binddn = NULL; static const char *bindpasswd = NULL; static const char *userattr = "uid"; static const char *passwdattr = NULL; static int searchscope = LDAP_SCOPE_SUBTREE; static int persistent = 0; static int bind_once = 0; static int noreferrals = 0; static int aliasderef = LDAP_DEREF_NEVER; #if defined(NETSCAPE_SSL) static const char *sslpath = NULL; static int sslinit = 0; #endif static int connect_timeout = 0; static int timelimit = LDAP_NO_LIMIT; /* Added for TLS support and version 3 */ static int use_tls = 0; static int version = -1; static int checkLDAP(LDAP * ld, const char *userid, const char *password, const char *server, int port); static int readSecret(const char *filename); /* Yuck.. we need to glue to different versions of the API */ #ifndef LDAP_NO_ATTRS #define LDAP_NO_ATTRS "1.1" #endif #if defined(LDAP_API_VERSION) && LDAP_API_VERSION > 1823 static int squid_ldap_errno(LDAP * ld) { int err = 0; ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err); return err; } static void squid_ldap_set_aliasderef(LDAP * ld, int deref) { ldap_set_option(ld, LDAP_OPT_DEREF, &deref); } static void squid_ldap_set_referrals(LDAP * ld, int referrals) { int *value = static_cast(referrals ? LDAP_OPT_ON :LDAP_OPT_OFF); ldap_set_option(ld, LDAP_OPT_REFERRALS, value); } static void squid_ldap_set_timelimit(LDAP * ld, int aTimeLimit) { ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &aTimeLimit); } static void squid_ldap_set_connect_timeout(LDAP * ld, int aTimeLimit) { #if defined(LDAP_OPT_NETWORK_TIMEOUT) struct timeval tv; tv.tv_sec = aTimeLimit; tv.tv_usec = 0; ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); #elif defined(LDAP_X_OPT_CONNECT_TIMEOUT) aTimeLimit *= 1000; ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &aTimeLimit); #endif } static void squid_ldap_memfree(char *p) { ldap_memfree(p); } #else static int squid_ldap_errno(LDAP * ld) { return ld->ld_errno; } static void squid_ldap_set_aliasderef(LDAP * ld, int deref) { ld->ld_deref = deref; } static void squid_ldap_set_referrals(LDAP * ld, int referrals) { if (referrals) ld->ld_options |= ~LDAP_OPT_REFERRALS; else ld->ld_options &= ~LDAP_OPT_REFERRALS; } static void squid_ldap_set_timelimit(LDAP * ld, int timelimit) { ld->ld_timelimit = timelimit; } static void squid_ldap_set_connect_timeout(LDAP * ld, int timelimit) { fprintf(stderr, "Connect timeouts not supported in your LDAP library\n"); } static void squid_ldap_memfree(char *p) { free(p); } #endif #ifdef LDAP_API_FEATURE_X_OPENLDAP #if LDAP_VENDOR_VERSION > 194 #define HAS_URI_SUPPORT 1 #endif #endif static LDAP * open_ldap_connection(const char *ldapServer, int port) { LDAP *ld = NULL; #if HAS_URI_SUPPORT if (strstr(ldapServer, "://") != NULL) { int rc = ldap_initialize(&ld, ldapServer); if (rc != LDAP_SUCCESS) { fprintf(stderr, "\nUnable to connect to LDAPURI:%s\n", ldapServer); exit(EXIT_FAILURE); } } else #endif #if NETSCAPE_SSL if (sslpath) { if (!sslinit && (ldapssl_client_init(sslpath, NULL) != LDAP_SUCCESS)) { fprintf(stderr, "\nUnable to initialise SSL with cert path %s\n", sslpath); exit(EXIT_FAILURE); } else { ++sslinit; } if ((ld = ldapssl_init(ldapServer, port, 1)) == NULL) { fprintf(stderr, "\nUnable to connect to SSL LDAP server: %s port:%d\n", ldapServer, port); exit(EXIT_FAILURE); } } else #endif if ((ld = ldap_init(ldapServer, port)) == NULL) { fprintf(stderr, "\nUnable to connect to LDAP server:%s port:%d\n", ldapServer, port); exit(EXIT_FAILURE); } if (connect_timeout) squid_ldap_set_connect_timeout(ld, connect_timeout); #ifdef LDAP_VERSION3 if (version == -1) { version = LDAP_VERSION3; } if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_SUCCESS) { fprintf(stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n", version); exit(EXIT_FAILURE); } if (use_tls) { #ifdef LDAP_OPT_X_TLS if (version != LDAP_VERSION3) { fprintf(stderr, "TLS requires LDAP version 3\n"); exit(EXIT_FAILURE); } else if (ldap_start_tls_s(ld, NULL, NULL) != LDAP_SUCCESS) { fprintf(stderr, "Could not Activate TLS connection\n"); exit(EXIT_FAILURE); } #else fprintf(stderr, "TLS not supported with your LDAP library\n"); exit(EXIT_FAILURE); #endif } #endif squid_ldap_set_timelimit(ld, timelimit); squid_ldap_set_referrals(ld, !noreferrals); squid_ldap_set_aliasderef(ld, aliasderef); return ld; } /* Make a sanity check on the username to reject oddly typed names */ static int validUsername(const char *user) { const unsigned char *p = (const unsigned char *) user; /* Leading whitespace? */ if (xisspace(p[0])) return 0; while (p[0] && p[1]) { if (xisspace(p[0])) { /* More than one consequitive space? */ if (xisspace(p[1])) return 0; /* or odd space type character used? */ if (p[0] != ' ') return 0; } ++p; } /* Trailing whitespace? */ if (xisspace(p[0])) return 0; return 1; } int main(int argc, char **argv) { char buf[1024]; char *user, *passwd; char *ldapServer = NULL; LDAP *ld = NULL; int tryagain; int port = LDAP_PORT; setbuf(stdout, NULL); while (argc > 1 && argv[1][0] == '-') { const char *value = ""; char option = argv[1][1]; switch (option) { case 'P': case 'R': case 'z': case 'Z': case 'd': case 'O': break; default: if (strlen(argv[1]) > 2) { value = argv[1] + 2; } else if (argc > 2) { value = argv[2]; ++argv; --argc; } else value = ""; break; } ++argv; --argc; switch (option) { case 'H': #if !HAS_URI_SUPPORT fprintf(stderr, "ERROR: Your LDAP library does not have URI support\n"); exit(EXIT_FAILURE); #endif /* Fall thru to -h */ case 'h': if (ldapServer) { int len = strlen(ldapServer) + 1 + strlen(value) + 1; char *newhost = static_cast(xmalloc(len)); snprintf(newhost, len, "%s %s", ldapServer, value); free(ldapServer); ldapServer = newhost; } else { ldapServer = xstrdup(value); } break; case 'b': basedn = value; break; case 'f': searchfilter = value; break; case 'u': userattr = value; break; case 'U': passwdattr = value; break; case 's': if (strcmp(value, "base") == 0) searchscope = LDAP_SCOPE_BASE; else if (strcmp(value, "one") == 0) searchscope = LDAP_SCOPE_ONELEVEL; else if (strcmp(value, "sub") == 0) searchscope = LDAP_SCOPE_SUBTREE; else { fprintf(stderr, PROGRAM_NAME ": ERROR: Unknown search scope '%s'\n", value); exit(EXIT_FAILURE); } break; case 'E': #if defined(NETSCAPE_SSL) sslpath = value; if (port == LDAP_PORT) port = LDAPS_PORT; #else fprintf(stderr, PROGRAM_NAME " ERROR: -E unsupported with this LDAP library\n"); exit(EXIT_FAILURE); #endif break; case 'c': connect_timeout = atoi(value); break; case 't': timelimit = atoi(value); break; case 'a': if (strcmp(value, "never") == 0) aliasderef = LDAP_DEREF_NEVER; else if (strcmp(value, "always") == 0) aliasderef = LDAP_DEREF_ALWAYS; else if (strcmp(value, "search") == 0) aliasderef = LDAP_DEREF_SEARCHING; else if (strcmp(value, "find") == 0) aliasderef = LDAP_DEREF_FINDING; else { fprintf(stderr, PROGRAM_NAME ": ERROR: Unknown alias dereference method '%s'\n", value); exit(EXIT_FAILURE); } break; case 'D': binddn = value; break; case 'w': bindpasswd = value; break; case 'W': readSecret(value); break; case 'P': persistent = !persistent; break; case 'O': bind_once = !bind_once; break; case 'p': port = atoi(value); break; case 'R': noreferrals = !noreferrals; break; #ifdef LDAP_VERSION3 case 'v': switch (atoi(value)) { case 2: version = LDAP_VERSION2; break; case 3: version = LDAP_VERSION3; break; default: fprintf(stderr, "Protocol version should be 2 or 3\n"); exit(EXIT_FAILURE); } break; case 'Z': if (version == LDAP_VERSION2) { fprintf(stderr, "TLS (-Z) is incompatible with version %d\n", version); exit(EXIT_FAILURE); } version = LDAP_VERSION3; use_tls = 1; break; #endif case 'd': debug_enabled = 1; break; default: fprintf(stderr, PROGRAM_NAME ": ERROR: Unknown command line option '%c'\n", option); exit(EXIT_FAILURE); } } while (argc > 1) { char *value = argv[1]; if (ldapServer) { int len = strlen(ldapServer) + 1 + strlen(value) + 1; char *newhost = static_cast(xmalloc(len)); snprintf(newhost, len, "%s %s", ldapServer, value); free(ldapServer); ldapServer = newhost; } else { ldapServer = xstrdup(value); } --argc; ++argv; } if (!ldapServer) ldapServer = xstrdup("localhost"); if (!basedn) { fprintf(stderr, "Usage: " PROGRAM_NAME " -b basedn [options] [ldap_server_name[:port]]...\n\n"); fprintf(stderr, "\t-b basedn (REQUIRED)\tbase dn under which to search\n"); fprintf(stderr, "\t-f filter\t\tsearch filter to locate user DN\n"); fprintf(stderr, "\t-u userattr\t\tusername DN attribute\n"); fprintf(stderr, "\t-s base|one|sub\t\tsearch scope\n"); fprintf(stderr, "\t-D binddn\t\tDN to bind as to perform searches\n"); fprintf(stderr, "\t-w bindpasswd\t\tpassword for binddn\n"); fprintf(stderr, "\t-W secretfile\t\tread password for binddn from file secretfile\n"); #if HAS_URI_SUPPORT fprintf(stderr, "\t-H URI\t\t\tLDAPURI (defaults to ldap://localhost)\n"); #endif fprintf(stderr, "\t-h server\t\tLDAP server (defaults to localhost)\n"); fprintf(stderr, "\t-p port\t\t\tLDAP server port\n"); fprintf(stderr, "\t-P\t\t\tpersistent LDAP connection\n"); #if defined(NETSCAPE_SSL) fprintf(stderr, "\t-E sslcertpath\t\tenable LDAP over SSL\n"); #endif fprintf(stderr, "\t-c timeout\t\tconnect timeout\n"); fprintf(stderr, "\t-t timelimit\t\tsearch time limit\n"); fprintf(stderr, "\t-R\t\t\tdo not follow referrals\n"); fprintf(stderr, "\t-a never|always|search|find\n\t\t\t\twhen to dereference aliases\n"); #ifdef LDAP_VERSION3 fprintf(stderr, "\t-v 2|3\t\t\tLDAP version\n"); fprintf(stderr, "\t-Z\t\t\tTLS encrypt the LDAP connection, requires LDAP version 3\n"); #endif fprintf(stderr, "\t-d\t\t\tenable debug mode\n"); fprintf(stderr, "\n"); fprintf(stderr, "\tIf no search filter is specified, then the dn =user,basedn\n\twill be used (same as specifying a search filter of '=',\n\tbut quicker as as there is no need to search for the user DN)\n\n"); fprintf(stderr, "\tIf you need to bind as a user to perform searches then use the\n\t-D binddn -w bindpasswd or -D binddn -W secretfile options\n\n"); exit(EXIT_FAILURE); } /* On Windows ldap_start_tls_s is available starting from Windows XP, * so we need to bind at run-time with the function entry point */ #if _SQUID_WINDOWS_ if (use_tls) { HMODULE WLDAP32Handle; WLDAP32Handle = GetModuleHandle("wldap32"); if ((Win32_ldap_start_tls_s = (PFldap_start_tls_s) GetProcAddress(WLDAP32Handle, LDAP_START_TLS_S)) == NULL) { fprintf(stderr, PROGRAM_NAME ": ERROR: TLS (-Z) not supported on this platform.\n"); exit(EXIT_FAILURE); } } #endif while (fgets(buf, sizeof(buf), stdin) != NULL) { user = strtok(buf, " \r\n"); passwd = strtok(NULL, "\r\n"); if (!user) { SEND_ERR(HLP_MSG("Missing username")); continue; } if (!passwd || !passwd[0]) { SEND_ERR(HLP_MSG("Missing password")); continue; } rfc1738_unescape(user); rfc1738_unescape(passwd); if (!validUsername(user)) { SEND_ERR(HLP_MSG("Invalid username")); continue; } tryagain = (ld != NULL); recover: if (ld == NULL && persistent) ld = open_ldap_connection(ldapServer, port); if (checkLDAP(ld, user, passwd, ldapServer, port) != 0) { const auto e = squid_ldap_errno(ld); if (tryagain && e != LDAP_INVALID_CREDENTIALS) { tryagain = 0; ldap_unbind(ld); ld = NULL; goto recover; } if (LDAP_SECURITY_ERROR(e)) SEND_ERR(ldap_err2string(e)); else SEND_BH(ldap_err2string(e)); } else { SEND_OK(""); } if (ld && (squid_ldap_errno(ld) != LDAP_SUCCESS && squid_ldap_errno(ld) != LDAP_INVALID_CREDENTIALS)) { ldap_unbind(ld); ld = NULL; } } if (ld) ldap_unbind(ld); return EXIT_SUCCESS; } static int ldap_escape_value(char *escaped, int size, const char *src) { int n = 0; while (size > 4 && *src) { switch (*src) { case '*': case '(': case ')': case '\\': n += 3; size -= 3; if (size > 0) { *escaped = '\\'; ++escaped; snprintf(escaped, 3, "%02x", (unsigned char) *src); ++src; escaped += 2; } break; default: *escaped = *src; ++escaped; ++src; ++n; --size; } } *escaped = '\0'; return n; } /* Check the userid & password. * Return 0 on success, 1 on failure */ static int checkLDAP(LDAP * persistent_ld, const char *userid, const char *password, const char *ldapServer, int port) { char dn[1024]; int ret = 0; LDAP *bind_ld = NULL; if (!*password) { /* LDAP can't bind with a blank password. Seen as "anonymous" * and always granted access */ debug("Blank password given\n"); return 1; } if (searchfilter) { char filter[16384]; char escaped_login[1024]; LDAPMessage *res = NULL; LDAPMessage *entry; char *searchattr[] = {(char *)LDAP_NO_ATTRS, NULL}; char *userdn; int rc; LDAP *search_ld = persistent_ld; if (!search_ld) search_ld = open_ldap_connection(ldapServer, port); ldap_escape_value(escaped_login, sizeof(escaped_login), userid); if (binddn) { rc = ldap_simple_bind_s(search_ld, binddn, bindpasswd); if (rc != LDAP_SUCCESS) { fprintf(stderr, PROGRAM_NAME ": WARNING, could not bind to binddn '%s'\n", ldap_err2string(rc)); ret = 1; goto search_done; } } snprintf(filter, sizeof(filter), searchfilter, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login, escaped_login); debug("user filter '%s', searchbase '%s'\n", filter, basedn); rc = ldap_search_s(search_ld, basedn, searchscope, filter, searchattr, 1, &res); if (rc != LDAP_SUCCESS) { if (noreferrals && rc == LDAP_PARTIAL_RESULTS) { /* Everything is fine. This is expected when referrals * are disabled. */ debug("noreferrals && rc == LDAP_PARTIAL_RESULTS\n"); } else { fprintf(stderr, PROGRAM_NAME ": WARNING, LDAP search error '%s'\n", ldap_err2string(rc)); #if defined(NETSCAPE_SSL) if (sslpath && ((rc == LDAP_SERVER_DOWN) || (rc == LDAP_CONNECT_ERROR))) { int sslerr = PORT_GetError(); fprintf(stderr, PROGRAM_NAME ": WARNING, SSL error %d (%s)\n", sslerr, ldapssl_err2string(sslerr)); } #endif ret = 1; goto search_done; } } entry = ldap_first_entry(search_ld, res); if (!entry) { debug("Ldap search returned nothing\n"); ret = 1; goto search_done; } userdn = ldap_get_dn(search_ld, entry); if (!userdn) { fprintf(stderr, PROGRAM_NAME ": ERROR, could not get user DN for '%s'\n", userid); ret = 1; goto search_done; } snprintf(dn, sizeof(dn), "%s", userdn); squid_ldap_memfree(userdn); if (ret == 0 && (!binddn || !bind_once || passwdattr)) { /* Reuse the search connection for comparing the user password attribute */ bind_ld = search_ld; search_ld = NULL; } search_done: if (res) { ldap_msgfree(res); res = NULL; } if (search_ld && search_ld != persistent_ld) { ldap_unbind(search_ld); search_ld = NULL; } if (ret != 0) return ret; } else { snprintf(dn, sizeof(dn), "%s=%s,%s", userattr, userid, basedn); } debug("attempting to authenticate user '%s'\n", dn); if (!bind_ld && !bind_once) bind_ld = persistent_ld; if (!bind_ld) bind_ld = open_ldap_connection(ldapServer, port); if (passwdattr) { if (ldap_compare_s(bind_ld, dn, passwdattr, password) != LDAP_COMPARE_TRUE) { ret = 1; } } else if (ldap_simple_bind_s(bind_ld, dn, password) != LDAP_SUCCESS) ret = 1; if (bind_ld != persistent_ld) { ldap_unbind(bind_ld); bind_ld = NULL; } return ret; } int readSecret(const char *filename) { char buf[BUFSIZ]; char *e = NULL; FILE *f; char *passwd = NULL; if (!(f = fopen(filename, "r"))) { fprintf(stderr, PROGRAM_NAME " ERROR: Can not read secret file %s\n", filename); return 1; } if (!fgets(buf, sizeof(buf) - 1, f)) { fprintf(stderr, PROGRAM_NAME " ERROR: Secret file %s is empty\n", filename); fclose(f); return 1; } /* strip whitespaces on end */ if ((e = strrchr(buf, '\n'))) *e = 0; if ((e = strrchr(buf, '\r'))) *e = 0; passwd = (char *) calloc(sizeof(char), strlen(buf) + 1); if (!passwd) { fprintf(stderr, PROGRAM_NAME " ERROR: can not allocate memory\n"); exit(EXIT_FAILURE); } strcpy(passwd, buf); bindpasswd = passwd; fclose(f); return 0; }