ldap._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens, Robert Hinn
2  *
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.
7  *
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.
12  *
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
16  *
17  * $Id: ldap.pike,v 1.15 2010/08/18 20:32:45 astra Exp $
18  */
19 inherit "/kernel/module";
20 #include <configure.h>
21 #include <macros.h>
22 #include <config.h>
23 #include <attributes.h>
24 #include <classes.h>
25 #include <events.h>
26 #include <database.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
31 //! make it work.
32 //!
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
89 //! commas):
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
94 //! passwords)
95 class ldap : public module{
96 public:
97 
98 
99 
100 
101 
102 #define LDAP_DEBUG 1
103 
104 #ifdef LDAP_DEBUG
105 #define LDAP_LOG(s, args...) werror("ldap: "+s+"\n", args)
106 #else
107 #define LDAP_LOG(s, args...)
108 #endif
109 
110 #define LOG_AUTH(s, args...) werror("ldap-auth (%s): "+s+"\n", Calendar.Second(time())->format_time(), args)
111 
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 ); }
113 
114 #define NR_RETRIES 1
115 
116 
117  string sServerURL;
118  mapping config = ([ ]);
119 
120  object charset_decoder;
121  object charset_encoder;
122 
123  object user_cache;
124  object group_cache;
125  object authorize_cache;
126 
127  Thread.Local single_ldap;
128  string last_bind_dn;
129  string last_bind_password_hash;
130  int reconnection_time = 30;
131 
132  bool restrict_access = true;
133  array valid_caller_objects = ({ });
134  array valid_caller_programs = ({ });
135 
136  int log_connection_times_min = Int.NATIVE_MAX;
137  int log_connection_times_max = 0;
138 
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 = ([ ]);
144 
145  object admin_account = 0;
146  array ldap_conflicts = ({ });
147 
148  int last_error = 0;
149 constant NO_ERROR = 0;
150 constant ERROR_UNKNOWN = -1;
151 constant ERROR_NOT_CONNECTED = -2;
152 
153  Thread.Queue invalidate_queue = Thread.Queue();
154 
155 string get_identifier() { return "ldap"; }
156 
157 
158 class LDAPCache {
159 public:
160  mapping by_identifier = ([ ]);
161  int cache_time;
162  function sync_func;
163  function last_modified_func;
164  function multi_sync_func;
165 
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;
175  }
176 
177  array list () {
178  check_access();
179  return indices( by_identifier );
180  }
181 
182  mapping find ( string identifier ) {
183  check_access();
184  return by_identifier[ identifier ];
185  }
186 
187 protected:
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;
193  }
194  }
195 
196 public:
197 
198  mixed fetch ( string identifier, mixed ... args ) {
199  check_access();
200  if ( !stringp(identifier) ) return 0;
201 
202  // first try to update the cache if an update thread is running
203  update_cache();
204 
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 )
210  {
211  int last_modified;
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();
222  }
223  }
224  cache_entry->last_accessed = time();
225  return cache_entry->data;
226  }
227  // need to add object to cache:
228  cache_entry = ([ ]);
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;
233 
234  by_identifier[ identifier ] = cache_entry;
235 
236  if ( functionp(last_modified_func) )
237  cache_entry->last_modified = last_modified_func( identifier );
238 
239  cache_entry->last_synchronized = time();
240  cache_entry->last_accessed = time();
241 
242  return cache_entry->data;
243  }
244 
245  void pre_fetch() {
246  check_access();
247  array userdata = multi_sync_func();
248  foreach(userdata, mapping data) {
249  mapping cache_entry = ([ ]);
250  string identifier;
251 
252  if (sizeof(data) == 0) {
253  continue;
254  }
255 
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;
262 
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();
267  }
268  }
269 
270  int drop ( void|string identifier ) {
271  if ( !has_index( by_identifier, identifier ) )
272  return 0;
273  m_delete( by_identifier, identifier );
274  return 1;
275  }
276 
277  int size() {
278  return sizeof(by_identifier);
279  }
280 }
281 
282 bool ldap_activated ()
283 {
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;
289  return false;
290  }
291  return false;
292 }
293 
294 
295 int get_last_error () {
296  return last_error;
297 }
298 
299 
300 string make_dn ( mixed ... parts ) {
301  string dn = "";
302  foreach ( parts, mixed part ) {
303  if ( stringp(part) && sizeof(part) > 0 ) {
304  if ( sizeof(dn) > 0 && dn[-1] != ',' ) dn += ",";
305  dn += part;
306  }
307  }
308  return dn;
309 }
310 
311 
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) )
318  return UNDEFINED;
319  return mkmapping( groupAttrs, groupClasses );
320 }
321 
322 
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 != "" )
330  return value;
331  }
332  return UNDEFINED;
333 }
334 
335 
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;
344  else return name;
345 }
346 
347 protected:
348  string low_dn_to_group_name ( string dn ) {
349  if ( !stringp(dn) || sizeof(dn)<1 ) return 0;
350  string name;
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 )
362  name += "." + part;
363  else
364  name = part;
365  }
366  }
367  return name;
368 }
369 
370 public:
371 
372 
373 bool check_access () {
374  if ( !restrict_access ) return true;
375  // check objects:
376  if ( search( valid_caller_objects, CALLER ) >= 0 ) return true;
377  // check programs:
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) );
382 }
383 
384 object connect ( void|string user, void|string password, void|bool dont_reconnect ) {
385  check_access();
386  last_error = ERROR_NOT_CONNECTED;
387  if ( !ldap_activated() ) return 0;
388 
389  string bind_dn = "";
390  if ( Config.bool_value( config->bindUser ) && stringp(user) ) {
391  if ( Config.bool_value(config->bindUserWithoutDN) )
392  bind_dn = user;
393  else {
394  bind_dn = make_dn( config->userAttr + "=" + user, config["userdn"], config["base_dc"] );
395  }
396  }
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"];
401  else
402  bind_dn = make_dn( "cn="+config["user"] );
403  password = config["password"];
404  }
405  else { // don't bind at all:
406  bind_dn = 0;
407  }
408 
409  LDAP_LOG("count of open file desciptors: " + sizeof(Stdio.get_all_active_fd()) );
410  object ldap;
411  mixed err = catch {
412  if ( Config.bool_value(config["reuseConnection"]) ||
413  config["reconnectTime"] > 0 ) {
414  if ( objectp(single_ldap) ) {
415  ldap = single_ldap;
416  }
417  }
418  else
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 );
428  }
429  }
430  if ( objectp(ldap) )
431  LDAP_LOG( "new connection: %O", ldap );
432  }
433  };
434  if ( err ) FATAL( "ldap: error while connecting: %O\n", err[0] );
435  if ( !objectp(ldap) ) return 0;
436 
437  single_ldap = ldap;
438 
439  err = catch {
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 );
450  }
451  }
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"]) );
456  };
457  if ( err != 0 ) {
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
466  disconnect( ldap );
467  return 0;
468  }
469  else {
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);
474  return 0;
475  }
476  else
477  return connect( user, password, true ); // try to reconnect once
478  }
479  }
480  last_error = NO_ERROR;
481  return ldap;
482 }
483 
484 
485 void disconnect ( object ldap, void|bool force_disconnect ) {
486  check_access();
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;
492  single_ldap = 0;
493  }
494  last_bind_dn = 0;
495  last_bind_password_hash = 0;
496 
497  //removed for pike 7.8: method query_fd() on ldap no longer exists.
498 // int fd = ldap->query_fd();
499  destruct( ldap );
500 // if ( fd > 600 ) {
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]" );
508 // }
509 // LDAP_LOG( "connection closed (file descriptor was %d)", fd );
510 }
511 
512 
513 mapping get_config ()
514 {
515  check_access();
516  return config;
517 }
518 
519 
520 object get_user_cache () {
521  if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
522  return user_cache;
523 }
524 
525 
526 object get_group_cache () {
527  if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
528  return group_cache;
529 }
530 
531 
532 object get_authorize_cache () {
533  if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
534  return authorize_cache;
535 }
536 
537 
538 mapping get_failed_authorize () {
539  if ( !GROUP("admin")->is_member( this_user() ) ) return 0;
540  return log_failed_auth;
541 }
542 
543 protected:
544  int hashcode(map userdata)
545 {
546  int hashvalue = 0;
547  if (!mappingp(userdata))
548  return 0;
549 
550  foreach(values(userdata), mixed v) {
551  if (stringp(v)) {
552  hashvalue += hash(v);
553  }
554  }
555  return hashvalue;
556 }
557 
558 public:
559 
560 protected:
561  void update_cache() {
562  while ( 1 ) {
563  // first check if the time is right
564  int startUpdate = config->updateCacheStartHour;
565  if (!intp(startUpdate))
566  startUpdate = 5;
567 
568  int hour = 0;
569 
570  sleep(1800);
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",
574  hour, startUpdate);
575 
576  if ( hour == startUpdate) {
577  while ( 1 ) {
578  int updateInterval = config->updateCacheInterval;
579 
580  int time_start = get_time_millis();
581  object ldap = connect();
582  array(map) users = fetch_users_internal();
583  disconnect(ldap);
584  foreach(users, mapping user_data) {
585  if (sizeof(user_data) == 0)
586  continue;
587 
588  user_data = fix_charset( user_data );
589 
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",
593  user_data);
594  invalidate_queue->write(user_data);
595  }
596  }
597  MESSAGE("Invalidating LDAP cache in %d ms, %d updates",
598  get_time_millis() - time_start, invalidate_queue->size());
599  sleep(updateInterval*3600);
600  }
601  }
602  }
603 }
604 
605 public:
606 
607 
608 private:
609  void init_module()
610 {
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" });
615 
616  last_error = NO_ERROR;
617  config = Config.read_config_file( _Server.get_config_dir()+"/modules/ldap.cfg", "ldap" );
618  if ( !mappingp(config) ) {
619  config = ([ ]);
620  MESSAGE("LDAP Service not started - missing configuration !");
621  last_error = ERROR_NOT_CONNECTED;
622  return; // ldap not started !
623  }
624  if ( !ldap_activated() ) {
625  MESSAGE("LDAP deactivated.");
626  last_error = ERROR_NOT_CONNECTED;
627  return; // ldap deactivated
628  }
629 
630  LDAP_LOG("configuration is %O", config);
631 
632  // charset:
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",
638  config["charset"] );
639  }
640  else {
641  charset_decoder = 0;
642  charset_encoder = 0;
643  }
644 
645  // caches:
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 );
653 
654  // log levels:
655  if ( stringp(config["logAuthorization"]) ) {
656  array a = Config.array_value( config["logAuthorization"] );
657  foreach ( a, string s ) {
658  switch ( lower_case(s) ) {
659  case "failed":
660  case "failure":
661  log_level_auth |= LOG_AUTH_FAILURE;
662  break;
663  case "succeeded":
664  case "success":
665  log_level_auth |= LOG_AUTH_SUCCESS;
666  break;
667  case "detailed":
668  case "details":
669  log_level_auth |= LOG_AUTH_DETAILS;
670  break;
671  }
672  }
673  }
674 
675  // restricted access:
676  if ( !zero_type(config->restrictAccess) &&
677  !Config.bool_value(config->restrictAccess) )
678  restrict_access = false;
679  else
680  restrict_access = true;
681  valid_caller_objects += ({ user_cache, group_cache, authorize_cache });
682 
683  if ( stringp(config->notfound) && lower_case(config->notfound) == "create"
684  && !config->objectName )
685  steam_error("objectName configuration missing !");
686 
687  if ( Config.bool_value( config->sync ) ) {
688  // if our main dc does not exist - create it
689  object ldap = connect();
690  mixed err = catch {
691  ldap->add( config->base_dc, ([
692  "objectclass": ({ "dcObject", "organization" }),
693  "o": "sTeam Authorization Directory",
694  "dc": "steam" ]) );
695  };
696  if ( err ) FATAL( "ldap: failed to create main dc: %O\n", err[0] );
697  disconnect( ldap );
698  }
699  else {
700  // check whether the connection is working
701  object ldap;
702  string server;
703  mixed err = catch {
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;
708  server = tmp_server;
709  ldap = Protocols.LDAP.client( server );
710  if ( objectp(ldap) ) break;
711  }
712  }
713  };
714  if ( err ) FATAL( "ldap: error while connecting: %O\n", err[0] );
715  if ( objectp(ldap) ) {
716  MESSAGE( "LDAP: connected to %s", server );
717  disconnect( ldap );
718  }
719  else {
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 );
723  }
724  }
725 
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);
731  }
732  if (intp(config->updateCacheInterval)) {
733  start_thread(update_cache);
734  }
735 }
736 
737 public:
738 
739 
740 void load_module()
741 {
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);
747  }
748 }
749 
750 
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() );
755  }
756 }
757 
758 
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() );
763  }
764 }
765 
766 
767 private:
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" );
776  return true;
777 }
778 
779 public:
780 
781 
782 protected:
783  mixed map_results(object results)
784 {
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);
790 
791  if (!mappingp(res)) {
792  LDAP_LOG("unable to map result!");
793  res = ([ ]);
794  }
795  foreach(indices(res), string attr) {
796  if ( arrayp(res[attr]) ) {
797  if ( sizeof(res[attr]) == 1 )
798  data[attr] = res[attr][0];
799  else
800  data[attr] = res[attr];
801  }
802  }
803  if ( results->num_entries() == 1 )
804  return data;
805  result += ({ data });
806  }
807  return result;
808 }
809 
810 public:
811 
812 
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 );
820 }
821 
822 
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 );
827 }
828 
829 
830 array(mapping) search_data ( string search_str, array result_attributes,
831  void|string base_dn, void|string user, void|string pass,
832  void|int nr_try ) {
833  check_access();
834  last_error = NO_ERROR;
835  if ( !ldap_activated() ) {
836  last_error = ERROR_NOT_CONNECTED;
837  return UNDEFINED;
838  }
839 
840  if ( !stringp(search_str) || sizeof(search_str) < 1 )
841  return UNDEFINED;
842 
843  mixed udata;
844  object results;
845 
846  int time_start = get_time_millis();
847 
848  object ldap;
849  if ( Config.bool_value(config->bindUser) && stringp(user) && stringp(pass) )
850  ldap = connect(user, pass);
851  else
852  ldap = connect();
853 
854  LDAP_LOG("searching data %s (user %O) : ldap is %O", search_str, user, ldap);
855 
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 );
861  }
862  last_error = ERROR_NOT_CONNECTED;
863  return UNDEFINED;
864  }
865 
866  LDAP_LOG("searching data in LDAP: %s\n", search_str);
867  if ( base_dn ) {
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 );
874  }
875  FATAL( "ldap: error searching data: %s (#%d)\n",
876  ldap->error_string(), ldap->error_number() );
877  }
878  }
879 
880  mixed err = catch( results = ldap->search( fix_query_string(search_str), result_attributes ) );
881  if ( err ) {
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 );
886  }
887  FATAL( "ldap: error searching data: %s\n", err[0] );
888  disconnect( ldap );
889  last_error = ERROR_UNKNOWN;
890  return UNDEFINED;
891  }
892 
893  LOG_TIME( time_start, "search_data( %s )", search_str );
894 
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 );
900  }
901  FATAL( "ldap: Error while searching data: %s (#%d)\n",
902  ldap->error_string(), ldap->error_number() );
903  disconnect( ldap );
904  last_error = ERROR_UNKNOWN;
905  return UNDEFINED;
906  }
907 
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 );
913  }
914  FATAL("ldap: Invalid results while searching data.\n");
915  disconnect( ldap );
916  last_error = ERROR_UNKNOWN;
917  return UNDEFINED;
918  }
919 
920  if ( results->num_entries() == 0 ) {
921  LDAP_LOG("No matching data found in LDAP directory: %s", search_str);
922  disconnect( ldap );
923  return ({ });
924  }
925 
926  udata = map_results( results );
927  if ( mappingp(udata) ) udata = ({ udata });
928 
929  disconnect( ldap );
930 
931  LOG_TIME( time_start, "search_data( %s )", search_str );
932 
933  if ( !arrayp(udata) ) return 0;
934  else return udata;
935 }
936 
937 
938 array search_users ( mapping terms, bool any,
939  void|string user, void|string pass ) {
940  check_access();
941  if ( !mappingp(terms) || sizeof(terms)<1 ) {
942  LDAP_LOG( "search_users: invalid terms: %O\n", terms );
943  last_error = ERROR_UNKNOWN;
944  return UNDEFINED;
945  }
946 
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;
952  switch ( attr ) {
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;
957  }
958  search_str += "(" + attr + "=" + replace( value, ([ "(":"", ")":"", "=":"", ":":"" ]) ) + ")";
959  }
960  if ( any ) search_str = "(&(objectclass=" + config->userClass + ")(|" +
961  search_str + "))";
962  else search_str = "(&(objectclass=" + config->userClass + ")" +
963  search_str + ")";
964 
965  string base_dn;
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 );
969  array users = ({ });
970  foreach ( result, mixed res ) {
971  if ( mappingp(res) ) {
972  res = res[ config->userAttr ];
973  if ( stringp(res) && sizeof(res) > 0 )
974  users += ({ res });
975  }
976  }
977  return users;
978 }
979 
980 
981 /**
982  * Returns an array of distinct names of all sub-groups of a group.
983  *
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
988  */
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;
994  return UNDEFINED;
995  }
996 
997  if ( !stringp(dn) || sizeof(dn)<1 ) {
998  FATAL( "LDAP: get_subgroups: invalid dn: %O\n", dn );
999  last_error = ERROR_UNKNOWN;
1000  return UNDEFINED;
1001  }
1002 
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;
1007  return UNDEFINED;
1008  }
1009 
1010  string group_identifier = dn_to_group_name( dn );
1011  string group_name = (group_identifier / ".")[-1];
1012 
1013  object results;
1014 
1015  int time_start = get_time_millis();
1016 
1017  object ldap = connect();
1018 
1019  if ( !objectp(ldap) ) {
1020  if ( nr_try < NR_RETRIES ) {
1021  disconnect( ldap, true );
1022  return get_sub_groups( dn, recursive, nr_try+1 );
1023  }
1024  last_error = ERROR_NOT_CONNECTED;
1025  return UNDEFINED;
1026  }
1027 
1028  int old_scope = -1;
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 );
1038  }
1039  FATAL( "ldap: error fetching subgroups of %s : %s (#%d)\n", dn,
1040  ldap->error_string(), ldap->error_number() );
1041  }
1042 
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) ) );
1049  if ( err ) {
1050  if ( nr_try < NR_RETRIES ) {
1051  disconnect( ldap, true );
1052  return get_sub_groups( dn, recursive, nr_try+1 );
1053  }
1054  FATAL( "ldap: error fetching subgroups of %s : %s\n", dn, err[0] );
1055  disconnect( ldap );
1056  last_error = ERROR_UNKNOWN;
1057  return UNDEFINED;
1058  }
1059 
1060  LOG_TIME( time_start, "get_subgroups( %s )", dn );
1061 
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 );
1066  }
1067  FATAL( "ldap: Error while fetching subgroups of %s : %s (#%d)\n", dn,
1068  ldap->error_string(), ldap->error_number() );
1069  disconnect( ldap );
1070  last_error = ERROR_UNKNOWN;
1071  return UNDEFINED;
1072  }
1073 
1074  if ( !objectp(results) ) {
1075  if ( nr_try < NR_RETRIES ) {
1076  disconnect( ldap, true );
1077  return get_sub_groups( dn, recursive, nr_try+1 );
1078  }
1079  FATAL("ldap: Invalid results while fetching subgroups of %s .\n", dn);
1080  disconnect( ldap );
1081  last_error = ERROR_UNKNOWN;
1082  return UNDEFINED;
1083  }
1084 
1085  if ( results->num_entries() == 0 ) {
1086  disconnect( ldap );
1087  return ({ });
1088  }
1089 
1090  array data = map_results(results);
1091  array dns = ({ });
1092  foreach ( data, mapping subgroup ) {
1093  string sub_dn = subgroup["dn"];
1094  if ( stringp(sub_dn) && sizeof(sub_dn) > 0 ) dns += ({ sub_dn });
1095  }
1096  LDAP_LOG( "group %s has %d subgroups", dn, sizeof(dns) );
1097 
1098  if ( old_scope < 0 ) catch( ldap->set_scope( 2 ) );
1099  else catch( ldap->set_scope( old_scope ) );
1100  disconnect( ldap );
1101 
1102  return dns;
1103 }
1104 
1105 
1106 array search_groups ( mapping terms, bool any,
1107  void|string user, void|string pass ) {
1108  check_access();
1109  if ( !mappingp(terms) || sizeof(terms)<1 ) {
1110  LDAP_LOG( "search_groups: invalid terms: %O\n", terms );
1111  last_error = ERROR_UNKNOWN;
1112  return UNDEFINED;
1113  }
1114 
1115  mapping group_spec = get_group_spec();
1116  if ( !mappingp(group_spec) ) {
1117  LDAP_LOG( "search_groups: cannot search: no groupAttr/groupClass " +
1118  "configured" );
1119  last_error = ERROR_UNKNOWN;
1120  return UNDEFINED;
1121  }
1122 
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=" +
1134  groupClass + "))";
1135  }
1136  if ( sizeof(group_spec) > 1 )
1137  name_search_str += "(|" + name_search_str + ")";
1138  search_str += name_search_str;
1139  }
1140  else
1141  search_str += "(" + attr + "=" + value + ")";
1142  }
1143  if ( any ) search_str = "(|" + search_str + ")";
1144  else search_str = "(&" + search_str + ")";
1145 
1146  string base_dn;
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) )
1151  return ({ });
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"] == "" )
1157  continue;
1158  groups += ({ dn_to_group_name( res["dn"] ) });
1159  }
1160  }
1161  return groups;
1162 }
1163 
1164 
1165 mapping search_user ( string search_str, void|string user, void|string pass,
1166  void|int nr_try )
1167 {
1168  check_access();
1169  last_error = NO_ERROR;
1170  if ( !ldap_activated() ) {
1171  last_error = ERROR_NOT_CONNECTED;
1172  return UNDEFINED;
1173  }
1174 
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;
1178  return UNDEFINED;
1179  }
1180 
1181  mapping udata = ([ ]);
1182  object results;
1183 
1184  int time_start = get_time_millis();
1185 
1186  object ldap;
1187  if ( Config.bool_value(config->bindUser) && stringp(user) && stringp(pass) )
1188  ldap = connect(user, pass);
1189  else
1190  ldap = connect();
1191 
1192  LDAP_LOG("searching %s (user %O) : ldap is %O", search_str, user, ldap);
1193 
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 );
1198  }
1199  last_error = ERROR_NOT_CONNECTED;
1200  return UNDEFINED;
1201  }
1202 
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 );
1210  }
1211  FATAL( "ldap: error searching user: %s (#%d)\n",
1212  ldap->error_string(), ldap->error_number() );
1213  }
1214  }
1215 
1216  mixed err = catch( results = ldap->search( fix_query_string(search_str) ) );
1217  if ( err ) {
1218  if ( nr_try < NR_RETRIES ) {
1219  disconnect( ldap, true );
1220  return search_user( search_str, user, pass, nr_try+1 );
1221  }
1222  FATAL( "ldap: error searching user: %s\n", err[0] );
1223  disconnect( ldap );
1224  last_error = ERROR_UNKNOWN;
1225  return UNDEFINED;
1226  }
1227 
1228  LOG_TIME( time_start, "search_user( %s )", search_str );
1229 
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 );
1234  }
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() );
1238  disconnect( ldap );
1239  last_error = ERROR_UNKNOWN;
1240  return UNDEFINED;
1241  }
1242 
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 );
1247  }
1248  FATAL("ldap: Invalid results while searching user.\n");
1249  disconnect( ldap );
1250  last_error = ERROR_UNKNOWN;
1251  return UNDEFINED;
1252  }
1253 
1254  if ( results->num_entries() == 0 ) {
1255  LDAP_LOG("User not found in LDAP directory: %s", search_str);
1256  disconnect( ldap );
1257  return UNDEFINED;
1258  }
1259 
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)) );
1263 
1264  disconnect( ldap );
1265 
1266  LOG_TIME( time_start, "search_user( %s )", search_str );
1267 
1268  return udata;
1269 }
1270 
1271 
1272 mapping search_group ( string search_str, void|int nr_try )
1273 {
1274  check_access();
1275  last_error = NO_ERROR;
1276  if ( !ldap_activated() ) {
1277  last_error = ERROR_NOT_CONNECTED;
1278  return UNDEFINED;
1279  }
1280 
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;
1284  return UNDEFINED;
1285  }
1286 
1287  mapping gdata = ([ ]);
1288  object results;
1289 
1290  if ( !mappingp(get_group_spec()) ) {
1291  last_error = ERROR_UNKNOWN;
1292  return UNDEFINED;
1293  }
1294 
1295  int time_start = get_time_millis();
1296 
1297  object ldap = connect();
1298 
1299  if ( !objectp(ldap) ) {
1300  if ( nr_try < NR_RETRIES ) {
1301  disconnect( ldap, true );
1302  return search_group( search_str, nr_try+1 );
1303  }
1304  last_error = ERROR_NOT_CONNECTED;
1305  return UNDEFINED;
1306  }
1307 
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 );
1315  }
1316  FATAL( "ldap: error searching group: %s (#%d)\n",
1317  ldap->error_string(), ldap->error_number() );
1318  }
1319  }
1320 
1321  mixed err = catch( results = ldap->search( fix_query_string(search_str) ) );
1322  if ( err ) {
1323  if ( nr_try < NR_RETRIES ) {
1324  disconnect( ldap, true );
1325  return search_group( search_str, nr_try+1 );
1326  }
1327  FATAL( "ldap: error searching group: %s\n", err[0] );
1328  disconnect( ldap );
1329  last_error = ERROR_UNKNOWN;
1330  return UNDEFINED;
1331  }
1332 
1333  LOG_TIME( time_start, "search_group( %s )", search_str );
1334 
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 );
1339  }
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() );
1343  disconnect( ldap );
1344  last_error = ERROR_UNKNOWN;
1345  return UNDEFINED;
1346  }
1347 
1348  if ( !objectp(results) ) {
1349  if ( nr_try < NR_RETRIES ) {
1350  disconnect( ldap, true );
1351  return search_group( search_str, nr_try+1 );
1352  }
1353  FATAL("ldap: Invalid results while searching group.\n");
1354  disconnect( ldap );
1355  last_error = ERROR_UNKNOWN;
1356  return UNDEFINED;
1357  }
1358 
1359  if ( results->num_entries() == 0 ) {
1360  LDAP_LOG("Group not found in LDAP directory: %s", search_str);
1361  disconnect( ldap );
1362  return UNDEFINED;
1363  }
1364 
1365  gdata = map_results(results);
1366  LDAP_LOG( "group %s has dn: %O", search_str, gdata["dn"] );
1367 
1368  disconnect( ldap );
1369 
1370  return gdata;
1371 }
1372 
1373 
1374 int user_last_modified ( string identifier, void|string pass )
1375 {
1376  check_access();
1377  //TODO: query ldap attribute modifyTimestamp
1378  return 0;
1379 }
1380 
1381 
1382 mapping fetch_user ( string identifier, void|string pass )
1383 {
1384  check_access();
1385  int time_start = get_time_millis();
1386  last_error = NO_ERROR;
1387  if ( !ldap_activated() ) {
1388  last_error = ERROR_NOT_CONNECTED;
1389  return 0;
1390  }
1391  if ( !stringp(identifier) ) {
1392  last_error = ERROR_UNKNOWN;
1393  return 0;
1394  }
1395 
1396  LDAP_LOG("fetch_user(%s) %d", identifier, stringp(pass));
1397 
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 );
1401  return result;
1402 }
1403 
1404 
1405 protected:
1406  mapping fetch_user_internal ( string identifier, void|string pass )
1407 {
1408  check_access();
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) )
1415  return UNDEFINED;
1416  if ( lower_case(result[config->userAttr]) != lower_case(identifier) )
1417  return UNDEFINED;
1418  else
1419  return result;
1420 }
1421 
1422 public:
1423 
1424 
1425 protected:
1426  array(mapping) fetch_users_internal ()
1427 {
1428  check_access();
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) )
1435  return ({ });
1436 
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);
1440  disconnect( ldap );
1441  return ({ });
1442  }
1443  LDAP_LOG("Found %d results", results->num_entries());
1444  disconnect( ldap );
1445  return map_results( results );
1446 }
1447 
1448 public:
1449 
1450 int group_last_modified ( string identifier )
1451 {
1452  check_access();
1453  //TODO: query ldap attribute modifyTimestamp
1454  return 0;
1455 }
1456 
1457 
1458 mapping fetch_group ( string identifier )
1459 {
1460  check_access();
1461  int time_start = get_time_millis();
1462  last_error = NO_ERROR;
1463  if ( !ldap_activated() ) {
1464  last_error = ERROR_NOT_CONNECTED;
1465  return 0;
1466  }
1467  if ( !stringp(identifier) || sizeof(identifier) < 1 ) {
1468  last_error = ERROR_UNKNOWN;
1469  return 0;
1470  }
1471 
1472  if ( !mappingp(get_group_spec()) ) {
1473  last_error = ERROR_UNKNOWN;
1474  return 0;
1475  }
1476 
1477  LDAP_LOG("fetch_group(%s)", identifier);
1478  mapping result = group_cache->fetch( identifier );
1479  LOG_TIME( time_start, "fetch_group( %s )", identifier );
1480  return result;
1481 }
1482 
1483 
1484 protected:
1485  mapping fetch_group_internal ( string identifier )
1486 {
1487  check_access();
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;
1494  return UNDEFINED;
1495  }
1496 
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] + "))";
1502  }
1503  search_str += ")";
1504  // sub-groups by structural method are already handled by that query
1505 
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] + "))";
1512  }
1513 
1514  mixed results = fix_charset( search_group( search_str ) );
1515 
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 ) {
1521  results = m;
1522  break;
1523  }
1524  }
1525  }
1526 
1527  // if there is more than one result, only return one:
1528  mapping result;
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) )
1535  return UNDEFINED;
1536  return result;
1537 }
1538 
1539 public:
1540 
1541 mixed fetch ( string dn, string pattern )
1542 {
1543  return fetch_scope( dn, pattern, 2 );
1544 }
1545 
1546 mixed fetch_scope ( string dn, string pattern, int scope )
1547 {
1548  check_access();
1549  last_error = NO_ERROR;
1550  if ( !ldap_activated() ) {
1551  last_error = ERROR_NOT_CONNECTED;
1552  return 0;
1553  }
1554  if ( !stringp(dn) || !stringp(pattern) ) {
1555  last_error = ERROR_UNKNOWN;
1556  return 0;
1557  }
1558 
1559  // caller must be module...
1560  if ( !_Server->is_module( CALLER ) )
1561  steam_error( "Access for non-module denied !" );
1562 
1563  object results;
1564 
1565  object ldap = connect();
1566 
1567  if ( !objectp(ldap) ) {
1568  last_error = ERROR_NOT_CONNECTED;
1569  return UNDEFINED;
1570  }
1571 
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) );
1576  else
1577  ldap->set_basedn( fix_query_string(make_dn( dn, config->base_dc )) );
1578  }
1579  else
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() );
1584 
1585  mixed err = catch( results = ldap->search( fix_query_string(pattern) ) );
1586  if ( err ) {
1587  FATAL( "ldap: error fetching: %s\n", err[0] );
1588  disconnect( ldap );
1589  last_error = ERROR_UNKNOWN;
1590  return UNDEFINED;
1591  }
1592 
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() );
1597  disconnect( ldap );
1598  last_error = ERROR_UNKNOWN;
1599  return UNDEFINED;
1600  }
1601 
1602  if ( !objectp(results) ) {
1603  FATAL("ldap: Invalid results while fetching.\n");
1604  disconnect( ldap );
1605  last_error = ERROR_UNKNOWN;
1606  return UNDEFINED;
1607  }
1608 
1609  if ( results->num_entries() == 0 ) {
1610  LDAP_LOG("No results when fetching: %s", pattern);
1611  disconnect( ldap );
1612  return UNDEFINED;
1613  }
1614 
1615  disconnect( ldap );
1616 
1617  return map_results( results );
1618 }
1619 
1620 
1621 array fetch_url ( string url, string additional_filter ) {
1622  check_access();
1623  last_error = NO_ERROR;
1624  if ( !ldap_activated() ) {
1625  last_error = ERROR_NOT_CONNECTED;
1626  return 0;
1627  }
1628  if ( !stringp(url) || sizeof(url) < 1 ) {
1629  last_error = ERROR_UNKNOWN;
1630  return 0;
1631  }
1632  // caller must be module...
1633  if ( !_Server->is_module( CALLER ) )
1634  steam_error( "Access for non-module denied !" );
1635 
1636  object results;
1637 
1638  object ldap = connect();
1639 
1640  if ( !objectp(ldap) ) {
1641  last_error = ERROR_NOT_CONNECTED;
1642  return 0;
1643  }
1644 
1645  mapping parts = ldap->parse_url( url );
1646  if ( !mappingp(parts) ) {
1647  last_error = ERROR_UNKNOWN;
1648  return 0;
1649  }
1650 
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..] * ",";
1660  }
1661  else if ( sizeof(filter) < 1 && sizeof(basedn) < 1 ) {
1662  last_error = ERROR_UNKNOWN;
1663  return 0;
1664  }
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 + ")";
1673  }
1674  mixed err = catch( results = ldap->search( fix_query_string(filter) ) );
1675  if ( err ) {
1676  FATAL( "ldap: error fetching url: %s\n", err[0] );
1677  disconnect( ldap );
1678  last_error = ERROR_UNKNOWN;
1679  return 0;
1680  }
1681 
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() );
1686  disconnect( ldap );
1687  last_error = ERROR_UNKNOWN;
1688  return 0;
1689  }
1690 
1691  if ( !objectp(results) ) {
1692  FATAL("ldap: Invalid results while fetching url.\n");
1693  disconnect( ldap );
1694  last_error = ERROR_UNKNOWN;
1695  return 0;
1696  }
1697 
1698  if ( results->num_entries() == 0 ) {
1699  LDAP_LOG("No results when fetching url: %s", url);
1700  disconnect( ldap );
1701  return ({ });
1702  }
1703 
1704  disconnect( ldap );
1705 
1706  mixed res = map_results( results );
1707  if ( mappingp(res) ) res = ({ res });
1708  return res;
1709 }
1710 
1711 
1712 string fix_query_string ( string s )
1713 {
1714  if ( !stringp(s) ) return s;
1715  string s2 = "";
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] );
1719  }
1720  return s2;
1721 }
1722 
1723 
1724 mixed fix_charset ( string|mapping|array v )
1725 {
1726  if ( zero_type(v) ) return UNDEFINED;
1727  if ( !objectp(charset_encoder) || !objectp(charset_decoder) ) return v;
1728  if ( stringp(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 );
1733  return tmp;
1734  }
1735  else if ( arrayp(v) ) {
1736  array tmp = ({ });
1737  foreach ( v, mixed i )
1738  tmp += ({ fix_charset(i) });
1739  return tmp;
1740  }
1741  else if ( mappingp(v) ) {
1742  mapping tmp = ([ ]);
1743  foreach ( indices(v), mixed i )
1744  tmp += ([ fix_charset(i) : fix_charset(v[i]) ]);
1745  return tmp;
1746  }
1747  else return UNDEFINED;
1748 }
1749 
1750 
1751 protected:
1752  bool check_password(string pass, string user_pw)
1753 {
1754  if ( !stringp(pass) || !stringp(user_pw) )
1755  return false;
1756 
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) );
1763  }
1764 
1765 public:
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);
1770  }
1771  if ( strlen(user_pw) < 3 || user_pw[0..2] != "$1$" )
1772  return crypt(pass, user_pw); // normal crypt check
1773 
1774  return verify_crypt_md5(pass, user_pw);
1775 }
1776 
1777 
1778 bool authorize_ldap ( object user, string pass )
1779 {
1780  return authenticate_user( user, pass );
1781 }
1782 
1783 
1784 bool authenticate_user ( object user, string pass )
1785 {
1786  check_access();
1787  int time_start = get_time_millis();
1788  last_error = NO_ERROR;
1789  if ( !ldap_activated() ) {
1790  last_error = ERROR_NOT_CONNECTED;
1791  return false;
1792  }
1793 
1794  if ( !objectp(user) )
1795  steam_error("User object expected for authentication !");
1796 
1797  if ( !stringp(pass) || sizeof(pass)<1 ) {
1798  last_error = ERROR_UNKNOWN;
1799  return false;
1800  }
1801 
1802  string uname = user->get_user_name();
1803 
1804  // don't authenticate restricted users:
1805  if ( _Persistence->user_restricted( uname ) ) return false;
1806 
1807  string cached = authorize_cache->fetch( uname, pass );
1808 
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 );
1819  log_auth = true;
1820  }
1821  }
1822  if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1823  LOG_AUTH( "%s authorized by ldap cache (crc: %d)",
1824  uname, Gz.crc32(pass) );
1825  log_auth = true;
1826  }
1827  return true;
1828  }
1829 
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 );
1835  }
1836  LOG_AUTH( "%s not authorized by ldap cache (password check) (crc: %d)",
1837  uname, Gz.crc32(pass) );
1838  }
1839  LDAP_LOG("user %s found in LDAP cache - password failed: %O",
1840  uname, cached);
1841  LOG_TIME( time_start, "authenticate_user[failed]( %s )", uname );
1842  return false;
1843 }
1844 
1845 
1846 protected:
1847  string authenticate_user_internal ( string uname, string pass,
1848  void|int nr_try )
1849 {
1850  int time_start = get_time_millis();
1851 
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 );
1861  }
1862  }
1863  if ( objectp(ldap) ) {
1864  disconnect( 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] );
1873  log_auth = true;
1874  }
1875  if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1876  LOG_AUTH( "%s authorized via ldap bind", uname );
1877  log_auth = true;
1878  }
1879  if ( log_auth && (log_level_auth & LOG_AUTH_DETAILS) )
1880  LOG_AUTH( "%s password crc32 : %d", uname, Gz.crc32( pass ) );
1881  return cached_pw;
1882  }
1883  else {
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 ) );
1889  }
1890  }
1891  }
1892 
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);
1896 
1897  LOG_TIME( time_start, "authenticate_user[fetch]( %s )", uname );
1898 
1899  if ( mappingp(udata) ) {
1900  object user = USER( uname );
1901  string dn = udata["dn"];
1902  if ( !stringp(dn) ) dn = "";
1903 
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 });
1908  if ( notify_admin(
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." ) );
1916  else
1917  FATAL( "ldap: user conflict: %s in sTeam vs. %s in LDAP\n", uname, dn );
1918  return 0;
1919  }
1920  }
1921  else if ( search(ldap_conflicts,uname) >= 0 )
1922  ldap_conflicts -= ({ uname });
1923 
1924  string ldap_password = udata[config->passwordAttr] || "";
1925  if ( stringp(config->passwordPrefix) && sizeof(config->passwordPrefix)>0 )
1926  ldap_password = config->passwordPrefix + ldap_password;
1927 
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] );
1940  log_auth = true;
1941  }
1942  if ( log_level_auth & LOG_AUTH_SUCCESS ) {
1943  LOG_AUTH( "%s authorized via password", uname );
1944  log_auth = true;
1945  }
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 );
1949  return cached_pw;
1950  }
1951  else {
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 );
1957  }
1958  LDAP_LOG("user %s found in LDAP directory - password failed!", uname);
1959  return 0;
1960  }
1961  }
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 );
1968  }
1969  return 0;
1970 }
1971 
1972 public:
1973 
1974 object sync_user(string name)
1975 {
1976  check_access();
1977  last_error = NO_ERROR;
1978  if ( !ldap_activated() ) {
1979  last_error = ERROR_NOT_CONNECTED;
1980  return 0;
1981  }
1982 
1983  // don't sync restricted users:
1984  if ( _Persistence->user_restricted( name ) )
1985  return 0;
1986 
1987  mapping udata = fetch_user( name );
1988  if ( !mappingp(udata) )
1989  return 0;
1990 
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],
2005  ]) ) )
2006  FATAL( "LDAP: Could not sync user attributes with ldap for \"%s\".\n", name );
2007  } else {
2008  // create new user to match LDAP user
2009  object factory = get_factory(CLASS_USER);
2010  user = factory->execute( ([
2011  "name" : name,
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],
2017  ]) );
2018  user->set_user_password( ldap_password, 1 );
2019  user->activate_user( factory->get_activation() );
2020  }
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+")");
2026  }
2027  }
2028 
2029  return user;
2030 }
2031 
2032 object sync_group(string name)
2033 {
2034  check_access();
2035  last_error = NO_ERROR;
2036  if ( !ldap_activated() ) {
2037  last_error = ERROR_NOT_CONNECTED;
2038  return 0;
2039  }
2040 
2041  // don't syncronize restricted groups:
2042  if ( _Persistence->group_restricted( name ) )
2043  return 0;
2044 
2045  mapping gdata = fetch_group( name );
2046  if ( !mappingp(gdata) )
2047  return 0;
2048 
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],
2057  ]) );
2058  } else {
2059  // create new group to match LDAP group
2060  object factory = get_factory(CLASS_GROUP);
2061  group = factory->execute( ([
2062  "name": name,
2063  "OBJ_DESC" : gdata[config->descriptionAttr],
2064  ]) );
2065  }
2066  return group;
2067 }
2068 
2069 protected:
2070  void sync_password(int event, object user, object caller)
2071 {
2072  if ( !Config.bool_value(config->sync) )
2073  return;
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());
2080 
2081  object ldap;
2082  string dn;
2083 
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);
2088  else
2089  dn = make_dn(config->userAttr+"="+name, config->base_dc);
2090  }
2091  else if ( ldap = connect() ) {
2092  dn = make_dn( config->base_dc, config->userAttr+"="+name );
2093  }
2094 
2095  if ( !stringp(dn) ) {
2096  LDAP_LOG("sync_password(): no dn");
2097  disconnect( ldap );
2098  return;
2099  }
2100 
2101  mixed err;
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());
2108  disconnect( ldap );
2109 }
2110 
2111 public:
2112 
2113 protected:
2114  void sync_ticket(int event, object user, object caller, string ticket)
2115 {
2116  last_error = NO_ERROR;
2117  mixed err;
2118  if ( !ldap_activated() ) {
2119  last_error = ERROR_NOT_CONNECTED;
2120  return;
2121  }
2122  if ( !Config.bool_value(config->sync) )
2123  return;
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 }),])) );
2128  disconnect( ldap );
2129 }
2130 
2131 public:
2132 
2133 bool is_user(string user)
2134 {
2135  check_access();
2136  last_error = NO_ERROR;
2137  object ldap = connect();
2138  if ( !objectp(ldap) ) {
2139  last_error = ERROR_NOT_CONNECTED;
2140  return false;
2141  }
2142  object results = ldap->search( "("+config["userAttr"]+"="+user+")" );
2143  bool result = (objectp(results) && results->num_entries() > 0);
2144  disconnect( ldap );
2145  return result;
2146 }
2147 
2148 protected:
2149  bool add_user(string name, string password, object user)
2150 {
2151  last_error = NO_ERROR;
2152  if ( !ldap_activated() ) {
2153  last_error = ERROR_NOT_CONNECTED;
2154  return false;
2155  }
2156  if ( !Config.bool_value(config->sync) ) return false;
2157  if ( !stringp(config->notfound) || lower_case(config->notfound) != "create" )
2158  return false;
2159 
2160  // don't add restricted users:
2161  if ( _Persistence->user_restricted( name ) ) return false;
2162 
2163  string fullname = user->get_name();
2164  string firstname = user->query_attribute(USER_FIRSTNAME);
2165  string email = user->query_attribute(USER_EMAIL);
2166 
2167  mapping attributes = ([
2168  config["userAttr"]: ({ name }),
2169  config["fullnameAttr"]: ({ fullname }),
2170  "objectClass": ({ config["objectName"] }),
2171  config["passwordAttr"]: ({ make_crypt_md5(password) }),
2172  ]);
2173  if ( stringp(firstname) && strlen(firstname) > 0 )
2174  config["nameAttr"] = ({ firstname });
2175  if ( stringp(email) && strlen(email) > 0 )
2176  config["emailAttr"] = ({ email });
2177 
2178  array requiredAttributes = config["requiredAttr"];
2179 
2180  if ( arrayp(requiredAttributes) && sizeof(requiredAttributes) > 0 ) {
2181  foreach(requiredAttributes, string attr) {
2182  if ( zero_type(attributes[attr]) )
2183  attributes[attr] = ({ "-" });
2184  }
2185  }
2186 
2187  object ldap = connect();
2188  if ( !objectp(ldap) ) {
2189  last_error = ERROR_NOT_CONNECTED;
2190  return false;
2191  }
2192  ldap->add( make_dn(config["userAttr"]+"="+name, config["base_dc"]),
2193  attributes );
2194  int err = ldap->error_number();
2195  if ( err != 0 )
2196  FATAL( "Failed to add user , error is " + ldap->error_string() );
2197  bool result = ldap->error_number() == 0;
2198  disconnect( ldap );
2199  return result;
2200 }
2201 
2202 public:
2203 
2204 
2205 /**
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.
2209  *
2210  * @return the minimum threshold (in milliseconds) for connection times logging
2211  */
2212 int get_log_connection_times_min () {
2213  return log_connection_times_min;
2214 }
2215 
2216 
2217 /**
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.
2221  *
2222  * @return the maximum threshold (in milliseconds) for connection times logging
2223  */
2224 int get_log_connection_times_max () {
2225  return log_connection_times_max;
2226 }
2227 
2228 
2229 /**
2230  * Activate or deactivate connection times logging and set the threshold for
2231  * log entries.
2232  *
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
2235  * deaktivated)
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
2238  * be used)
2239  * @return true if logging is now active, false if it is now not active
2240  */
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;
2244  else
2245  log_connection_times_min = min_milliseconds;
2246  if ( zero_type(max_milliseconds) || max_milliseconds < 0 )
2247  log_connection_times_max = Int.NATIVE_MAX;
2248  else
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;
2252 }
2253 
2254 
2255 protected:
2256  int get_time_millis () {
2257  array tod = System.gettimeofday();
2258  return tod[0]*1000 + tod[1]/1000;
2259 }
2260 
2261 public:
2262 
2263 
2264 /**
2265  * Set the log level for authentication logging during runtime.
2266  * The log levels are:
2267  * 0 : no logging,
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
2274  * the log).
2275  *
2276  * @param log_level authentication log level
2277  * @return the new log level
2278  */
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;
2283 }
2284 
2285 
2286 /**
2287  * Query the authentication log level
2288  * The log levels are:
2289  * 0 : no logging,
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
2296  * the log).
2297  *
2298  * @return the authentication log level
2299  */
2300 int get_authorize_log_level () {
2301  return log_level_auth;
2302 }
2303 
2304 
2305 /*
2306 void synchronization_thread () {
2307  while ( 1 ) {
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 ];
2312  int t = time();
2313  if ( t - entry->last_accessed > cache_live_time ) {
2314  //TODO: drop entry
2315  continue;
2316  }
2317  if ( t - entry->last_synchronized > cache_time ) {
2318  //TODO: sync entry
2319  }
2320  }
2321  foreach ( indices( group_cache ), string identifier ) {
2322  }
2323  foreach ( indices( authorize_cache ), string identifier ) {
2324  }
2325  sleep( cacheTime ); //TODO: substract the time needed for sync
2326  }
2327 }
2328 */
2329 
2330 
2331 };