1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens, Robert Hinn
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: ldap.pike,v 1.15 2010/08/18 20:32:45 astra Exp $
19 inherit "/kernel/module";
20 #include <configure.h>
23 #include <attributes.h>
27 //! This module is a ldap client inside sTeam. It reads configuration
28 //! parameters of sTeam to contact a ldap server.
29 //! All the user management can be done with ldap this way. Some
30 //! special scenario might require to modify the modules code to
33 //! The configuration variables used are:
34 //! server - the server name to connect to (if none is specified, then ldap will not be used)
35 //! cacheTime - how long (in seconds) shall ldap entries be cached? (to reduce server requests)
36 //! reuseConnection - "true" if you want to keep a single ldap connection open and reuse it, "false" if you want to close the connection after each request
37 //! restrictAccess - "true" if the ldap module shall only allow certain modules
38 //! (persistence:ldap and auth) to call it (this is a privacy feature that
39 //! prevents users to query ldap data of other users)
40 //! user - ldap user for logging in
41 //! password - the password for the user
42 //! base_dc - ldap base dc, consult ldap documentation
43 //! userdn - the dn path where new users are stored
44 //! groupdn - the dn path where new groups are stored
45 //! objectName - the ldap object name to be used for the search
46 //! userAttr - the attribute containing the user's login name
47 //! passwordAttr - the attribute containing the password
48 //! passwordPrefix - a string to put before the password from passwordAttr, e.g. {sha}, {crypt}, {lm}, $1$
49 //! emailAttr - the attribute containing the user's email address
50 //! iconAttr - the attribute containing the user's icon
51 //! fullnameAttr - the attribute containing the user's surname
52 //! nameAttr - the attribute containing the user's first name
53 //! userClass - an attribute value that identifies users
54 //! userId - an attribute that contains a value that can be used to match users to groups
55 //! userAttributes - a (comma-separated) list of ldap attributes that should be copied as attributes to the user object (the steam attributes will be called the same as the ldap attributes)
56 //! groupAttr - the attribute (or comma-separated attributes) containing the group's name (must match the entries in groupClass)
57 //! groupParentAttr - an attribute specifying an ldap attribute that contains the name of the parent group for a group (Note: this is only a workaround for two-level hierarchies in certain settings. Real group hierarchies in ldap will be implemented later!)
58 //! groupClass - an attribute value (or comma-separated list of attribute values) that identifies groups (must match the entries in groupAttr)
59 //! groupId - an attribute that contains a value that can be used to match users to groups
60 //! subgroupMethod - determines how group hierarchies are done in ldap:
61 //! "structural" : subgroups are children in the ldap tree and the
62 //! parent group thus appears in their dn
63 //! "attribute" : a single attribute specifies the name of the
64 //! parent group (which doesn't have to exist in ldap),
65 //! this method is considered deprecated and only
66 //! allows one level group hierarchies
67 //! subgroupIgnore - (may be specified multiple times) identifies dn parts
68 //! that should not be considered parent groups if they appear
69 //! in the dn of a group
70 //! memberAttr - an attribute that contains a value that can be used to match users to groups
71 //! groupAttributes - a (comma-separated) list of ldap attributes that should be copied as attributes to the group object (the steam attributes will be called the same as the ldap attributes)
72 //! descriptionAttr - the attribute that contains a user or group description
73 //! notfound - defines what should be done if a user or group could not be found in LDAP:
74 //! "create" : create and insert a new user in LDAP (at userdn)
75 //! "ignore" : do nothing
76 //! sync - "true"/"false" : sync user or group data (false: read-only LDAP access)
77 //! bindUser - "true" : when authorizing users, try ldap bind first (you will need this if the ldap lookup doesn't return a password for the user)
78 //! bindUserWithoutDN - "false" : when binding users, only use the user name instead of the dn
79 //! prefetchUsers : set to true to prefetch all users from ldap
80 //! updateCacheInterval: interval in hours to update cache, set cacheTime to 0
81 //! updateCacheStartHour: start hour for update as a string, e.g. 4 for 4am
82 //! requiredAttr : required attributes for creating new users in LDAP
83 //! adminAccount - "root" : sTeam account that will receive administrative mails about ldap
84 //! charset - "utf-8" : charset used in ldap server
85 //! suspendAttr - an ldap attribute that is used to suspend (lock out) users
86 //! suspendAttrValue - the value (or values, separated by commas) of the suspendAttr ldap attribute. If the attribute has one of these values, the user will be suspended
87 //! logAuthorization - switch on logging of authorization attempts by default,
88 //! can be any combination of the following (separated by
90 //! "failure" : log failed authorization attempts (and
91 //! the first subsequent successful attempt)
92 //! "success" : log successful authorization attempts
93 //! "details" : output more details (crc checksums of
95 class ldap : public module{
105 #define LDAP_LOG(s, args...) werror("ldap: "+s+"\n", args)
107 #define LDAP_LOG(s, args...)
110 #define LOG_AUTH(s, args...) werror("ldap-auth (%s): "+s+"\n", Calendar.Second(time())->format_time(), args)
112 #define LOG_TIME( start_time, msg, args... ) { int tmp_diff_time = get_time_millis() - start_time; if ( tmp_diff_time >= log_connection_times_min && tmp_diff_time <= log_connection_times_max ) MESSAGE( "LDAP took %d milliseconds: " + msg, tmp_diff_time, args ); }
118 mapping config = ([ ]);
120 object charset_decoder;
121 object charset_encoder;
125 object authorize_cache;
127 Thread.Local single_ldap;
129 string last_bind_password_hash;
130 int reconnection_time = 30;
132 bool restrict_access = true;
133 array valid_caller_objects = ({ });
134 array valid_caller_programs = ({ });
136 int log_connection_times_min = Int.NATIVE_MAX;
137 int log_connection_times_max = 0;
139 int log_level_auth = 0;
140 constant LOG_AUTH_FAILURE = 1;
141 constant LOG_AUTH_SUCCESS = 2;
142 constant LOG_AUTH_DETAILS = 4;
143 mapping log_failed_auth = ([ ]);
145 object admin_account = 0;
146 array ldap_conflicts = ({ });
149 constant NO_ERROR = 0;
150 constant ERROR_UNKNOWN = -1;
151 constant ERROR_NOT_CONNECTED = -2;
153 Thread.Queue invalidate_queue = Thread.Queue();
155 string get_identifier() { return "ldap"; }
160 mapping by_identifier = ([ ]);
163 function last_modified_func;
164 function multi_sync_func;
166 object create ( int cache_time_seconds, function synchronize_function,
167 function|void last_modified_function,
168 function|void multi_synchronize_function ) {
169 if ( !functionp(synchronize_function) )
170 FATAL( "ldap: no synchronize function specified on cache creation" );
171 sync_func = synchronize_function;
172 multi_sync_func = multi_synchronize_function;
173 last_modified_func = last_modified_function;
174 cache_time = cache_time_seconds;
179 return indices( by_identifier );
182 mapping find ( string identifier ) {
184 return by_identifier[ identifier ];
188 void update_cache() {
189 while (invalidate_queue->size() > 0) {
190 mapping update_entry = invalidate_queue->read();
191 LDAP_LOG("Updating cached entry of %O", update_entry);
192 by_identifier[update_entry[config->userAttr]] = update_entry;
198 mixed fetch ( string identifier, mixed ... args ) {
200 if ( !stringp(identifier) ) return 0;
202 // first try to update the cache if an update thread is running
205 // check whether object is already cached:
206 mapping cache_entry = by_identifier[ identifier ];
207 if ( mappingp(cache_entry) ) {
208 if ( cache_time > 0 &&
209 time() - cache_entry->last_synchronized >= cache_time )
212 if ( !functionp(last_modified_func) ||
213 (cache_entry->last_modified == 0) ||
214 ((last_modified = last_modified_func( identifier, @args )) >
215 cache_entry->last_modified) ) {
216 mixed tmp_data = sync_func( identifier, @args );
217 if ( !tmp_data ) return 0; // only cache values != 0
218 if ( stringp(tmp_data) && sizeof(tmp_data) < 1 ) return 0;
219 cache_entry->data = tmp_data;
220 if ( last_modified ) cache_entry->last_modified = last_modified;
221 cache_entry->last_synchronized = time();
224 cache_entry->last_accessed = time();
225 return cache_entry->data;
227 // need to add object to cache:
229 cache_entry->data = sync_func( identifier, @args );
230 if ( !cache_entry->data ) return 0; // only cache values != 0
231 if ( stringp(cache_entry->data) && sizeof(cache_entry->data)<1 ) return 0;
232 cache_entry->identifier = identifier;
234 by_identifier[ identifier ] = cache_entry;
236 if ( functionp(last_modified_func) )
237 cache_entry->last_modified = last_modified_func( identifier );
239 cache_entry->last_synchronized = time();
240 cache_entry->last_accessed = time();
242 return cache_entry->data;
247 array userdata = multi_sync_func();
248 foreach(userdata, mapping data) {
249 mapping cache_entry = ([ ]);
252 if (sizeof(data) == 0) {
256 data = fix_charset( data );
257 identifier = data[config->userAttr];
258 LDAP_LOG("pre_fetch: %s %O (%d rows)",identifier,data->cn,sizeof(data));
259 cache_entry->data = data;
260 cache_entry->identifier = identifier;
261 by_identifier[identifier] = cache_entry;
263 if ( functionp(last_modified_func) )
264 cache_entry->last_modified = last_modified_func( identifier );
265 cache_entry->last_synchronized = time();
266 cache_entry->last_accessed = time();
270 int drop ( void|string identifier ) {
271 if ( !has_index( by_identifier, identifier ) )
273 m_delete( by_identifier, identifier );
278 return sizeof(by_identifier);
282 bool ldap_activated ()
284 array servers = Config.array_value( config["server"] );
285 if ( arrayp(servers) ) {
286 if ( sizeof(servers) < 1 ) return false;
287 foreach ( servers, mixed server )
288 if ( stringp(server) && sizeof(server) > 0 ) return true;
295 int get_last_error () {
300 string make_dn ( mixed ... parts ) {
302 foreach ( parts, mixed part ) {
303 if ( stringp(part) && sizeof(part) > 0 ) {
304 if ( sizeof(dn) > 0 && dn[-1] != ',' ) dn += ",";
312 mapping get_group_spec () {
313 array groupAttrs = Config.array_value( config->groupAttr );
314 array groupClasses = Config.array_value( config->groupClass );
315 if ( !arrayp(groupAttrs) || sizeof(groupAttrs) == 0 ||
316 !arrayp(groupClasses) || sizeof(groupClasses) == 0 ||
317 sizeof(groupAttrs) != sizeof(groupClasses) )
319 return mkmapping( groupAttrs, groupClasses );
323 string get_group_attr ( mapping data, void|mapping group_spec ) {
324 if ( !mappingp(group_spec) )
325 group_spec = get_group_spec();
326 if ( !mappingp(group_spec) ) return UNDEFINED;
327 foreach ( indices(group_spec), string attr ) {
328 mixed value = data[ attr ];
329 if ( stringp(value) && value != "" )
336 string dn_to_group_name ( string dn, void|int dont_strip_base_dc ) {
337 string name = low_dn_to_group_name( dn );
338 if ( !stringp(name) || sizeof(name) < 1 ) return name;
339 if ( dont_strip_base_dc || !stringp(config->base_dc) ) return name;
340 string base = low_dn_to_group_name( config->base_dc );
341 if ( !has_prefix( lower_case(name), lower_case(base) ) ) return name;
342 name = name[(sizeof(base)+1)..];
343 if ( sizeof(name)<1 ) return 0;
348 string low_dn_to_group_name ( string dn ) {
349 if ( !stringp(dn) || sizeof(dn)<1 ) return 0;
351 array ignore = Config.array_value(config->subgroupIgnore);
352 if ( !arrayp(ignore) ) ignore = ({ });
353 for ( int i=0; i<sizeof(ignore); i++ )
354 ignore[i] = lower_case( String.trim_all_whites(ignore[i]) );
355 foreach ( reverse( dn / "," ), string part ) {
356 part = String.trim_all_whites( part );
357 // TODO: handle wildcard *
358 if ( search( ignore, lower_case( part ) ) >= 0 ) continue;
359 sscanf( part, "%*s=%s", part );
360 if ( stringp(part) && sizeof(part)>0 ) {
361 if ( stringp(name) && sizeof(name)>0 )
373 bool check_access () {
374 if ( !restrict_access ) return true;
376 if ( search( valid_caller_objects, CALLER ) >= 0 ) return true;
378 string prog = sprintf( "%O",object_program(CALLER) );
379 if ( search( valid_caller_programs, prog ) >= 0 ) return true;
380 // if caller is not valid, throw an exception:
381 steam_error( sprintf("ldap may not be accessed by caller %O\n", CALLER) );
384 object connect ( void|string user, void|string password, void|bool dont_reconnect ) {
386 last_error = ERROR_NOT_CONNECTED;
387 if ( !ldap_activated() ) return 0;
390 if ( Config.bool_value( config->bindUser ) && stringp(user) ) {
391 if ( Config.bool_value(config->bindUserWithoutDN) )
394 bind_dn = make_dn( config->userAttr + "=" + user, config["userdn"], config["base_dc"] );
397 else if ( stringp(config["user"]) && sizeof(config["user"]) > 0 ) {
398 // bind with default/root user
399 if ( search( config["user"], "=" ) >= 0 )
400 bind_dn = config["user"];
402 bind_dn = make_dn( "cn="+config["user"] );
403 password = config["password"];
405 else { // don't bind at all:
409 LDAP_LOG("count of open file desciptors: " + sizeof(Stdio.get_all_active_fd()) );
412 if ( Config.bool_value(config["reuseConnection"]) ||
413 config["reconnectTime"] > 0 ) {
414 if ( objectp(single_ldap) ) {
419 disconnect( single_ldap );
420 if ( !objectp(ldap) ) {
421 array servers = Config.array_value( config["server"] );
422 if ( arrayp(servers) ) {
423 foreach ( servers, mixed server ) {
424 if ( !stringp(server) || sizeof(server) < 1 ) continue;
425 ldap = Protocols.LDAP.client( server );
426 if ( objectp(ldap) ) break;
427 LDAP_LOG( "failed connection to %s", server );
431 LDAP_LOG( "new connection: %O", ldap );
434 if ( err ) FATAL( "ldap: error while connecting: %O\n", err[0] );
435 if ( !objectp(ldap) ) return 0;
440 if ( stringp(bind_dn) && stringp(password) ) {
441 if ( bind_dn != last_bind_dn ||
442 !verify_crypt_md5( password, last_bind_password_hash ) ) {
443 LDAP_LOG( "binding %O", bind_dn );
444 if ( !ldap->bind( fix_query_string(bind_dn), password ) )
445 throw( "bind failed on " + bind_dn );
446 // remember dn and password hash, so we don't need to re-bind if
447 // the user and password haven't changed:
448 last_bind_dn = bind_dn;
449 last_bind_password_hash = make_crypt_md5( password );
452 else LDAP_LOG( "using without bind" );
453 ldap->set_scope( 2 );
454 if ( stringp(config["base_dc"]) && sizeof(config["base_dc"]) )
455 ldap->set_basedn( fix_query_string(config["base_dc"]) );
458 string error_msg = "";
459 mixed ldap_error_nr = ldap->error_number();
460 if ( ldap_error_nr > 0 )
461 error_msg = "\n" + ldap->error_string() +
462 sprintf( " (#%d)", ldap_error_nr );
463 LDAP_LOG( "Failed to bind " + bind_dn + " on ldap" + error_msg );
464 if ( ldap_error_nr == 0x31 || // invalid credentials
465 ldap_error_nr == 0x30 ) { // no such object
470 disconnect( ldap, true );
471 last_error = ERROR_NOT_CONNECTED;
472 if ( dont_reconnect ) {
473 FATAL("Failed to bind " + bind_dn + " on ldap" + error_msg);
477 return connect( user, password, true ); // try to reconnect once
480 last_error = NO_ERROR;
485 void disconnect ( object ldap, void|bool force_disconnect ) {
487 last_error = NO_ERROR;
488 if ( !objectp(ldap) ) return;
489 if ( Config.bool_value(config["reuseConnection"]) ||
490 config["reconnectTime"] > 0 ) {
491 if ( !force_disconnect ) return;
495 last_bind_password_hash = 0;
497 //removed for pike 7.8: method query_fd() on ldap no longer exists.
498 // int fd = ldap->query_fd();
501 // FATAL( "ldap: %d open file descriptors, calling garbage collector...\n",
502 // sizeof( Stdio.get_all_active_fd() ) );
503 // int time_start = get_time_millis();
504 // gc(); // force garbage collection to free ldap file descriptors
505 // FATAL( "ldap: garbage collector done, %d open file descriptors remain.\n",
506 // sizeof( Stdio.get_all_active_fd() ) );
507 // LOG_TIME( time_start, "disconnect[gc]" );
509 // LDAP_LOG( "connection closed (file descriptor was %d)", fd );
513 mapping get_config ()
520 object get_user_cache () {
521 if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
526 object get_group_cache () {
527 if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
532 object get_authorize_cache () {
533 if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
534 return authorize_cache;
538 mapping get_failed_authorize () {
539 if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
540 return log_failed_auth;
544 int hashcode(map userdata)
547 if (!mappingp(userdata))
550 foreach(values(userdata), mixed v) {
552 hashvalue += hash(v);
561 void update_cache() {
563 // first check if the time is right
564 int startUpdate = config->updateCacheStartHour;
565 if (!intp(startUpdate))
571 string current_time = ctime(time());
572 sscanf(current_time, "%*s %*s %*d %d:%*d:%*d %*s\n", hour);
573 LDAP_LOG("Checking update time: Hour = %O, update = %O\n",
576 if ( hour == startUpdate) {
578 int updateInterval = config->updateCacheInterval;
580 int time_start = get_time_millis();
581 object ldap = connect();
582 array(map) users = fetch_users_internal();
584 foreach(users, mapping user_data) {
585 if (sizeof(user_data) == 0)
588 user_data = fix_charset( user_data );
590 mapping cached_data = user_cache->fetch(user_data[config->userAttr]);
591 if (hashcode(user_data) != hashcode(cached_data)) {
592 LDAP_LOG("User updated in LDAP directory: invalidating user %O\n",
594 invalidate_queue->write(user_data);
597 MESSAGE("Invalidating LDAP cache in %d ms, %d updates",
598 get_time_millis() - time_start, invalidate_queue->size());
599 sleep(updateInterval*3600);
611 valid_caller_objects = ({ master() });
612 // since ldap is initialized before ldappersistence, we cannot fetch the
613 // ldappersistence object yet... we just check against the program path:
614 valid_caller_programs = ({ "/modules/ldappersistence", "/modules/auth" });
616 last_error = NO_ERROR;
617 config = Config.read_config_file( _Server.get_config_dir()+"/modules/ldap.cfg", "ldap" );
618 if ( !mappingp(config) ) {
620 MESSAGE("LDAP Service not started - missing configuration !");
621 last_error = ERROR_NOT_CONNECTED;
622 return; // ldap not started !
624 if ( !ldap_activated() ) {
625 MESSAGE("LDAP deactivated.");
626 last_error = ERROR_NOT_CONNECTED;
627 return; // ldap deactivated
630 LDAP_LOG("configuration is %O", config);
633 if ( stringp(config["charset"]) && config["charset"] != "utf-8" ) {
634 charset_decoder = Locale.Charset.decoder( config["charset"] );
635 charset_encoder = Locale.Charset.encoder( "utf-8" );
636 if ( !objectp(charset_decoder) || !objectp(charset_encoder) )
637 FATAL( "LDAP: could not create a charset converter for %s\n",
646 int cache_time = config["cacheTime"];
647 user_cache = LDAPCache( cache_time, fetch_user_internal,
648 user_last_modified, fetch_users_internal );
649 group_cache = LDAPCache( cache_time, fetch_group_internal,
650 group_last_modified );
651 authorize_cache = LDAPCache( cache_time, authenticate_user_internal,
652 user_last_modified );
655 if ( stringp(config["logAuthorization"]) ) {
656 array a = Config.array_value( config["logAuthorization"] );
657 foreach ( a, string s ) {
658 switch ( lower_case(s) ) {
661 log_level_auth |= LOG_AUTH_FAILURE;
665 log_level_auth |= LOG_AUTH_SUCCESS;
669 log_level_auth |= LOG_AUTH_DETAILS;
675 // restricted access:
676 if ( !zero_type(config->restrictAccess) &&
677 !Config.bool_value(config->restrictAccess) )
678 restrict_access = false;
680 restrict_access = true;
681 valid_caller_objects += ({ user_cache, group_cache, authorize_cache });
683 if ( stringp(config->notfound) && lower_case(config->notfound) == "create"
684 && !config->objectName )
685 steam_error("objectName configuration missing !");
687 if ( Config.bool_value( config->sync ) ) {
688 // if our main dc does not exist - create it
689 object ldap = connect();
691 ldap->add( config->base_dc, ([
692 "objectclass": ({ "dcObject", "organization" }),
693 "o": "sTeam Authorization Directory",
696 if ( err ) FATAL( "ldap: failed to create main dc: %O\n", err[0] );
700 // check whether the connection is working
704 array servers = Config.array_value( config["server"] );
705 if ( arrayp(servers) ) {
706 foreach ( servers, mixed tmp_server ) {
707 if ( !stringp(tmp_server) || sizeof(tmp_server) < 1 ) continue;
709 ldap = Protocols.LDAP.client( server );
710 if ( objectp(ldap) ) break;
714 if ( err ) FATAL( "ldap: error while connecting: %O\n", err[0] );
715 if ( objectp(ldap) ) {
716 MESSAGE( "LDAP: connected to %s", server );
720 last_error = ERROR_NOT_CONNECTED;
721 MESSAGE( "LDAP: failed to connect to %s", server );
722 werror( "LDAP: failed to connect to %s\n", server );
726 if (stringp(config->prefetchUsers)) {
727 int time_start = get_time_millis();
728 user_cache->pre_fetch();
729 MESSAGE("LDAP: pre-fetched " + user_cache->size() + " users in %d ms",
730 get_time_millis() - time_start);
732 if (intp(config->updateCacheInterval)) {
733 start_thread(update_cache);
742 if ( ldap_activated() ) {
743 add_global_event(EVENT_USER_CHANGE_PW, sync_password, PHASE_NOTIFY);
744 add_global_event(EVENT_USER_NEW_TICKET, sync_ticket, PHASE_NOTIFY);
745 add_global_event(EVENT_ADD_MEMBER, event_add_member, PHASE_NOTIFY);
746 add_global_event(EVENT_REMOVE_MEMBER, event_remove_member, PHASE_NOTIFY);
751 void event_add_member ( int e, object grp, object caller, object member, bool pw ) {
752 if ( member->get_object_class() & CLASS_GROUP ) {
753 uncache_group( member->get_identifier() );
754 uncache_group( grp->get_identifier() );
759 void event_remove_member ( int e, object grp, object caller, object member ) {
760 if ( member->get_object_class() & CLASS_GROUP ) {
761 uncache_group( member->get_identifier() );
762 uncache_group( grp->get_identifier() );
768 private bool notify_admin ( string msg ) {
769 if ( zero_type( config["adminAccount"] ) ) return false;
770 object admin = USER( config["adminAccount"] );
771 if ( !objectp(admin) ) admin = GROUP( config["adminAccount"] );
772 if ( !objectp(admin) ) return false;
773 string msg_text = "The following LDAP situation occured on the server "
774 + _Server->get_server_name() +" at "+ (ctime(time())-"\n") + " :\n" + msg;
775 admin->mail( msg_text, "LDAP on " + _Server->get_server_name(), 0, "text/plain" );
783 mixed map_results(object results)
785 array result = ({ });
786 LDAP_LOG("map_results with " + results->num_entries() + " entries");
787 for ( int i = 1; i <= results->num_entries(); i++ ) {
788 mapping data = ([ ]);
789 mapping res = results->fetch(i);
791 if (!mappingp(res)) {
792 LDAP_LOG("unable to map result!");
795 foreach(indices(res), string attr) {
796 if ( arrayp(res[attr]) ) {
797 if ( sizeof(res[attr]) == 1 )
798 data[attr] = res[attr][0];
800 data[attr] = res[attr];
803 if ( results->num_entries() == 1 )
805 result += ({ data });
813 int uncache_user ( string user ) {
814 if ( !objectp(user_cache) ) return 0;
815 LDAP_LOG( "uncaching user %s from user cache", user );
816 int dropped = user_cache->drop( user );
817 if ( !objectp(authorize_cache) ) return 0;
818 LDAP_LOG( "uncaching user %s from authorize cache", user );
819 return dropped & authorize_cache->drop( user );
823 int uncache_group ( string group ) {
824 if ( !objectp(group_cache) ) return 0;
825 LDAP_LOG( "uncaching group %s from group cache", group );
826 return group_cache->drop( group );
830 array(mapping) search_data ( string search_str, array result_attributes,
831 void|string base_dn, void|string user, void|string pass,
834 last_error = NO_ERROR;
835 if ( !ldap_activated() ) {
836 last_error = ERROR_NOT_CONNECTED;
840 if ( !stringp(search_str) || sizeof(search_str) < 1 )
846 int time_start = get_time_millis();
849 if ( Config.bool_value(config->bindUser) && stringp(user) && stringp(pass) )
850 ldap = connect(user, pass);
854 LDAP_LOG("searching data %s (user %O) : ldap is %O", search_str, user, ldap);
856 if ( !objectp(ldap) ) {
857 if ( nr_try < NR_RETRIES ) {
858 disconnect( ldap, true );
859 return search_data( search_str, result_attributes, base_dn,
860 user, pass, nr_try+1 );
862 last_error = ERROR_NOT_CONNECTED;
866 LDAP_LOG("searching data in LDAP: %s\n", search_str);
868 ldap->set_basedn( fix_query_string(make_dn( base_dn, config->base_dc )) );
869 if ( ldap->error_number() ) {
870 if ( nr_try < NR_RETRIES ) {
871 disconnect( ldap, true );
872 return search_data( search_str, result_attributes, base_dn,
873 user, pass, nr_try+1 );
875 FATAL( "ldap: error searching data: %s (#%d)\n",
876 ldap->error_string(), ldap->error_number() );
880 mixed err = catch( results = ldap->search( fix_query_string(search_str), result_attributes ) );
882 if ( nr_try < NR_RETRIES ) {
883 disconnect( ldap, true );
884 return search_data( search_str, result_attributes, base_dn,
885 user, pass, nr_try+1 );
887 FATAL( "ldap: error searching data: %s\n", err[0] );
889 last_error = ERROR_UNKNOWN;
893 LOG_TIME( time_start, "search_data( %s )", search_str );
895 if ( objectp(ldap) && ldap->error_number() ) {
896 if ( nr_try < NR_RETRIES ) {
897 disconnect( ldap, true );
898 return search_data( search_str, result_attributes, base_dn,
899 user, pass, nr_try+1 );
901 FATAL( "ldap: Error while searching data: %s (#%d)\n",
902 ldap->error_string(), ldap->error_number() );
904 last_error = ERROR_UNKNOWN;
908 if ( !objectp(results) ) {
909 if ( nr_try < NR_RETRIES ) {
910 disconnect( ldap, true );
911 return search_data( search_str, result_attributes, base_dn,
912 user, pass, nr_try+1 );
914 FATAL("ldap: Invalid results while searching data.\n");
916 last_error = ERROR_UNKNOWN;
920 if ( results->num_entries() == 0 ) {
921 LDAP_LOG("No matching data found in LDAP directory: %s", search_str);
926 udata = map_results( results );
927 if ( mappingp(udata) ) udata = ({ udata });
931 LOG_TIME( time_start, "search_data( %s )", search_str );
933 if ( !arrayp(udata) ) return 0;
938 array search_users ( mapping terms, bool any,
939 void|string user, void|string pass ) {
941 if ( !mappingp(terms) || sizeof(terms)<1 ) {
942 LDAP_LOG( "search_users: invalid terms: %O\n", terms );
943 last_error = ERROR_UNKNOWN;
947 string search_str = "";
948 foreach ( indices(terms), mixed attr ) {
949 mixed value = terms[attr];
950 if ( !stringp(attr) || sizeof(attr) < 1 ||
951 !stringp(value) || sizeof(value) < 1 ) continue;
953 case "login" : attr = config->userAttr; break;
954 case "firstname" : attr = config->nameAttr; break;
955 case "lastname" : attr = config->fullnameAttr; break;
956 case "email" : attr = config->emailAttr; break;
958 search_str += "(" + attr + "=" + replace( value, ([ "(":"", ")":"", "=":"", ":":"" ]) ) + ")";
960 if ( any ) search_str = "(&(objectclass=" + config->userClass + ")(|" +
962 else search_str = "(&(objectclass=" + config->userClass + ")" +
966 if ( stringp(config->user_dn) ) base_dn = config->user_dn;
967 array result = search_data( search_str, ({ config->userAttr }),
968 base_dn, user, pass );
970 foreach ( result, mixed res ) {
971 if ( mappingp(res) ) {
972 res = res[ config->userAttr ];
973 if ( stringp(res) && sizeof(res) > 0 )
982 * Returns an array of distinct names of all sub-groups of a group.
984 * @param dn the dn of the group of which to return the sub-groups
985 * @param recursive if 0 (default) then return only the immediate sub-groups of
986 * the group, otherwise return sub-groups recursively
987 * @return an array of dn entries of the sub-groups
989 array get_sub_groups ( string dn, int|void recursive, void|int nr_try ) {
990 //check_access(); // group structures are no privacy data
991 last_error = NO_ERROR;
992 if ( !ldap_activated() ) {
993 last_error = ERROR_NOT_CONNECTED;
997 if ( !stringp(dn) || sizeof(dn)<1 ) {
998 FATAL( "LDAP: get_subgroups: invalid dn: %O\n", dn );
999 last_error = ERROR_UNKNOWN;
1003 mapping group_spec = get_group_spec();
1004 if ( !mappingp(group_spec) ) {
1005 LDAP_LOG( "get_sub_groups: no groupAttr/groupClass configured" );
1006 last_error = ERROR_UNKNOWN;
1010 string group_identifier = dn_to_group_name( dn );
1011 string group_name = (group_identifier / ".")[-1];
1015 int time_start = get_time_millis();
1017 object ldap = connect();
1019 if ( !objectp(ldap) ) {
1020 if ( nr_try < NR_RETRIES ) {
1021 disconnect( ldap, true );
1022 return get_sub_groups( dn, recursive, nr_try+1 );
1024 last_error = ERROR_NOT_CONNECTED;
1029 LDAP_LOG( "fetching subgroups of: %s%s\n",
1030 dn, (recursive ? " (recursive)":"") );
1031 ldap->set_basedn( fix_query_string(dn) );
1032 if ( recursive ) old_scope = ldap->set_scope( 2 );
1033 else old_scope = ldap->set_scope( 1 );
1034 if ( ldap->error_number() ) {
1035 if ( nr_try < NR_RETRIES ) {
1036 disconnect( ldap, true );
1037 return get_sub_groups( dn, recursive, nr_try+1 );
1039 FATAL( "ldap: error fetching subgroups of %s : %s (#%d)\n", dn,
1040 ldap->error_string(), ldap->error_number() );
1043 string search_str = "";
1044 foreach ( values(group_spec), string groupClass )
1045 search_str += "(objectclass=" + groupClass + ")";
1046 if ( sizeof(group_spec) > 1 )
1047 search_str = "(|" + search_str + ")";
1048 mixed err = catch( results = ldap->search( fix_query_string(search_str) ) );
1050 if ( nr_try < NR_RETRIES ) {
1051 disconnect( ldap, true );
1052 return get_sub_groups( dn, recursive, nr_try+1 );
1054 FATAL( "ldap: error fetching subgroups of %s : %s\n", dn, err[0] );
1056 last_error = ERROR_UNKNOWN;
1060 LOG_TIME( time_start, "get_subgroups( %s )", dn );
1062 if ( objectp(ldap) && ldap->error_number() ) {
1063 if ( nr_try < NR_RETRIES ) {
1064 disconnect( ldap, true );
1065 return get_sub_groups( dn, recursive, nr_try+1 );
1067 FATAL( "ldap: Error while fetching subgroups of %s : %s (#%d)\n", dn,
1068 ldap->error_string(), ldap->error_number() );
1070 last_error = ERROR_UNKNOWN;
1074 if ( !objectp(results) ) {
1075 if ( nr_try < NR_RETRIES ) {
1076 disconnect( ldap, true );
1077 return get_sub_groups( dn, recursive, nr_try+1 );
1079 FATAL("ldap: Invalid results while fetching subgroups of %s .\n", dn);
1081 last_error = ERROR_UNKNOWN;
1085 if ( results->num_entries() == 0 ) {
1090 array data = map_results(results);
1092 foreach ( data, mapping subgroup ) {
1093 string sub_dn = subgroup["dn"];
1094 if ( stringp(sub_dn) && sizeof(sub_dn) > 0 ) dns += ({ sub_dn });
1096 LDAP_LOG( "group %s has %d subgroups", dn, sizeof(dns) );
1098 if ( old_scope < 0 ) catch( ldap->set_scope( 2 ) );
1099 else catch( ldap->set_scope( old_scope ) );
1106 array search_groups ( mapping terms, bool any,
1107 void|string user, void|string pass ) {
1109 if ( !mappingp(terms) || sizeof(terms)<1 ) {
1110 LDAP_LOG( "search_groups: invalid terms: %O\n", terms );
1111 last_error = ERROR_UNKNOWN;
1115 mapping group_spec = get_group_spec();
1116 if ( !mappingp(group_spec) ) {
1117 LDAP_LOG( "search_groups: cannot search: no groupAttr/groupClass " +
1119 last_error = ERROR_UNKNOWN;
1123 string search_str = "";
1124 foreach ( indices(terms), mixed attr ) {
1125 mixed value = terms[attr];
1126 if ( !stringp(attr) || sizeof(attr) < 1 ||
1127 !stringp(value) || sizeof(value) < 1 ) continue;
1128 value = replace( value, ([ "(":"", ")":"", "=":"", ":":"" ]) );
1129 if ( attr == "name" ) {
1130 string name_search_str = "";
1131 foreach ( indices( group_spec ), string groupAttr ) {
1132 string groupClass = group_spec[ groupAttr ];
1133 name_search_str += "(&(" + groupAttr + "=" + value + ")(objectclass=" +
1136 if ( sizeof(group_spec) > 1 )
1137 name_search_str += "(|" + name_search_str + ")";
1138 search_str += name_search_str;
1141 search_str += "(" + attr + "=" + value + ")";
1143 if ( any ) search_str = "(|" + search_str + ")";
1144 else search_str = "(&" + search_str + ")";
1147 if ( stringp(config->group_dn) ) base_dn = config->group_dn;
1148 mixed result = search_data( search_str, ({ "dn" }) + indices(group_spec),
1149 base_dn, user, pass );
1150 if ( !arrayp(result) )
1152 array groups = ({ });
1153 foreach ( result, mixed res ) {
1154 if ( mappingp(res) ) {
1155 if ( !stringp(get_group_attr( res, group_spec )) ||
1156 !stringp(res["dn"]) || res["dn"] == "" )
1158 groups += ({ dn_to_group_name( res["dn"] ) });
1165 mapping search_user ( string search_str, void|string user, void|string pass,
1169 last_error = NO_ERROR;
1170 if ( !ldap_activated() ) {
1171 last_error = ERROR_NOT_CONNECTED;
1175 if ( !stringp(search_str) || sizeof(search_str)<1 ) {
1176 FATAL( "LDAP: search_user: invalid search_str: %O\n", search_str );
1177 last_error = ERROR_UNKNOWN;
1181 mapping udata = ([ ]);
1184 int time_start = get_time_millis();
1187 if ( Config.bool_value(config->bindUser) && stringp(user) && stringp(pass) )
1188 ldap = connect(user, pass);
1192 LDAP_LOG("searching %s (user %O) : ldap is %O", search_str, user, ldap);
1194 if ( !objectp(ldap) ) {
1195 if ( nr_try < NR_RETRIES ) {
1196 disconnect( ldap, true );
1197 return search_user( search_str, user, pass, nr_try+1 );
1199 last_error = ERROR_NOT_CONNECTED;
1203 LDAP_LOG("looking up user in LDAP: %s\n", search_str);
1204 if ( config->userdn ) {
1205 ldap->set_basedn( fix_query_string(make_dn( config->userdn, config->base_dc )) );
1206 if ( ldap->error_number() ) {
1207 if ( nr_try < NR_RETRIES ) {
1208 disconnect( ldap, true );
1209 return search_user( search_str, user, pass, nr_try+1 );
1211 FATAL( "ldap: error searching user: %s (#%d)\n",
1212 ldap->error_string(), ldap->error_number() );
1216 mixed err = catch( results = ldap->search( fix_query_string(search_str) ) );
1218 if ( nr_try < NR_RETRIES ) {
1219 disconnect( ldap, true );
1220 return search_user( search_str, user, pass, nr_try+1 );
1222 FATAL( "ldap: error searching user: %s\n", err[0] );
1224 last_error = ERROR_UNKNOWN;
1228 LOG_TIME( time_start, "search_user( %s )", search_str );
1230 if ( objectp(ldap) && ldap->error_number() ) {
1231 if ( nr_try < NR_RETRIES ) {
1232 disconnect( ldap, true );
1233 return search_user( search_str, user, pass, nr_try+1 );
1235 if ( ldap->error_number() != 32 ) // 32 = no such object
1236 FATAL( "ldap: Error while searching user: %s (#%d)\n",
1237 ldap->error_string(), ldap->error_number() );
1239 last_error = ERROR_UNKNOWN;
1243 if ( !objectp(results) ) {
1244 if ( nr_try < NR_RETRIES ) {
1245 disconnect( ldap, true );
1246 return search_user( search_str, user, pass, nr_try+1 );
1248 FATAL("ldap: Invalid results while searching user.\n");
1250 last_error = ERROR_UNKNOWN;
1254 if ( results->num_entries() == 0 ) {
1255 LDAP_LOG("User not found in LDAP directory: %s", search_str);
1260 udata = map_results( results );
1261 LDAP_LOG( "user %s has dn: %O and %d data entries",
1262 search_str, udata["dn"], sizeof(indices(udata)) );
1266 LOG_TIME( time_start, "search_user( %s )", search_str );
1272 mapping search_group ( string search_str, void|int nr_try )
1275 last_error = NO_ERROR;
1276 if ( !ldap_activated() ) {
1277 last_error = ERROR_NOT_CONNECTED;
1281 if ( !stringp(search_str) || sizeof(search_str)<1 ) {
1282 FATAL( "LDAP: search_group: invalid search_str: %O\n", search_str );
1283 last_error = ERROR_UNKNOWN;
1287 mapping gdata = ([ ]);
1290 if ( !mappingp(get_group_spec()) ) {
1291 last_error = ERROR_UNKNOWN;
1295 int time_start = get_time_millis();
1297 object ldap = connect();
1299 if ( !objectp(ldap) ) {
1300 if ( nr_try < NR_RETRIES ) {
1301 disconnect( ldap, true );
1302 return search_group( search_str, nr_try+1 );
1304 last_error = ERROR_NOT_CONNECTED;
1308 LDAP_LOG("looking up group in LDAP: %s\n", search_str);
1309 if ( config->groupdn ) {
1310 ldap->set_basedn( fix_query_string(make_dn( config->groupdn, config->base_dc )) );
1311 if ( ldap->error_number() ) {
1312 if ( nr_try < NR_RETRIES ) {
1313 disconnect( ldap, true );
1314 return search_group( search_str, nr_try+1 );
1316 FATAL( "ldap: error searching group: %s (#%d)\n",
1317 ldap->error_string(), ldap->error_number() );
1321 mixed err = catch( results = ldap->search( fix_query_string(search_str) ) );
1323 if ( nr_try < NR_RETRIES ) {
1324 disconnect( ldap, true );
1325 return search_group( search_str, nr_try+1 );
1327 FATAL( "ldap: error searching group: %s\n", err[0] );
1329 last_error = ERROR_UNKNOWN;
1333 LOG_TIME( time_start, "search_group( %s )", search_str );
1335 if ( objectp(ldap) && ldap->error_number() ) {
1336 if ( nr_try < NR_RETRIES ) {
1337 disconnect( ldap, true );
1338 return search_group( search_str, nr_try+1 );
1340 if ( ldap->error_number() != 32 ) // 32 = no such object
1341 FATAL( "ldap: Error while searching group: %s (#%d)\n",
1342 ldap->error_string(), ldap->error_number() );
1344 last_error = ERROR_UNKNOWN;
1348 if ( !objectp(results) ) {
1349 if ( nr_try < NR_RETRIES ) {
1350 disconnect( ldap, true );
1351 return search_group( search_str, nr_try+1 );
1353 FATAL("ldap: Invalid results while searching group.\n");
1355 last_error = ERROR_UNKNOWN;
1359 if ( results->num_entries() == 0 ) {
1360 LDAP_LOG("Group not found in LDAP directory: %s", search_str);
1365 gdata = map_results(results);
1366 LDAP_LOG( "group %s has dn: %O", search_str, gdata["dn"] );
1374 int user_last_modified ( string identifier, void|string pass )
1377 //TODO: query ldap attribute modifyTimestamp
1382 mapping fetch_user ( string identifier, void|string pass )
1385 int time_start = get_time_millis();
1386 last_error = NO_ERROR;
1387 if ( !ldap_activated() ) {
1388 last_error = ERROR_NOT_CONNECTED;
1391 if ( !stringp(identifier) ) {
1392 last_error = ERROR_UNKNOWN;
1396 LDAP_LOG("fetch_user(%s) %d", identifier, stringp(pass));
1398 mapping result = user_cache->fetch( identifier, pass );
1399 if ( !mappingp(result) ) authorize_cache->drop( identifier );
1400 LOG_TIME( time_start, "fetch_user( %s )", identifier );
1406 mapping fetch_user_internal ( string identifier, void|string pass )
1409 LDAP_LOG("fetch_user_internal(%s) %d", identifier, stringp(pass));
1410 string search_str = "("+config->userAttr+"="+identifier+")";
1411 if ( stringp(config->userClass) && sizeof(config->userClass)>0 )
1412 search_str = "(&"+search_str+"(objectclass="+config->userClass+"))";
1413 mapping result = fix_charset( search_user( search_str, identifier, pass ) );
1414 if ( !mappingp(result) )
1416 if ( lower_case(result[config->userAttr]) != lower_case(identifier) )
1426 array(mapping) fetch_users_internal ()
1429 LDAP_LOG("fetch_users_internal()");
1430 string search_str = "("+config->userAttr+"=*)";
1431 if ( stringp(config->userClass) && sizeof(config->userClass)>0 )
1432 search_str = "(&"+search_str+"(objectclass="+config->userClass+"))";
1433 object ldap = connect();
1434 if ( !objectp(ldap) )
1437 object results = ldap->search (fix_query_string(search_str));
1438 if ( results->num_entries() == 0 ) {
1439 LDAP_LOG("No matching data found in LDAP directory: %s", search_str);
1443 LDAP_LOG("Found %d results", results->num_entries());
1445 return map_results( results );
1450 int group_last_modified ( string identifier )
1453 //TODO: query ldap attribute modifyTimestamp
1458 mapping fetch_group ( string identifier )
1461 int time_start = get_time_millis();
1462 last_error = NO_ERROR;
1463 if ( !ldap_activated() ) {
1464 last_error = ERROR_NOT_CONNECTED;
1467 if ( !stringp(identifier) || sizeof(identifier) < 1 ) {
1468 last_error = ERROR_UNKNOWN;
1472 if ( !mappingp(get_group_spec()) ) {
1473 last_error = ERROR_UNKNOWN;
1477 LDAP_LOG("fetch_group(%s)", identifier);
1478 mapping result = group_cache->fetch( identifier );
1479 LOG_TIME( time_start, "fetch_group( %s )", identifier );
1485 mapping fetch_group_internal ( string identifier )
1488 LDAP_LOG("fetch_group_internal(%s)", identifier);
1489 mapping group_spec = get_group_spec();
1490 if ( !mappingp(group_spec) ) {
1491 LDAP_LOG( "fetch_group_internal: could not fetch: no " +
1492 "groupAttr/groupClass specified in config." );
1493 last_error = ERROR_UNKNOWN;
1497 array parts = identifier / ".";
1498 string search_str = "(|";
1499 foreach ( indices(group_spec), string groupAttr ) {
1500 search_str += "(&(" + groupAttr + "=" + parts[-1] +
1501 ")(objectclass=" + group_spec[groupAttr] + "))";
1504 // sub-groups by structural method are already handled by that query
1506 // pseudo sub-groups by attribute:
1507 if ( config->subgroupMethod == "attribute" &&
1508 stringp(config->groupParentAttr) &&
1509 sizeof(config->groupParentAttr) > 0 && sizeof(parts) > 1 ) {
1510 search_str = "(&" + search_str + "(" + config->groupParentAttr +
1511 "=" + parts[-2] + "))";
1514 mixed results = fix_charset( search_group( search_str ) );
1516 if ( config->subgroupMethod == "structural" && arrayp(results) ) {
1517 string lower_identifier = lower_case( identifier );
1518 foreach ( results, mapping m ) {
1519 string result_name = dn_to_group_name( m->dn );
1520 if ( lower_case( result_name ) == lower_identifier ) {
1527 // if there is more than one result, only return one:
1529 if ( arrayp(results) ) result = results[0];
1530 else if ( mappingp(results) ) result = results;
1531 if ( !mappingp(result) ) return UNDEFINED;
1532 string identifier_last_part = (identifier / ".")[-1];
1533 string result_last_part = (dn_to_group_name( result->dn ) / ".")[-1];
1534 if ( lower_case(result_last_part) != lower_case(identifier_last_part) )
1541 mixed fetch ( string dn, string pattern )
1543 return fetch_scope( dn, pattern, 2 );
1546 mixed fetch_scope ( string dn, string pattern, int scope )
1549 last_error = NO_ERROR;
1550 if ( !ldap_activated() ) {
1551 last_error = ERROR_NOT_CONNECTED;
1554 if ( !stringp(dn) || !stringp(pattern) ) {
1555 last_error = ERROR_UNKNOWN;
1559 // caller must be module...
1560 if ( !_Server->is_module( CALLER ) )
1561 steam_error( "Access for non-module denied !" );
1565 object ldap = connect();
1567 if ( !objectp(ldap) ) {
1568 last_error = ERROR_NOT_CONNECTED;
1572 ldap->set_scope( scope );
1573 if ( stringp(dn) && sizeof(dn)>0 ) {
1574 if ( has_suffix( dn, config->base_dc ) )
1575 ldap->set_basedn( fix_query_string(dn) );
1577 ldap->set_basedn( fix_query_string(make_dn( dn, config->base_dc )) );
1580 ldap->set_basedn( fix_query_string(config->base_dc) );
1581 if ( ldap->error_number() )
1582 FATAL( "ldap: error fetching: %s (#%d)\n",
1583 ldap->error_string(), ldap->error_number() );
1585 mixed err = catch( results = ldap->search( fix_query_string(pattern) ) );
1587 FATAL( "ldap: error fetching: %s\n", err[0] );
1589 last_error = ERROR_UNKNOWN;
1593 if ( objectp(ldap) && ldap->error_number() ) {
1594 if ( ldap->error_number() != 32 ) // 32 = no such object
1595 FATAL( "ldap: Error while fetching: %s (#%d)\n",
1596 ldap->error_string(), ldap->error_number() );
1598 last_error = ERROR_UNKNOWN;
1602 if ( !objectp(results) ) {
1603 FATAL("ldap: Invalid results while fetching.\n");
1605 last_error = ERROR_UNKNOWN;
1609 if ( results->num_entries() == 0 ) {
1610 LDAP_LOG("No results when fetching: %s", pattern);
1617 return map_results( results );
1621 array fetch_url ( string url, string additional_filter ) {
1623 last_error = NO_ERROR;
1624 if ( !ldap_activated() ) {
1625 last_error = ERROR_NOT_CONNECTED;
1628 if ( !stringp(url) || sizeof(url) < 1 ) {
1629 last_error = ERROR_UNKNOWN;
1632 // caller must be module...
1633 if ( !_Server->is_module( CALLER ) )
1634 steam_error( "Access for non-module denied !" );
1638 object ldap = connect();
1640 if ( !objectp(ldap) ) {
1641 last_error = ERROR_NOT_CONNECTED;
1645 mapping parts = ldap->parse_url( url );
1646 if ( !mappingp(parts) ) {
1647 last_error = ERROR_UNKNOWN;
1651 string basedn = parts["basedn"];
1652 if ( !stringp(basedn) ) basedn = "";
1653 string filter = parts["filter"];
1654 if ( !stringp(filter) ) filter = "";
1655 if ( sizeof(filter) < 1 && sizeof(basedn) > 0 ) {
1656 // basedn without filter:
1657 array basedn_parts = basedn / ",";
1658 filter = "(" + basedn_parts[0] + ")";
1659 basedn = basedn_parts[1..] * ",";
1661 else if ( sizeof(filter) < 1 && sizeof(basedn) < 1 ) {
1662 last_error = ERROR_UNKNOWN;
1665 ldap->set_scope( parts["scope"] );
1666 ldap->set_basedn( fix_query_string(basedn) );
1667 if ( stringp(additional_filter) && sizeof(additional_filter) > 0 ) {
1668 if ( !has_prefix( additional_filter, "(" ) )
1669 additional_filter = "(" + additional_filter;
1670 if ( !has_suffix( additional_filter, ")" ) )
1671 additional_filter += ")";
1672 filter = "(&" + filter + additional_filter + ")";
1674 mixed err = catch( results = ldap->search( fix_query_string(filter) ) );
1676 FATAL( "ldap: error fetching url: %s\n", err[0] );
1678 last_error = ERROR_UNKNOWN;
1682 if ( objectp(ldap) && ldap->error_number() ) {
1683 if ( ldap->error_number() != 32 ) // 32 = no such object
1684 FATAL( "ldap: Error while fetching url: %s (#%d)\n",
1685 ldap->error_string(), ldap->error_number() );
1687 last_error = ERROR_UNKNOWN;
1691 if ( !objectp(results) ) {
1692 FATAL("ldap: Invalid results while fetching url.\n");
1694 last_error = ERROR_UNKNOWN;
1698 if ( results->num_entries() == 0 ) {
1699 LDAP_LOG("No results when fetching url: %s", url);
1706 mixed res = map_results( results );
1707 if ( mappingp(res) ) res = ({ res });
1712 string fix_query_string ( string s )
1714 if ( !stringp(s) ) return s;
1716 for ( int i=0; i<sizeof(s); i++ ) {
1717 if ( (32 <= (int)s[i]) && ((int)s[i] < 128) ) s2 += s[i..i];
1718 else s2 += sprintf( "\\%X", (int)s[i] );
1724 mixed fix_charset ( string|mapping|array v )
1726 if ( zero_type(v) ) return UNDEFINED;
1727 if ( !objectp(charset_encoder) || !objectp(charset_decoder) ) return v;
1729 if ( xml.utf8_check(v) ) return v; // already utf-8
1730 string tmp = charset_decoder->clear()->feed(v)->drain();
1731 tmp = charset_encoder->clear()->feed(tmp)->drain();
1732 // LDAP_LOG( "charset conversion: from \"%s\" to \"%s\".", v, tmp );
1735 else if ( arrayp(v) ) {
1737 foreach ( v, mixed i )
1738 tmp += ({ fix_charset(i) });
1741 else if ( mappingp(v) ) {
1742 mapping tmp = ([ ]);
1743 foreach ( indices(v), mixed i )
1744 tmp += ([ fix_charset(i) : fix_charset(v[i]) ]);
1747 else return UNDEFINED;
1752 bool check_password(string pass, string user_pw)
1754 if ( !stringp(pass) || !stringp(user_pw) )
1757 LDAP_LOG("check_password()");
1758 if ( strlen(user_pw) > 5 && lower_case(user_pw[0..4]) == "{sha}" )
1759 return user_pw[5..] == MIME.encode_base64( sha_hash(pass) );
1760 if ( strlen(user_pw) > 6 && lower_case(user_pw[0..5]) == "{ssha}" ) {
1761 string salt = MIME.decode_base64( user_pw[6..] )[20..]; // last 8 bytes is the salt
1762 return user_pw[6..] == MIME.encode_base64( sha_hash(pass+salt) );
1766 if ( strlen(user_pw) > 7 && lower_case(user_pw[0..6]) == "{crypt}" )
1767 return crypt(pass, user_pw[7..]);
1768 if ( strlen(user_pw) > 4 && lower_case(user_pw[0..3]) == "{lm}" ) {
1769 return user_pw[4..] == LanManHash.lanman_hash(pass);
1771 if ( strlen(user_pw) < 3 || user_pw[0..2] != "$1$" )
1772 return crypt(pass, user_pw); // normal crypt check
1774 return verify_crypt_md5(pass, user_pw);
1778 bool authorize_ldap ( object user, string pass )
1780 return authenticate_user( user, pass );
1784 bool authenticate_user ( object user, string pass )
1787 int time_start = get_time_millis();
1788 last_error = NO_ERROR;
1789 if ( !ldap_activated() ) {
1790 last_error = ERROR_NOT_CONNECTED;
1794 if ( !objectp(user) )
1795 steam_error("User object expected for authentication !");
1797 if ( !stringp(pass) || sizeof(pass)<1 ) {
1798 last_error = ERROR_UNKNOWN;
1802 string uname = user->get_user_name();
1804 // don't authenticate restricted users:
1805 if ( _Persistence->user_restricted( uname ) ) return false;
1807 string cached = authorize_cache->fetch( uname, pass );
1809 if ( stringp(cached) && check_password( pass, cached ) ) {
1810 // successfully authorized from cache
1811 LDAP_LOG("user %s LDAP cache authorized", uname);
1812 LOG_TIME( time_start, "authenticate_user[success]( %s )", uname );
1813 bool log_auth = false;
1814 if ( log_level_auth & LOG_AUTH_FAILURE ) {
1815 if ( has_index( log_failed_auth, uname ) ) {
1816 LOG_AUTH( "%s finally authorized by ldap cache after %d seconds "+
1817 "(crc: %d)", uname, time()-log_failed_auth[uname], Gz.crc32(pass) );
1818 m_delete( log_failed_auth, uname );
1822 if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1823 LOG_AUTH( "%s authorized by ldap cache (crc: %d)",
1824 uname, Gz.crc32(pass) );
1830 // failed to authorize from cache
1831 if ( log_level_auth & LOG_AUTH_FAILURE ) {
1832 if ( !has_index( log_failed_auth, uname ) ) {
1833 log_failed_auth[ uname ] = time();
1834 LOG_AUTH( "%s : %s failed", (ctime(time())-"\n"), uname );
1836 LOG_AUTH( "%s not authorized by ldap cache (password check) (crc: %d)",
1837 uname, Gz.crc32(pass) );
1839 LDAP_LOG("user %s found in LDAP cache - password failed: %O",
1841 LOG_TIME( time_start, "authenticate_user[failed]( %s )", uname );
1847 string authenticate_user_internal ( string uname, string pass,
1850 int time_start = get_time_millis();
1852 // try authorizing via bind:
1853 if ( Config.bool_value(config->bindUser) &&
1854 stringp(uname) && sizeof(uname) > 0 &&
1855 stringp(pass) && sizeof(pass) > 0) {
1856 object ldap = connect( uname, pass );
1857 if ( !objectp(ldap) ) {
1858 if ( nr_try < NR_RETRIES ) {
1859 disconnect( ldap, true );
1860 return authenticate_user_internal( uname, pass, nr_try+1 );
1863 if ( objectp(ldap) ) {
1865 LDAP_LOG("authorized %s via bind", uname);
1866 LOG_TIME( time_start, "authenticate_user[bind]( %s )", uname );
1867 string cached_pw = make_crypt_md5( pass );
1868 bool log_auth = false;
1869 if ( (log_level_auth & LOG_AUTH_FAILURE) &&
1870 has_index( log_failed_auth, uname ) ) {
1871 LOG_AUTH( "%s finally authorized via ldap bind (after %d "+
1872 "seconds)", uname, time() - log_failed_auth[uname] );
1875 if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1876 LOG_AUTH( "%s authorized via ldap bind", uname );
1879 if ( log_auth && (log_level_auth & LOG_AUTH_DETAILS) )
1880 LOG_AUTH( "%s password crc32 : %d", uname, Gz.crc32( pass ) );
1884 LDAP_LOG("could not authorize %s via bind (no ldap connection)", uname);
1885 if ( log_level_auth & LOG_AUTH_FAILURE ) {
1886 LOG_AUTH( "%s not authorized via ldap bind", uname );
1887 if ( log_level_auth & LOG_AUTH_DETAILS )
1888 LOG_AUTH( "%s password crc32: %d", uname, Gz.crc32( pass ) );
1893 // fetch user data and authorize via password:
1894 mapping udata = fetch_user(uname, pass);
1895 LDAP_LOG("trying to authorize user %s via password", uname);
1897 LOG_TIME( time_start, "authenticate_user[fetch]( %s )", uname );
1899 if ( mappingp(udata) ) {
1900 object user = USER( uname );
1901 string dn = udata["dn"];
1902 if ( !stringp(dn) ) dn = "";
1904 // check for conflicts (different user in sTeam than in LDAP):
1905 if (config->checkConflicts && !stringp(user->query_attribute("ldap:dn"))) {
1906 if ( search(ldap_conflicts,uname)<0 ) {
1907 ldap_conflicts += ({ uname });
1909 "Dear LDAP administrator at "+_Server->get_server_name()
1910 +",\n\nthere has been a conflict between LDAP and sTeam:\n"
1911 +"User \""+uname+"\" already exists in sTeam, but now "
1912 +"there is also an LDAP user with the same name/id.\nYou "
1913 +"will need to remove/rename one of them or, if they are "
1914 +"the same user, you can overwrite the sTeam data from LDAP "
1915 +"by adding a \"dn\" attribute to the sTeam user." ) );
1917 FATAL( "ldap: user conflict: %s in sTeam vs. %s in LDAP\n", uname, dn );
1921 else if ( search(ldap_conflicts,uname) >= 0 )
1922 ldap_conflicts -= ({ uname });
1924 string ldap_password = udata[config->passwordAttr] || "";
1925 if ( stringp(config->passwordPrefix) && sizeof(config->passwordPrefix)>0 )
1926 ldap_password = config->passwordPrefix + ldap_password;
1928 if ( check_password( pass, ldap_password ) ) {
1929 // need to synchronize passwords from ldap if ldap is down ?!
1930 // this is only done when the ldap password is received
1931 if ( ldap_password != user->get_user_password())
1932 user->set_user_password(ldap_password, 1);
1933 LDAP_LOG("user %s authorized via password from LDAP", uname);
1934 string cached_pw = make_crypt_md5( pass );
1935 bool log_auth = false;
1936 if ( (log_level_auth & LOG_AUTH_FAILURE) &&
1937 has_index( log_failed_auth, uname ) ) {
1938 LOG_AUTH( "%s finally authorized via password (after %d seconds)",
1939 uname, time() - log_failed_auth[uname] );
1942 if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1943 LOG_AUTH( "%s authorized via password", uname );
1946 if ( log_auth && (log_level_auth & LOG_AUTH_DETAILS) )
1947 LOG_AUTH( "%s password crc32: %d\n* ldap hash: %O",
1948 uname, Gz.crc32(pass), ldap_password );
1952 if ( log_level_auth & LOG_AUTH_FAILURE ) {
1953 LOG_AUTH( "%s not authorized via password", uname );
1954 if ( log_level_auth & LOG_AUTH_DETAILS )
1955 LOG_AUTH( "%s password crc32: %d\n* ldap hash: %O",
1956 uname, Gz.crc32(pass), ldap_password );
1958 LDAP_LOG("user %s found in LDAP directory - password failed!", uname);
1962 LDAP_LOG("user " + uname + " was not found in LDAP directory.");
1963 // if notfound configuration is set to create, then we should create a user:
1964 if ( config->notfound == "create" ) {
1965 object user = USER( uname );
1966 if ( add_user( uname, pass, user ) )
1967 return make_crypt_md5( pass );
1974 object sync_user(string name)
1977 last_error = NO_ERROR;
1978 if ( !ldap_activated() ) {
1979 last_error = ERROR_NOT_CONNECTED;
1983 // don't sync restricted users:
1984 if ( _Persistence->user_restricted( name ) )
1987 mapping udata = fetch_user( name );
1988 if ( !mappingp(udata) )
1991 LDAP_LOG("sync of ldap user \"%s\": %O", name, udata);
1992 object user = get_module("users")->get_value(name);
1993 string ldap_password = udata[config->passwordAttr];
1994 if ( stringp(ldap_password) && stringp(config->passwordPrefix) &&
1995 sizeof(config->passwordPrefix)>0 )
1996 ldap_password = config->passwordPrefix + ldap_password;
1997 if ( objectp(user) ) {
1998 // update user date from LDAP
1999 if ( ! user->set_attributes( ([
2000 "pw" : ldap_password,
2001 "email" : udata[config->emailAttr],
2002 "fullname" : udata[config->fullnameAttr],
2003 "firstname" : udata[config->nameAttr],
2004 "OBJ_DESC" : udata[config->descriptionAttr],
2006 FATAL( "LDAP: Could not sync user attributes with ldap for \"%s\".\n", name );
2008 // create new user to match LDAP user
2009 object factory = get_factory(CLASS_USER);
2010 user = factory->execute( ([
2012 "pw" : udata[config->passwordAttr],
2013 "email" : udata[config->emailAttr],
2014 "fullname" : udata[config->fullnameAttr],
2015 "firstname" : udata[config->nameAttr],
2016 "OBJ_DESC" : udata[config->descriptionAttr],
2018 user->set_user_password( ldap_password, 1 );
2019 user->activate_user( factory->get_activation() );
2021 // sync group membership:
2022 if ( objectp( user ) ) {
2023 string primaryGroupId = udata[config->groupId];
2024 if ( stringp( primaryGroupId ) ) {
2025 mapping group = search_group("("+config->groupId+"="+primaryGroupId+")");
2032 object sync_group(string name)
2035 last_error = NO_ERROR;
2036 if ( !ldap_activated() ) {
2037 last_error = ERROR_NOT_CONNECTED;
2041 // don't syncronize restricted groups:
2042 if ( _Persistence->group_restricted( name ) )
2045 mapping gdata = fetch_group( name );
2046 if ( !mappingp(gdata) )
2049 LDAP_LOG("sync of ldap group: %O", gdata);
2050 //object group = get_module("groups")->lookup(name);
2051 object group = get_module("groups")->get_value( name );
2052 if ( objectp(group) ) {
2053 // group memberships are handled by the persistence manager
2054 // update group date from LDAP
2055 group->set_attributes( ([
2056 "OBJ_DESC" : gdata[config->descriptionAttr],
2059 // create new group to match LDAP group
2060 object factory = get_factory(CLASS_GROUP);
2061 group = factory->execute( ([
2063 "OBJ_DESC" : gdata[config->descriptionAttr],
2070 void sync_password(int event, object user, object caller)
2072 if ( !Config.bool_value(config->sync) )
2074 string oldpw = user->get_old_password();
2075 string crypted = user->get_user_password();
2076 string name = user->get_user_name();
2077 // don't sync password for restricted users:
2078 if ( _Persistence->group_restricted( name ) ) return;
2079 LDAP_LOG("password sync for " + user->get_user_name());
2084 if ( Config.bool_value(config->bindUser) && oldpw &&
2085 objectp(ldap = connect( name, oldpw )) ) {
2086 if ( config->userdn )
2087 dn = make_dn(config->userAttr+"="+name, config->userdn, config->base_dc);
2089 dn = make_dn(config->userAttr+"="+name, config->base_dc);
2091 else if ( ldap = connect() ) {
2092 dn = make_dn( config->base_dc, config->userAttr+"="+name );
2095 if ( !stringp(dn) ) {
2096 LDAP_LOG("sync_password(): no dn");
2102 if ( crypted[..2] == "$1$" )
2103 crypted = "{crypt}" + crypted;
2104 err = catch( ldap->modify(dn, ([ config->passwordAttr: ({ 2,crypted }),])) );
2105 authorize_cache->drop( name );
2106 user_cache->drop( name );
2107 LDAP_LOG("sync_password(): %s - %s - %O\n", crypted, dn, ldap->error_string());
2114 void sync_ticket(int event, object user, object caller, string ticket)
2116 last_error = NO_ERROR;
2118 if ( !ldap_activated() ) {
2119 last_error = ERROR_NOT_CONNECTED;
2122 if ( !Config.bool_value(config->sync) )
2124 string name = user->get_user_name();
2125 string dn = make_dn( config->base_dc, config->userAttr + "=" + name );
2126 object ldap = connect();
2127 err = catch( ldap->modify(dn, ([ "userCertificate": ({ 2,ticket }),])) );
2133 bool is_user(string user)
2136 last_error = NO_ERROR;
2137 object ldap = connect();
2138 if ( !objectp(ldap) ) {
2139 last_error = ERROR_NOT_CONNECTED;
2142 object results = ldap->search( "("+config["userAttr"]+"="+user+")" );
2143 bool result = (objectp(results) && results->num_entries() > 0);
2149 bool add_user(string name, string password, object user)
2151 last_error = NO_ERROR;
2152 if ( !ldap_activated() ) {
2153 last_error = ERROR_NOT_CONNECTED;
2156 if ( !Config.bool_value(config->sync) ) return false;
2157 if ( !stringp(config->notfound) || lower_case(config->notfound) != "create" )
2160 // don't add restricted users:
2161 if ( _Persistence->user_restricted( name ) ) return false;
2163 string fullname = user->get_name();
2164 string firstname = user->query_attribute(USER_FIRSTNAME);
2165 string email = user->query_attribute(USER_EMAIL);
2167 mapping attributes = ([
2168 config["userAttr"]: ({ name }),
2169 config["fullnameAttr"]: ({ fullname }),
2170 "objectClass": ({ config["objectName"] }),
2171 config["passwordAttr"]: ({ make_crypt_md5(password) }),
2173 if ( stringp(firstname) && strlen(firstname) > 0 )
2174 config["nameAttr"] = ({ firstname });
2175 if ( stringp(email) && strlen(email) > 0 )
2176 config["emailAttr"] = ({ email });
2178 array requiredAttributes = config["requiredAttr"];
2180 if ( arrayp(requiredAttributes) && sizeof(requiredAttributes) > 0 ) {
2181 foreach(requiredAttributes, string attr) {
2182 if ( zero_type(attributes[attr]) )
2183 attributes[attr] = ({ "-" });
2187 object ldap = connect();
2188 if ( !objectp(ldap) ) {
2189 last_error = ERROR_NOT_CONNECTED;
2192 ldap->add( make_dn(config["userAttr"]+"="+name, config["base_dc"]),
2194 int err = ldap->error_number();
2196 FATAL( "Failed to add user , error is " + ldap->error_string() );
2197 bool result = ldap->error_number() == 0;
2206 * Returns the minimum number of milliseconds a connection request must take
2207 * to appear in the log. Note: if logging is switched off, then this will be
2208 * the maximum value an integer can take.
2210 * @return the minimum threshold (in milliseconds) for connection times logging
2212 int get_log_connection_times_min () {
2213 return log_connection_times_min;
2218 * Returns the maximum number of milliseconds a connection request must take
2219 * to appear in the log. Note: if no upper threshold is used, then this will be
2220 * the maximum value an integer can take.
2222 * @return the maximum threshold (in milliseconds) for connection times logging
2224 int get_log_connection_times_max () {
2225 return log_connection_times_max;
2230 * Activate or deactivate connection times logging and set the threshold for
2233 * @param min_milliseconds the minimum number of milliseconds a connection
2234 * request must take to appear in the log (if -1, then logging will be
2236 * @param max_milliseconds the maximum number of milliseconds a connection
2237 * request must take to appear in the log (if -1, then no upper limit will
2239 * @return true if logging is now active, false if it is now not active
2241 bool set_log_connection_times ( int min_milliseconds, void|int max_milliseconds ) {
2242 if ( min_milliseconds < 0 )
2243 log_connection_times_min = Int.NATIVE_MAX;
2245 log_connection_times_min = min_milliseconds;
2246 if ( zero_type(max_milliseconds) || max_milliseconds < 0 )
2247 log_connection_times_max = Int.NATIVE_MAX;
2249 log_connection_times_max = max_milliseconds;
2250 return log_connection_times_min < Int.NATIVE_MAX &&
2251 log_connection_times_max >= log_connection_times_min;
2256 int get_time_millis () {
2257 array tod = System.gettimeofday();
2258 return tod[0]*1000 + tod[1]/1000;
2265 * Set the log level for authentication logging during runtime.
2266 * The log levels are:
2268 * 1 : failed authentication,
2269 * 2 : successful authentication,
2270 * 3 : failed and successful authentication,
2271 * 5 : detailed failed authentication (with password hashes in the log),
2272 * 6 : detailed successful authentication (with password hashes in the log),
2273 * 7 : detailed failed and successful authentication (with password hashes in
2276 * @param log_level authentication log level
2277 * @return the new log level
2279 int set_authorize_log_level ( int log_level ) {
2280 log_level_auth = log_level;
2281 if ( log_level_auth < 1 ) log_failed_auth = ([ ]);
2282 return log_level_auth;
2287 * Query the authentication log level
2288 * The log levels are:
2290 * 1 : failed authentication,
2291 * 2 : successful authentication,
2292 * 3 : failed and successful authentication,
2293 * 5 : detailed failed authentication (with password hashes in the log),
2294 * 6 : detailed successful authentication (with password hashes in the log),
2295 * 7 : detailed failed and successful authentication (with password hashes in
2298 * @return the authentication log level
2300 int get_authorize_log_level () {
2301 return log_level_auth;
2306 void synchronization_thread () {
2308 int cache_time = config["cacheTime"];
2309 int cache_live_time = config["cacheTime"] * 2; //TODO: make this configurable
2310 foreach ( indices( user_cache ), string identifier ) {
2311 mapping entry = user_cache[ identifier ];
2313 if ( t - entry->last_accessed > cache_live_time ) {
2317 if ( t - entry->last_synchronized > cache_time ) {
2321 foreach ( indices( group_cache ), string identifier ) {
2323 foreach ( indices( authorize_cache ), string identifier ) {
2325 sleep( cacheTime ); //TODO: substract the time needed for sync