2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 * squid_ldap_auth: authentication via ldap for squid proxy server
20 * National Research Council
22 * with contributions from others mentioned in the Changes section below
24 * Usage: squid_ldap_auth -b basedn [-s searchscope]
25 * [-f searchfilter] [-D binddn -w bindpasswd]
26 * [-u attr] [-h host] [-p port] [-P] [-R] [ldap_server_name[:port]] ...
28 * Dependencies: You need to get the OpenLDAP libraries
29 * from http://www.openldap.org or another compatible LDAP C-API
32 * If you want to make a TLS enabled connection you will also need the
33 * OpenSSL libraries linked into openldap. See http://www.openssl.org/
35 * License: squid_ldap_auth is free software; you can redistribute it
36 * and/or modify it under the terms of the GNU General Public License
37 * as published by the Free Software Foundation; either version 2,
38 * or (at your option) any later version.
41 * 2005-01-07: Henrik Nordstrom <hno@squid-cache.org>
42 * - Added some sanity checks on login names to avoid
43 * users bypassing equality checks by exploring the
44 * overly helpful match capabilities of LDAP
45 * 2004-07-17: Henrik Nordstrom <hno@squid-cache.org>
46 * - Corrected non-persistent mode to only issue one
47 * ldap_bind per connection.
48 * - -U option to compare the users password rather
50 * 2004-03-01: Henrik Nordstrom <hno@squid-cache.org>
51 * - corrected building of search filters to escape
53 * - -d option for "debug" like squid_ldap_group
54 * 2004-01-05: Henrik Nordstrom <hno@squid-cache.org>
55 * - Corrected TLS mode
56 * 2003-03-01: David J N Begley
57 * - Support for Netscape API method of ldap over SSL
59 * - Timeout option for better recovery when using
60 * multiple LDAP servers
61 * 2003-03-01: Christoph Lechleitner <lech@ibcl.at>
62 * - Added -W option to read bindpasswd from file
63 * 2003-03-01: Juerg Michel
64 * - Added support for ldap URI via the -H option
66 * 2001-12-12: Michael Cunningham <m.cunningham@xpedite.com>
67 * - Added TLS support and partial ldap version 3 support.
68 * 2001-10-04: Henrik Nordstrom <hno@squid-cache.org>
69 * - Be consistent with the other helpers in how
70 * spaces are managed. If there is space characters
71 * then these are assumed to be part of the password
72 * 2001-09-05: Henrik Nordstrom <hno@squid-cache.org>
73 * - Added ability to specify another default LDAP port to
74 * connect to. Persistent connections moved to -P
75 * 2001-05-02: Henrik Nordstrom <hno@squid-cache.org>
76 * - Support newer OpenLDAP 2.x libraries using the
77 * revised Internet Draft API which unfortunately
78 * is not backwards compatible with RFC1823..
79 * 2001-04-15: Henrik Nordstrom <hno@squid-cache.org>
80 * - Added command line option for basedn
81 * - Added the ability to search for the user DN
82 * 2001-04-16: Henrik Nordstrom <hno@squid-cache.org>
83 * - Added -D binddn -w bindpasswd.
84 * 2001-04-17: Henrik Nordstrom <hno@squid-cache.org>
85 * - Added -R to disable referrals
86 * - Added -a to control alias dereferencing
87 * 2001-04-17: Henrik Nordstrom <hno@squid-cache.org>
88 * - Added -u, DN username attribute name
89 * 2001-04-18: Henrik Nordstrom <hno@squid-cache.org>
90 * - Allow full filter specifications in -f
95 #define LDAP_DEPRECATED 1
103 #if _SQUID_WINDOWS_ && !_SQUID_CYGWIN_
104 #define snprintf _snprintf
108 #define LDAPAPI __cdecl
111 #ifndef LDAP_OPT_X_TLS
112 #define LDAP_OPT_X_TLS 0x6000
114 /* Some tricks to allow dynamic bind with ldap_start_tls_s entry point at
117 #undef ldap_start_tls_s
119 #define LDAP_START_TLS_S "ldap_start_tls_sW"
120 typedef WINLDAPAPI
ULONG(LDAPAPI
* PFldap_start_tls_s
) (IN PLDAP
, OUT PULONG
, OUT LDAPMessage
**, IN PLDAPControlW
*, IN PLDAPControlW
*);
122 #define LDAP_START_TLS_S "ldap_start_tls_sA"
123 typedef WINLDAPAPI
ULONG(LDAPAPI
* PFldap_start_tls_s
) (IN PLDAP
, OUT PULONG
, OUT LDAPMessage
**, IN PLDAPControlA
*, IN PLDAPControlA
*);
124 #endif /* LDAP_UNICODE */
125 PFldap_start_tls_s Win32_ldap_start_tls_s
;
126 #define ldap_start_tls_s(l,s,c) Win32_ldap_start_tls_s(l,NULL,NULL,s,c)
127 #endif /* LDAP_VERSION3 */
136 #define PROGRAM_NAME "basic_ldap_auth"
139 static const char *basedn
;
140 static const char *searchfilter
= NULL
;
141 static const char *binddn
= NULL
;
142 static const char *bindpasswd
= NULL
;
143 static const char *userattr
= "uid";
144 static const char *passwdattr
= NULL
;
145 static int searchscope
= LDAP_SCOPE_SUBTREE
;
146 static int persistent
= 0;
147 static int bind_once
= 0;
148 static int noreferrals
= 0;
149 static int aliasderef
= LDAP_DEREF_NEVER
;
150 #if defined(NETSCAPE_SSL)
151 static const char *sslpath
= NULL
;
152 static int sslinit
= 0;
154 static int connect_timeout
= 0;
155 static int timelimit
= LDAP_NO_LIMIT
;
157 /* Added for TLS support and version 3 */
158 static int use_tls
= 0;
159 static int version
= -1;
161 static int checkLDAP(LDAP
* ld
, const char *userid
, const char *password
, const char *server
, int port
);
162 static int readSecret(const char *filename
);
164 /* Yuck.. we need to glue to different versions of the API */
166 #ifndef LDAP_NO_ATTRS
167 #define LDAP_NO_ATTRS "1.1"
170 #if defined(LDAP_API_VERSION) && LDAP_API_VERSION > 1823
172 squid_ldap_errno(LDAP
* ld
)
175 ldap_get_option(ld
, LDAP_OPT_ERROR_NUMBER
, &err
);
179 squid_ldap_set_aliasderef(LDAP
* ld
, int deref
)
181 ldap_set_option(ld
, LDAP_OPT_DEREF
, &deref
);
184 squid_ldap_set_referrals(LDAP
* ld
, int referrals
)
186 int *value
= static_cast<int*>(referrals
? LDAP_OPT_ON
:LDAP_OPT_OFF
);
187 ldap_set_option(ld
, LDAP_OPT_REFERRALS
, value
);
190 squid_ldap_set_timelimit(LDAP
* ld
, int aTimeLimit
)
192 ldap_set_option(ld
, LDAP_OPT_TIMELIMIT
, &aTimeLimit
);
195 squid_ldap_set_connect_timeout(LDAP
* ld
, int aTimeLimit
)
197 #if defined(LDAP_OPT_NETWORK_TIMEOUT)
199 tv
.tv_sec
= aTimeLimit
;
201 ldap_set_option(ld
, LDAP_OPT_NETWORK_TIMEOUT
, &tv
);
202 #elif defined(LDAP_X_OPT_CONNECT_TIMEOUT)
204 ldap_set_option(ld
, LDAP_X_OPT_CONNECT_TIMEOUT
, &aTimeLimit
);
208 squid_ldap_memfree(char *p
)
215 squid_ldap_errno(LDAP
* ld
)
220 squid_ldap_set_aliasderef(LDAP
* ld
, int deref
)
222 ld
->ld_deref
= deref
;
225 squid_ldap_set_referrals(LDAP
* ld
, int referrals
)
228 ld
->ld_options
|= ~LDAP_OPT_REFERRALS
;
230 ld
->ld_options
&= ~LDAP_OPT_REFERRALS
;
233 squid_ldap_set_timelimit(LDAP
* ld
, int timelimit
)
235 ld
->ld_timelimit
= timelimit
;
238 squid_ldap_set_connect_timeout(LDAP
* ld
, int timelimit
)
240 fprintf(stderr
, "Connect timeouts not supported in your LDAP library\n");
243 squid_ldap_memfree(char *p
)
250 #ifdef LDAP_API_FEATURE_X_OPENLDAP
251 #if LDAP_VENDOR_VERSION > 194
252 #define HAS_URI_SUPPORT 1
257 open_ldap_connection(const char *ldapServer
, int port
)
261 if (strstr(ldapServer
, "://") != NULL
) {
262 int rc
= ldap_initialize(&ld
, ldapServer
);
263 if (rc
!= LDAP_SUCCESS
) {
264 fprintf(stderr
, "\nUnable to connect to LDAPURI:%s\n", ldapServer
);
271 if (!sslinit
&& (ldapssl_client_init(sslpath
, NULL
) != LDAP_SUCCESS
)) {
272 fprintf(stderr
, "\nUnable to initialise SSL with cert path %s\n",
278 if ((ld
= ldapssl_init(ldapServer
, port
, 1)) == NULL
) {
279 fprintf(stderr
, "\nUnable to connect to SSL LDAP server: %s port:%d\n",
285 if ((ld
= ldap_init(ldapServer
, port
)) == NULL
) {
286 fprintf(stderr
, "\nUnable to connect to LDAP server:%s port:%d\n",
291 squid_ldap_set_connect_timeout(ld
, connect_timeout
);
295 version
= LDAP_VERSION3
;
297 if (ldap_set_option(ld
, LDAP_OPT_PROTOCOL_VERSION
, &version
) != LDAP_SUCCESS
) {
298 fprintf(stderr
, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",
303 #ifdef LDAP_OPT_X_TLS
304 if (version
!= LDAP_VERSION3
) {
305 fprintf(stderr
, "TLS requires LDAP version 3\n");
307 } else if (ldap_start_tls_s(ld
, NULL
, NULL
) != LDAP_SUCCESS
) {
308 fprintf(stderr
, "Could not Activate TLS connection\n");
312 fprintf(stderr
, "TLS not supported with your LDAP library\n");
317 squid_ldap_set_timelimit(ld
, timelimit
);
318 squid_ldap_set_referrals(ld
, !noreferrals
);
319 squid_ldap_set_aliasderef(ld
, aliasderef
);
323 /* Make a sanity check on the username to reject oddly typed names */
325 validUsername(const char *user
)
327 const unsigned char *p
= (const unsigned char *) user
;
329 /* Leading whitespace? */
332 while (p
[0] && p
[1]) {
333 if (xisspace(p
[0])) {
334 /* More than one consequitive space? */
337 /* or odd space type character used? */
343 /* Trailing whitespace? */
350 main(int argc
, char **argv
)
354 char *ldapServer
= NULL
;
357 int port
= LDAP_PORT
;
359 setbuf(stdout
, NULL
);
361 while (argc
> 1 && argv
[1][0] == '-') {
362 const char *value
= "";
363 char option
= argv
[1][1];
373 if (strlen(argv
[1]) > 2) {
375 } else if (argc
> 2) {
388 fprintf(stderr
, "ERROR: Your LDAP library does not have URI support\n");
391 /* Fall thru to -h */
394 int len
= strlen(ldapServer
) + 1 + strlen(value
) + 1;
395 char *newhost
= static_cast<char*>(xmalloc(len
));
396 snprintf(newhost
, len
, "%s %s", ldapServer
, value
);
398 ldapServer
= newhost
;
400 ldapServer
= xstrdup(value
);
407 searchfilter
= value
;
416 if (strcmp(value
, "base") == 0)
417 searchscope
= LDAP_SCOPE_BASE
;
418 else if (strcmp(value
, "one") == 0)
419 searchscope
= LDAP_SCOPE_ONELEVEL
;
420 else if (strcmp(value
, "sub") == 0)
421 searchscope
= LDAP_SCOPE_SUBTREE
;
423 fprintf(stderr
, PROGRAM_NAME
": ERROR: Unknown search scope '%s'\n", value
);
428 #if defined(NETSCAPE_SSL)
430 if (port
== LDAP_PORT
)
433 fprintf(stderr
, PROGRAM_NAME
" ERROR: -E unsupported with this LDAP library\n");
438 connect_timeout
= atoi(value
);
441 timelimit
= atoi(value
);
444 if (strcmp(value
, "never") == 0)
445 aliasderef
= LDAP_DEREF_NEVER
;
446 else if (strcmp(value
, "always") == 0)
447 aliasderef
= LDAP_DEREF_ALWAYS
;
448 else if (strcmp(value
, "search") == 0)
449 aliasderef
= LDAP_DEREF_SEARCHING
;
450 else if (strcmp(value
, "find") == 0)
451 aliasderef
= LDAP_DEREF_FINDING
;
453 fprintf(stderr
, PROGRAM_NAME
": ERROR: Unknown alias dereference method '%s'\n", value
);
467 persistent
= !persistent
;
470 bind_once
= !bind_once
;
476 noreferrals
= !noreferrals
;
480 switch (atoi(value
)) {
482 version
= LDAP_VERSION2
;
485 version
= LDAP_VERSION3
;
488 fprintf(stderr
, "Protocol version should be 2 or 3\n");
493 if (version
== LDAP_VERSION2
) {
494 fprintf(stderr
, "TLS (-Z) is incompatible with version %d\n",
498 version
= LDAP_VERSION3
;
506 fprintf(stderr
, PROGRAM_NAME
": ERROR: Unknown command line option '%c'\n", option
);
512 char *value
= argv
[1];
514 int len
= strlen(ldapServer
) + 1 + strlen(value
) + 1;
515 char *newhost
= static_cast<char*>(xmalloc(len
));
516 snprintf(newhost
, len
, "%s %s", ldapServer
, value
);
518 ldapServer
= newhost
;
520 ldapServer
= xstrdup(value
);
526 ldapServer
= xstrdup("localhost");
529 fprintf(stderr
, "Usage: " PROGRAM_NAME
" -b basedn [options] [ldap_server_name[:port]]...\n\n");
530 fprintf(stderr
, "\t-b basedn (REQUIRED)\tbase dn under which to search\n");
531 fprintf(stderr
, "\t-f filter\t\tsearch filter to locate user DN\n");
532 fprintf(stderr
, "\t-u userattr\t\tusername DN attribute\n");
533 fprintf(stderr
, "\t-s base|one|sub\t\tsearch scope\n");
534 fprintf(stderr
, "\t-D binddn\t\tDN to bind as to perform searches\n");
535 fprintf(stderr
, "\t-w bindpasswd\t\tpassword for binddn\n");
536 fprintf(stderr
, "\t-W secretfile\t\tread password for binddn from file secretfile\n");
538 fprintf(stderr
, "\t-H URI\t\t\tLDAPURI (defaults to ldap://localhost)\n");
540 fprintf(stderr
, "\t-h server\t\tLDAP server (defaults to localhost)\n");
541 fprintf(stderr
, "\t-p port\t\t\tLDAP server port\n");
542 fprintf(stderr
, "\t-P\t\t\tpersistent LDAP connection\n");
543 #if defined(NETSCAPE_SSL)
544 fprintf(stderr
, "\t-E sslcertpath\t\tenable LDAP over SSL\n");
546 fprintf(stderr
, "\t-c timeout\t\tconnect timeout\n");
547 fprintf(stderr
, "\t-t timelimit\t\tsearch time limit\n");
548 fprintf(stderr
, "\t-R\t\t\tdo not follow referrals\n");
549 fprintf(stderr
, "\t-a never|always|search|find\n\t\t\t\twhen to dereference aliases\n");
551 fprintf(stderr
, "\t-v 2|3\t\t\tLDAP version\n");
552 fprintf(stderr
, "\t-Z\t\t\tTLS encrypt the LDAP connection, requires LDAP version 3\n");
554 fprintf(stderr
, "\t-d\t\t\tenable debug mode\n");
555 fprintf(stderr
, "\n");
556 fprintf(stderr
, "\tIf no search filter is specified, then the dn <userattr>=user,basedn\n\twill be used (same as specifying a search filter of '<userattr>=',\n\tbut quicker as as there is no need to search for the user DN)\n\n");
557 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");
560 /* On Windows ldap_start_tls_s is available starting from Windows XP,
561 * so we need to bind at run-time with the function entry point
566 HMODULE WLDAP32Handle
;
568 WLDAP32Handle
= GetModuleHandle("wldap32");
569 if ((Win32_ldap_start_tls_s
= (PFldap_start_tls_s
) GetProcAddress(WLDAP32Handle
, LDAP_START_TLS_S
)) == NULL
) {
570 fprintf(stderr
, PROGRAM_NAME
": ERROR: TLS (-Z) not supported on this platform.\n");
576 while (fgets(buf
, sizeof(buf
), stdin
) != NULL
) {
577 user
= strtok(buf
, " \r\n");
578 passwd
= strtok(NULL
, "\r\n");
581 printf("ERR Missing username\n");
584 if (!passwd
|| !passwd
[0]) {
585 printf("ERR Missing password '%s'\n", user
);
588 rfc1738_unescape(user
);
589 rfc1738_unescape(passwd
);
590 if (!validUsername(user
)) {
591 printf("ERR No such user '%s':'%s'\n",user
, passwd
);
594 tryagain
= (ld
!= NULL
);
596 if (ld
== NULL
&& persistent
)
597 ld
= open_ldap_connection(ldapServer
, port
);
598 if (checkLDAP(ld
, user
, passwd
, ldapServer
, port
) != 0) {
599 if (tryagain
&& squid_ldap_errno(ld
) != LDAP_INVALID_CREDENTIALS
) {
605 printf("ERR %s\n", ldap_err2string(squid_ldap_errno(ld
)));
609 if (ld
&& (squid_ldap_errno(ld
) != LDAP_SUCCESS
&& squid_ldap_errno(ld
) != LDAP_INVALID_CREDENTIALS
)) {
620 ldap_escape_value(char *escaped
, int size
, const char *src
)
623 while (size
> 4 && *src
) {
634 snprintf(escaped
, 3, "%02x", (unsigned char) *src
);
651 /* Check the userid & password.
652 * Return 0 on success, 1 on failure
655 checkLDAP(LDAP
* persistent_ld
, const char *userid
, const char *password
, const char *ldapServer
, int port
)
659 LDAP
*bind_ld
= NULL
;
662 /* LDAP can't bind with a blank password. Seen as "anonymous"
663 * and always granted access
665 debug("Blank password given\n");
670 char escaped_login
[1024];
671 LDAPMessage
*res
= NULL
;
673 char *searchattr
[] = {(char *)LDAP_NO_ATTRS
, NULL
};
676 LDAP
*search_ld
= persistent_ld
;
679 search_ld
= open_ldap_connection(ldapServer
, port
);
681 ldap_escape_value(escaped_login
, sizeof(escaped_login
), userid
);
683 rc
= ldap_simple_bind_s(search_ld
, binddn
, bindpasswd
);
684 if (rc
!= LDAP_SUCCESS
) {
685 fprintf(stderr
, PROGRAM_NAME
": WARNING, could not bind to binddn '%s'\n", ldap_err2string(rc
));
690 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
);
691 debug("user filter '%s', searchbase '%s'\n", filter
, basedn
);
692 rc
= ldap_search_s(search_ld
, basedn
, searchscope
, filter
, searchattr
, 1, &res
);
693 if (rc
!= LDAP_SUCCESS
) {
694 if (noreferrals
&& rc
== LDAP_PARTIAL_RESULTS
) {
695 /* Everything is fine. This is expected when referrals
698 debug("noreferrals && rc == LDAP_PARTIAL_RESULTS\n");
700 fprintf(stderr
, PROGRAM_NAME
": WARNING, LDAP search error '%s'\n", ldap_err2string(rc
));
701 #if defined(NETSCAPE_SSL)
702 if (sslpath
&& ((rc
== LDAP_SERVER_DOWN
) || (rc
== LDAP_CONNECT_ERROR
))) {
703 int sslerr
= PORT_GetError();
704 fprintf(stderr
, PROGRAM_NAME
": WARNING, SSL error %d (%s)\n", sslerr
, ldapssl_err2string(sslerr
));
711 entry
= ldap_first_entry(search_ld
, res
);
713 debug("Ldap search returned nothing\n");
717 userdn
= ldap_get_dn(search_ld
, entry
);
719 fprintf(stderr
, PROGRAM_NAME
": ERROR, could not get user DN for '%s'\n", userid
);
723 snprintf(dn
, sizeof(dn
), "%s", userdn
);
724 squid_ldap_memfree(userdn
);
726 if (ret
== 0 && (!binddn
|| !bind_once
|| passwdattr
)) {
727 /* Reuse the search connection for comparing the user password attribute */
736 if (search_ld
&& search_ld
!= persistent_ld
) {
737 ldap_unbind(search_ld
);
743 snprintf(dn
, sizeof(dn
), "%s=%s,%s", userattr
, userid
, basedn
);
746 debug("attempting to authenticate user '%s'\n", dn
);
747 if (!bind_ld
&& !bind_once
)
748 bind_ld
= persistent_ld
;
750 bind_ld
= open_ldap_connection(ldapServer
, port
);
752 if (ldap_compare_s(bind_ld
, dn
, passwdattr
, password
) != LDAP_COMPARE_TRUE
) {
755 } else if (ldap_simple_bind_s(bind_ld
, dn
, password
) != LDAP_SUCCESS
)
757 if (bind_ld
!= persistent_ld
) {
758 ldap_unbind(bind_ld
);
765 readSecret(const char *filename
)
772 if (!(f
= fopen(filename
, "r"))) {
773 fprintf(stderr
, PROGRAM_NAME
" ERROR: Can not read secret file %s\n", filename
);
776 if (!fgets(buf
, sizeof(buf
) - 1, f
)) {
777 fprintf(stderr
, PROGRAM_NAME
" ERROR: Secret file %s is empty\n", filename
);
781 /* strip whitespaces on end */
782 if ((e
= strrchr(buf
, '\n')))
784 if ((e
= strrchr(buf
, '\r')))
787 passwd
= (char *) calloc(sizeof(char), strlen(buf
) + 1);
789 fprintf(stderr
, PROGRAM_NAME
" ERROR: can not allocate memory\n");