2 * ext_ldap_group_acl: lookup group membership in LDAP
6 * (C)2002,2003 MARA Systems AB
8 * License: squid_ldap_group is free software; you can redistribute it
9 * and/or modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2,
11 * or (at your option) any later version.
14 * Flavio Pescuma <flavio@marasystems.com>
15 * Henrik Nordstrom <hno@marasystems.com>
16 * MARA Systems AB, Sweden <http://www.marasystems.com>
18 * With contributions from others mentioned in the ChangeLog file
20 * In part based on squid_ldap_auth by Glen Newton and Henrik Nordstrom.
22 * Latest version of this program can always be found from MARA Systems
23 * at http://marasystems.com/download/LDAP_Group/
25 * Dependencies: You need to get the OpenLDAP libraries
26 * from http://www.openldap.org or use another compatible
29 * If you want to make a TLS enabled connection you will also need the
30 * OpenSSL libraries linked into openldap. See http://www.openssl.org/
32 * License: squid_ldap_group is free software; you can redistribute it
33 * and/or modify it under the terms of the GNU General Public License
34 * as published by the Free Software Foundation; either version 2,
35 * or (at your option) any later version.
38 #include "helpers/defines.h"
42 #define LDAP_DEPRECATED 1
54 #if _SQUID_MSWIN_ /* Native Windows port and MinGW */
56 #define snprintf _snprintf
60 #define LDAPAPI __cdecl
63 #ifndef LDAP_OPT_X_TLS
64 #define LDAP_OPT_X_TLS 0x6000
66 /* Some tricks to allow dynamic bind with ldap_start_tls_s entry point at
69 #undef ldap_start_tls_s
71 #define LDAP_START_TLS_S "ldap_start_tls_sW"
72 typedef WINLDAPAPI
ULONG(LDAPAPI
* PFldap_start_tls_s
) (IN PLDAP
, OUT PULONG
, OUT LDAPMessage
**, IN PLDAPControlW
*, IN PLDAPControlW
*);
74 #define LDAP_START_TLS_S "ldap_start_tls_sA"
75 typedef WINLDAPAPI
ULONG(LDAPAPI
* PFldap_start_tls_s
) (IN PLDAP
, OUT PULONG
, OUT LDAPMessage
**, IN PLDAPControlA
*, IN PLDAPControlA
*);
76 #endif /* LDAP_UNICODE */
77 PFldap_start_tls_s Win32_ldap_start_tls_s
;
78 #define ldap_start_tls_s(l,s,c) Win32_ldap_start_tls_s(l,NULL,NULL,s,c)
79 #endif /* LDAP_VERSION3 */
92 #if defined(LDAP_OPT_NETWORK_TIMEOUT)
96 #define PROGRAM_NAME "ext_ldap_group_acl"
97 #define PROGRAM_VERSION "2.17"
101 static const char *basedn
= NULL
;
102 static const char *searchfilter
= NULL
;
103 static const char *userbasedn
= NULL
;
104 static const char *userdnattr
= NULL
;
105 static const char *usersearchfilter
= NULL
;
106 static const char *binddn
= NULL
;
107 static const char *bindpasswd
= NULL
;
108 static int searchscope
= LDAP_SCOPE_SUBTREE
;
109 static int persistent
= 0;
110 static int noreferrals
= 0;
111 static int aliasderef
= LDAP_DEREF_NEVER
;
112 #if defined(NETSCAPE_SSL)
113 static char *sslpath
= NULL
;
114 static int sslinit
= 0;
116 static int connect_timeout
= 0;
117 static int timelimit
= LDAP_NO_LIMIT
;
120 /* Added for TLS support and version 3 */
121 static int use_tls
= 0;
122 static int version
= -1;
125 static int searchLDAP(LDAP
* ld
, char *group
, char *user
, char *extension_dn
);
127 static int readSecret(const char *filename
);
129 /* Yuck.. we need to glue to different versions of the API */
131 #ifndef LDAP_NO_ATTRS
132 #define LDAP_NO_ATTRS "1.1"
135 #if defined(LDAP_API_VERSION) && LDAP_API_VERSION > 1823
137 squid_ldap_errno(LDAP
* ld
)
140 ldap_get_option(ld
, LDAP_OPT_ERROR_NUMBER
, &err
);
144 squid_ldap_set_aliasderef(LDAP
* ld
, int deref
)
146 ldap_set_option(ld
, LDAP_OPT_DEREF
, &deref
);
149 squid_ldap_set_referrals(LDAP
* ld
, int referrals
)
151 int *value
= static_cast<int*>(referrals
? LDAP_OPT_ON
:LDAP_OPT_OFF
);
152 ldap_set_option(ld
, LDAP_OPT_REFERRALS
, value
);
155 squid_ldap_set_timelimit(LDAP
* ld
, int aTimeLimit
)
157 ldap_set_option(ld
, LDAP_OPT_TIMELIMIT
, &aTimeLimit
);
160 squid_ldap_set_connect_timeout(LDAP
* ld
, int aTimeLimit
)
162 #if defined(LDAP_OPT_NETWORK_TIMEOUT)
164 tv
.tv_sec
= aTimeLimit
;
166 ldap_set_option(ld
, LDAP_OPT_NETWORK_TIMEOUT
, &tv
);
167 #elif defined(LDAP_X_OPT_CONNECT_TIMEOUT)
169 ldap_set_option(ld
, LDAP_X_OPT_CONNECT_TIMEOUT
, &aTimeLimit
);
173 squid_ldap_memfree(char *p
)
180 squid_ldap_errno(LDAP
* ld
)
185 squid_ldap_set_aliasderef(LDAP
* ld
, int deref
)
187 ld
->ld_deref
= deref
;
190 squid_ldap_set_referrals(LDAP
* ld
, int referrals
)
193 ld
->ld_options
|= ~LDAP_OPT_REFERRALS
;
195 ld
->ld_options
&= ~LDAP_OPT_REFERRALS
;
198 squid_ldap_set_timelimit(LDAP
* ld
, int timelimit
)
200 ld
->ld_timelimit
= timelimit
;
203 squid_ldap_set_connect_timeout(LDAP
* ld
, int timelimit
)
205 fprintf(stderr
, "WARNING: Connect timeouts not supported in your LDAP library\n");
208 squid_ldap_memfree(char *p
)
215 #ifdef LDAP_API_FEATURE_X_OPENLDAP
216 #if LDAP_VENDOR_VERSION > 194
217 #define HAS_URI_SUPPORT 1
222 main(int argc
, char **argv
)
224 char buf
[HELPER_INPUT_BUFFER
];
225 char *user
, *group
, *extension_dn
= NULL
;
226 char *ldapServer
= NULL
;
228 int tryagain
= 0, rc
;
229 int port
= LDAP_PORT
;
230 int use_extension_dn
= 0;
231 int strip_nt_domain
= 0;
232 int strip_kerberos_realm
= 0;
234 setbuf(stdout
, NULL
);
236 while (argc
> 1 && argv
[1][0] == '-') {
237 const char *value
= "";
238 char option
= argv
[1][1];
250 if (strlen(argv
[1]) > 2) {
252 } else if (argc
> 2) {
265 fprintf(stderr
, "FATAL: Your LDAP library does not have URI support\n");
268 /* Fall thru to -h */
271 int len
= strlen(ldapServer
) + 1 + strlen(value
) + 1;
272 char *newhost
= (char*)malloc(len
);
273 snprintf(newhost
, len
, "%s %s", ldapServer
, value
);
275 ldapServer
= newhost
;
277 ldapServer
= xstrdup(value
);
284 searchfilter
= value
;
290 usersearchfilter
= value
;
296 if (strcmp(value
, "base") == 0)
297 searchscope
= LDAP_SCOPE_BASE
;
298 else if (strcmp(value
, "one") == 0)
299 searchscope
= LDAP_SCOPE_ONELEVEL
;
300 else if (strcmp(value
, "sub") == 0)
301 searchscope
= LDAP_SCOPE_SUBTREE
;
303 fprintf(stderr
, PROGRAM_NAME
": FATAL: Unknown search scope '%s'\n", value
);
308 #if defined(NETSCAPE_SSL)
310 if (port
== LDAP_PORT
)
313 fprintf(stderr
, PROGRAM_NAME
": FATAL: -E unsupported with this LDAP library\n");
318 connect_timeout
= atoi(value
);
321 timelimit
= atoi(value
);
324 if (strcmp(value
, "never") == 0)
325 aliasderef
= LDAP_DEREF_NEVER
;
326 else if (strcmp(value
, "always") == 0)
327 aliasderef
= LDAP_DEREF_ALWAYS
;
328 else if (strcmp(value
, "search") == 0)
329 aliasderef
= LDAP_DEREF_SEARCHING
;
330 else if (strcmp(value
, "find") == 0)
331 aliasderef
= LDAP_DEREF_FINDING
;
333 fprintf(stderr
, PROGRAM_NAME
": FATAL: Unknown alias dereference method '%s'\n", value
);
347 persistent
= !persistent
;
353 noreferrals
= !noreferrals
;
357 switch (atoi(value
)) {
359 version
= LDAP_VERSION2
;
362 version
= LDAP_VERSION3
;
365 fprintf(stderr
, "FATAL: Protocol version should be 2 or 3\n");
370 if (version
== LDAP_VERSION2
) {
371 fprintf(stderr
, "FATAL: TLS (-Z) is incompatible with version %d\n",
375 version
= LDAP_VERSION3
;
383 use_extension_dn
= 1;
389 strip_kerberos_realm
= 1;
392 fprintf(stderr
, PROGRAM_NAME
": FATAL: Unknown command line option '%c'\n", option
);
398 char *value
= argv
[1];
400 int len
= strlen(ldapServer
) + 1 + strlen(value
) + 1;
401 char *newhost
= (char*)malloc(len
);
402 snprintf(newhost
, len
, "%s %s", ldapServer
, value
);
404 ldapServer
= newhost
;
406 ldapServer
= xstrdup(value
);
413 ldapServer
= (char *) "localhost";
415 if (!basedn
|| !searchfilter
) {
416 fprintf(stderr
, "\n" PROGRAM_NAME
" version " PROGRAM_VERSION
"\n\n");
417 fprintf(stderr
, "Usage: " PROGRAM_NAME
" -b basedn -f filter [options] ldap_server_name\n\n");
418 fprintf(stderr
, "\t-b basedn (REQUIRED)\tbase dn under where to search for groups\n");
419 fprintf(stderr
, "\t-f filter (REQUIRED)\tgroup search filter pattern. %%u = user,\n\t\t\t\t%%v = group\n");
420 fprintf(stderr
, "\t-B basedn (REQUIRED)\tbase dn under where to search for users\n");
421 fprintf(stderr
, "\t-F filter (REQUIRED)\tuser search filter pattern. %%s = login\n");
422 fprintf(stderr
, "\t-s base|one|sub\t\tsearch scope\n");
423 fprintf(stderr
, "\t-D binddn\t\tDN to bind as to perform searches\n");
424 fprintf(stderr
, "\t-w bindpasswd\t\tpassword for binddn\n");
425 fprintf(stderr
, "\t-W secretfile\t\tread password for binddn from file secretfile\n");
427 fprintf(stderr
, "\t-H URI\t\t\tLDAPURI (defaults to ldap://localhost)\n");
429 fprintf(stderr
, "\t-h server\t\tLDAP server (defaults to localhost)\n");
430 fprintf(stderr
, "\t-p port\t\t\tLDAP server port (defaults to %d)\n", LDAP_PORT
);
431 fprintf(stderr
, "\t-P\t\t\tpersistent LDAP connection\n");
432 #if defined(NETSCAPE_SSL)
433 fprintf(stderr
, "\t-E sslcertpath\t\tenable LDAP over SSL\n");
435 fprintf(stderr
, "\t-c timeout\t\tconnect timeout\n");
436 fprintf(stderr
, "\t-t timelimit\t\tsearch time limit\n");
437 fprintf(stderr
, "\t-R\t\t\tdo not follow referrals\n");
438 fprintf(stderr
, "\t-a never|always|search|find\n\t\t\t\twhen to dereference aliases\n");
440 fprintf(stderr
, "\t-v 2|3\t\t\tLDAP version\n");
441 fprintf(stderr
, "\t-Z\t\t\tTLS encrypt the LDAP connection, requires\n\t\t\t\tLDAP version 3\n");
443 fprintf(stderr
, "\t-g\t\t\tfirst query parameter is base DN extension\n\t\t\t\tfor this query\n");
444 fprintf(stderr
, "\t-S\t\t\tStrip NT domain from usernames\n");
445 fprintf(stderr
, "\t-K\t\t\tStrip Kerberos realm from usernames\n");
446 fprintf(stderr
, "\t-d\t\t\tenable debug mode\n");
447 fprintf(stderr
, "\n");
448 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");
451 /* On Windows ldap_start_tls_s is available starting from Windows XP,
452 * so we need to bind at run-time with the function entry point
457 HMODULE WLDAP32Handle
;
459 WLDAP32Handle
= GetModuleHandle("wldap32");
460 if ((Win32_ldap_start_tls_s
= (PFldap_start_tls_s
) GetProcAddress(WLDAP32Handle
, LDAP_START_TLS_S
)) == NULL
) {
461 fprintf(stderr
, PROGRAM_NAME
": FATAL: TLS (-Z) not supported on this platform.\n");
467 while (fgets(buf
, HELPER_INPUT_BUFFER
, stdin
) != NULL
) {
469 if (!strchr(buf
, '\n')) {
470 /* too large message received.. skip and deny */
471 fprintf(stderr
, "%s: ERROR: Input Too large: %s\n", argv
[0], buf
);
472 while (fgets(buf
, sizeof(buf
), stdin
)) {
473 fprintf(stderr
, "%s: ERROR: Input Too large..: %s\n", argv
[0], buf
);
474 if (strchr(buf
, '\n') != NULL
)
480 user
= strtok(buf
, " \n");
482 debug("%s: Invalid request: No Username given\n", argv
[0]);
483 SEND_ERR("Invalid request. No Username");
486 rfc1738_unescape(user
);
487 if (strip_nt_domain
) {
488 char *u
= strrchr(user
, '\\');
490 u
= strrchr(user
, '/');
492 u
= strrchr(user
, '+');
496 if (strip_kerberos_realm
) {
497 char *u
= strchr(user
, '@');
502 if (use_extension_dn
) {
503 extension_dn
= strtok(NULL
, " \n");
505 debug("%s: Invalid request: Extension DN configured, but none sent.\n", argv
[0]);
506 SEND_ERR("Invalid Request. Extension DN required.");
509 rfc1738_unescape(extension_dn
);
511 while (!found
&& user
&& (group
= strtok(NULL
, " \n")) != NULL
) {
512 rfc1738_unescape(group
);
517 if (strstr(ldapServer
, "://") != NULL
) {
518 rc
= ldap_initialize(&ld
, ldapServer
);
519 if (rc
!= LDAP_SUCCESS
) {
520 fprintf(stderr
, "%s: ERROR: Unable to connect to LDAPURI:%s\n", argv
[0], ldapServer
);
527 if (!sslinit
&& (ldapssl_client_init(sslpath
, NULL
) != LDAP_SUCCESS
)) {
528 fprintf(stderr
, "FATAL: Unable to initialise SSL with cert path %s\n", sslpath
);
533 if ((ld
= ldapssl_init(ldapServer
, port
, 1)) == NULL
) {
534 fprintf(stderr
, "FATAL: Unable to connect to SSL LDAP server: %s port:%d\n",
540 if ((ld
= ldap_init(ldapServer
, port
)) == NULL
) {
541 fprintf(stderr
, "ERROR: Unable to connect to LDAP server:%s port:%d\n", ldapServer
, port
);
545 squid_ldap_set_connect_timeout(ld
, connect_timeout
);
549 version
= LDAP_VERSION3
;
551 if (ldap_set_option(ld
, LDAP_OPT_PROTOCOL_VERSION
, &version
) != LDAP_SUCCESS
) {
552 fprintf(stderr
, "ERROR: Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",
559 #ifdef LDAP_OPT_X_TLS
560 if (version
!= LDAP_VERSION3
) {
561 fprintf(stderr
, "FATAL: TLS requires LDAP version 3\n");
563 } else if (ldap_start_tls_s(ld
, NULL
, NULL
) != LDAP_SUCCESS
) {
564 fprintf(stderr
, "ERROR: Could not Activate TLS connection\n");
570 fprintf(stderr
, "FATAL: TLS not supported with your LDAP library\n");
575 squid_ldap_set_timelimit(ld
, timelimit
);
576 squid_ldap_set_referrals(ld
, !noreferrals
);
577 squid_ldap_set_aliasderef(ld
, aliasderef
);
578 if (binddn
&& bindpasswd
&& *binddn
&& *bindpasswd
) {
579 rc
= ldap_simple_bind_s(ld
, binddn
, bindpasswd
);
580 if (rc
!= LDAP_SUCCESS
) {
581 fprintf(stderr
, PROGRAM_NAME
": WARNING: could not bind to binddn '%s'\n", ldap_err2string(rc
));
587 debug("Connected OK\n");
589 if (searchLDAP(ld
, group
, user
, extension_dn
) == 0) {
608 if (!persistent
|| (squid_ldap_errno(ld
) != LDAP_SUCCESS
&& squid_ldap_errno(ld
) != LDAP_INVALID_CREDENTIALS
)) {
622 ldap_escape_value(char *escaped
, int size
, const char *src
)
625 while (size
> 4 && *src
) {
635 snprintf(escaped
, 3, "%02x", (unsigned char) *src
++);
650 build_filter(char *filter
, int size
, const char *templ
, const char *user
, const char *group
)
653 while (*templ
&& size
> 0) {
661 n
= ldap_escape_value(filter
, size
, user
);
668 n
= ldap_escape_value(filter
, size
, group
);
673 fprintf(stderr
, "ERROR: Unknown filter template string %%%c\n", *templ
);
681 *filter
++ = *templ
++;
686 *filter
++ = *templ
++;
692 fprintf(stderr
, "ERROR: Filter too large\n");
700 searchLDAPGroup(LDAP
* ld
, char *group
, char *member
, char *extension_dn
)
703 static char searchbase
[256];
704 LDAPMessage
*res
= NULL
;
707 char *searchattr
[] = {(char *) LDAP_NO_ATTRS
, NULL
};
709 if (extension_dn
&& *extension_dn
)
710 snprintf(searchbase
, sizeof(searchbase
), "%s,%s", extension_dn
, basedn
);
712 snprintf(searchbase
, sizeof(searchbase
), "%s", basedn
);
714 if (build_filter(filter
, sizeof(filter
), searchfilter
, member
, group
) != 0) {
715 fprintf(stderr
, PROGRAM_NAME
": ERROR: Failed to construct LDAP search filter. filter=\"%s\", user=\"%s\", group=\"%s\"\n", filter
, member
, group
);
718 debug("group filter '%s', searchbase '%s'\n", filter
, searchbase
);
720 rc
= ldap_search_s(ld
, searchbase
, searchscope
, filter
, searchattr
, 1, &res
);
721 if (rc
!= LDAP_SUCCESS
) {
722 if (noreferrals
&& rc
== LDAP_PARTIAL_RESULTS
) {
723 /* Everything is fine. This is expected when referrals
727 fprintf(stderr
, PROGRAM_NAME
": WARNING: LDAP search error '%s'\n", ldap_err2string(rc
));
728 #if defined(NETSCAPE_SSL)
729 if (sslpath
&& ((rc
== LDAP_SERVER_DOWN
) || (rc
== LDAP_CONNECT_ERROR
))) {
730 int sslerr
= PORT_GetError();
731 fprintf(stderr
, PROGRAM_NAME
": WARNING: SSL error %d (%s)\n", sslerr
, ldapssl_err2string(sslerr
));
738 entry
= ldap_first_entry(ld
, res
);
748 searchLDAP(LDAP
* ld
, char *group
, char *login
, char *extension_dn
)
751 if (usersearchfilter
) {
753 char searchbase
[8192];
754 char escaped_login
[1024];
755 LDAPMessage
*res
= NULL
;
759 char *searchattr
[] = {(char *) LDAP_NO_ATTRS
, NULL
};
760 if (extension_dn
&& *extension_dn
)
761 snprintf(searchbase
, sizeof(searchbase
), "%s,%s", extension_dn
, userbasedn
? userbasedn
: basedn
);
763 snprintf(searchbase
, sizeof(searchbase
), "%s", userbasedn
? userbasedn
: basedn
);
764 ldap_escape_value(escaped_login
, sizeof(escaped_login
), login
);
765 snprintf(filter
, sizeof(filter
), usersearchfilter
, 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
, escaped_login
);
766 debug("user filter '%s', searchbase '%s'\n", filter
, searchbase
);
767 rc
= ldap_search_s(ld
, searchbase
, searchscope
, filter
, searchattr
, 1, &res
);
768 if (rc
!= LDAP_SUCCESS
) {
769 if (noreferrals
&& rc
== LDAP_PARTIAL_RESULTS
) {
770 /* Everything is fine. This is expected when referrals
774 fprintf(stderr
, PROGRAM_NAME
": WARNING: LDAP search error '%s'\n", ldap_err2string(rc
));
775 #if defined(NETSCAPE_SSL)
776 if (sslpath
&& ((rc
== LDAP_SERVER_DOWN
) || (rc
== LDAP_CONNECT_ERROR
))) {
777 int sslerr
= PORT_GetError();
778 fprintf(stderr
, PROGRAM_NAME
": WARNING: SSL error %d (%s)\n", sslerr
, ldapssl_err2string(sslerr
));
785 entry
= ldap_first_entry(ld
, res
);
787 fprintf(stderr
, PROGRAM_NAME
": WARNING: User '%s' not found in '%s'\n", login
, searchbase
);
791 userdn
= ldap_get_dn(ld
, entry
);
792 rc
= searchLDAPGroup(ld
, group
, userdn
, extension_dn
);
793 squid_ldap_memfree(userdn
);
796 } else if (userdnattr
) {
798 if (extension_dn
&& *extension_dn
)
799 snprintf(dn
, 8192, "%s=%s, %s, %s", userdnattr
, login
, extension_dn
, userbasedn
? userbasedn
: basedn
);
801 snprintf(dn
, 8192, "%s=%s, %s", userdnattr
, login
, userbasedn
? userbasedn
: basedn
);
802 return searchLDAPGroup(ld
, group
, dn
, extension_dn
);
804 return searchLDAPGroup(ld
, group
, login
, extension_dn
);
810 readSecret(const char *filename
)
816 if (!(f
= fopen(filename
, "r"))) {
817 fprintf(stderr
, PROGRAM_NAME
": ERROR: Can not read secret file %s\n", filename
);
820 if (!fgets(buf
, sizeof(buf
) - 1, f
)) {
821 fprintf(stderr
, PROGRAM_NAME
": ERROR: Secret file %s is empty\n", filename
);
825 /* strip whitespaces on end */
826 if ((e
= strrchr(buf
, '\n')))
828 if ((e
= strrchr(buf
, '\r')))
831 bindpasswd
= xstrdup(buf
);
833 fprintf(stderr
, PROGRAM_NAME
": ERROR: can not allocate memory\n");