Persistence._pike
Go to the documentation of this file.
1 /* Copyright (C) 2005-2008 Thomas Bopp, Thorsten Hampel, 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 #include <macros.h>
18 #include <assert.h>
19 #include <attributes.h>
20 #include <database.h>
21 #include <config.h>
22 #include <classes.h>
23 #include <access.h>
24 #include <roles.h>
25 #include <events.h>
26 #include <exception.h>
27 #include <types.h>
28 #include <configure.h>
29 class Persistence {
30 public:
31 
32 
33  Thread.Queue saveQueue = Thread.Queue();
34 
35 
36 //#define DEBUG_PERSISTENCE 1
37 
38 #ifdef DEBUG_PERSISTENCE
39 #define PDEBUG(s, args...) werror("persistence: "+s+"\n", args)
40 #else
41 #define PDEBUG(s, args...)
42 #endif
43 
44 #define PROXY "/kernel/proxy.pike"
45 
46  mapping config = ([ ]);
47 
48  mapping namespaces = ([ ]);
49  mapping namespace_types = ([ ]);
50  mapping namespace_configs = ([ ]);
51 
52  mapping pending_users = ([ ]);
53  mapping pending_groups = ([ ]);
54  mapping pending_objects = ([ ]);
55  array pending_synchronizations = ({ });
56  bool dont_create_users = false; // used by lookup_internal()
57 
58  array restricted_users = ({ "root", "service", "postman", "guest" });
59 protected:
60  array restricted_groups = ({ "steam", "admin", "coder", "privgroups",
61  "wikigroups", "help", "everyone" });
62 
63 public:
64 
65  array whitelist_users;
66  array whitelist_groups;
67 
68  object __Database;
69  object root_user;
70 
71  int store_content_in_database;
72  int store_content_in_filesystem;
73 
74  object object_cache = ObjectCache();
75 
76 
77 class ObjectCacheEntry {
78 public:
79  object proxy;
80  int obj_class;
81  int obj_id;
82  string identifier;
83 
84  int time_loaded;
85  int time_accessed;
86  int time_synchronized;
87 
88  int `<(object o) { return time_accessed < o->time_accessed; }
89  int `>(object o) { return time_accessed > o->time_accessed; }
90  int `==(object o) { return time_accessed == o->time_accessed; }
91 }
92 
93 class ObjectCache {
94 public:
95  mapping by_id = ([ ]);
96  mapping by_proxy = ([ ]);
97  mapping by_identifier = ([ ]);
98  int synchronize_frequency = 30;
99 
100  /**
101  * Get the synchronization frequency. Objects in the cache will synchronize
102  * with data in the persistence layers on their next access when at least
103  * this number of seconds have passed.
104  *
105  * @return the synchronization frequency (number of seconds), 0 means that
106  * objects will synchronize on every access (e.g. lookup).
107  */
108  int get_synchronize_frequency () {
109  return synchronize_frequency;
110  }
111 
112  /**
113  * Set the synchronization frequency. Objects in the cache will synchronize
114  * with data in the persistence layers on their next access when at least
115  * this number of seconds have passed.
116  *
117  * @param nr_seconds the number of seconds after which an object is
118  * considered to need synchronization with the persistence layers. If you
119  * set this to 0, then objects will synchronize on each cache access
120  * (e.g. each lookup), which can be quite often and cause delays.
121  */
122  void set_synchronize_frequency ( int nr_seconds ) {
123  synchronize_frequency = nr_seconds;
124  }
125 
126  /**
127  * Finds a cached object and returns its cache entry.
128  * This does not touch the object (meaning that it won't update any of
129  * its cache times).
130  *
131  * @param obj the object (or proxy) to look for in the cache
132  * @return the ObjectCacheEntry of the cached object, or 0 if no object
133  * was found
134  */
135  object find_by_proxy ( object obj ) {
136  }
137 
138  /**
139  * Finds a cached object by its object id and returns its cache entry.
140  * This does not touch the object (meaning that it won't update any of
141  * its cache times).
142  *
143  * @param id the object id of the object to look for in the cache
144  * @return the ObjectCacheEntry of the cached object, or 0 if no object
145  * was found
146  */
147  object find_by_id ( int id ) {
148  return by_id[ id ];
149  }
150 
151  /**
152  * Finds a cached object by its identifier. Since multiple objects might
153  * have the same identifier, an array of cache entries will be returned,
154  * even if there is only one result. If no object is found by that
155  * identifier, then 0 will be returned.
156  * This does not touch the object (meaning that it won't update any of
157  * its cache times).
158  *
159  * @param id the object id of the object to look for in the cache
160  * @return the ObjectCacheEntry of the cached object, or 0 if no object
161  * was found
162  */
163  array find_by_identifier ( string identifier ) {
164  return by_identifier[ identifier ];
165  }
166 
167  /**
168  * Fetches an object in the object cache. If the object was already cached,
169  * then its "accessed" time is updated, otherwise it will be cached.
170  * If the objects previous access time is older that the synchronization
171  * frequency allows, then the object will synchronize with data in the
172  * persistence layers. An object that wasn't cached before will always
173  * synchronize with the persistence layers by default.
174  *
175  * @param obj the object (or proxy) to touch (or cache)
176  * @param dont_synchronize if true (or != 0) then do not synchronize the
177  * object with the persistence layers, even if its cache age would
178  * require it to.
179  * @return the cache entry (type ObjectCacheEntry) of the cached object, or 0
180  * if the object could not be cached (e.g. has no get_identifier() method).
181  */
182  object fetch ( object obj, bool dont_synchronize ) {
183  if ( !objectp(obj) ) return 0;
184  // check whether object already is cached:
185  if ( objectp(cache_entry) ) {
186  if ( !dont_synchronize &&
187  (time() - cache_entry->time_synchronized >= synchronize_frequency) ) {
188  synchronize_object( obj );
189  // check whether the object has been deleted by synchronization:
190  if ( obj->status() == PSTAT_DELETED ) {
191  drop( obj );
192  return 0;
193  }
194  cache_entry->time_synchronized = time();
195  }
196  cache_entry->time_accessed = time();
197  return cache_entry;
198  }
199  // need to add object to cache:
200  if ( !functionp(obj->get_object_class) ||
201  !functionp(obj->get_object_id) ||
202  !functionp(obj->get_identifier) )
203  return 0;
204  int obj_class = obj->get_object_class();
205  int obj_id = obj->get_object_id();
206  string identifier = obj->get_identifier();
207 
208  cache_entry = ObjectCacheEntry();
209  cache_entry->obj_class = obj_class;
210  cache_entry->obj_id = obj_id;
211  cache_entry->identifier = identifier;
212  cache_entry->time_accessed = time();
213 
214  by_id[ obj_id ] = cache_entry;
215  if ( arrayp(by_identifier[identifier]) )
216  by_identifier[ identifier ] += ({ cache_entry });
217  else
218  by_identifier[ identifier ] = ({ cache_entry });
219 
220  if ( !dont_synchronize ) {
221  synchronize_object( obj );
222  cache_entry->time_synchronized = time();
223  }
224 
225  return cache_entry;
226  }
227 
228  /**
229  * Drops a cache entry. If the object wasn't cached then nothing will happen.
230  * Note: the object will only be removed from the object cache, it will
231  * not be dropped from memory.
232  *
233  * @param obj the object (or proxy) to remove from the cache
234  * @return 1 if the object was found in the cache and dropped, 0 if the
235  * object was not found in the cache
236  */
237  int drop ( object obj ) {
238  if ( !objectp(obj) )
239  return 0;
240  object cache_entry = find_by_proxy( obj );
241  if ( !objectp(cache_entry) )
242  return 0;
243  m_delete( by_proxy, cache_entry->proxy );
244  m_delete( by_id, cache_entry->obj_id );
245  array identifier_entry = by_identifier[ cache_entry->identifier ];
246  if ( arrayp(identifier_entry) &&
247  search( identifier_entry, cache_entry ) >= 0 ) {
248  if ( sizeof( identifier_entry ) <= 1 )
249  m_delete( by_identifier, cache_entry->identifier );
250  else
251  by_identifier[ cache_entry->identifier ] -= ({ cache_entry });
252  }
253  //TODO: really drop the object from memory (and document this)
254  return 1;
255  }
256 }
257 
258 
259 int uncache_object ( object obj ) {
260  if ( !objectp( obj ) ) return 0;
261  // drop from persistence layer caches:
262  foreach( indices(namespaces), mixed idx ) {
263  object handler = namespaces[idx];
264  if ( functionp( handler["uncache_object"] ) ) {
265  mixed err = catch( handler->uncache_object( obj ) );
266  if ( err )
267  FATAL( "Error while uncaching object %O from namespace %O: %s\n%O\n",
268  obj, idx, err[0], err[1] );
269  }
270  }
271  // drop from object cache:
272  int res = object_cache->drop( obj );
273  PDEBUG( (res ? "uncached" : "could not uncache") + " object %O", obj );
274  return res;
275 }
276 
277 
278 /**
279  * Drops a user from any caches, so that it will receive fresh data on the
280  * next lookup. This can be useful if you know that user data has changed in
281  * one of the persistence layers and you want the user object to update its
282  * data accordingly (before the regular update after the cache times out).
283  *
284  * @param identifier the identifier (user's login name) of the user that shall
285  * be dropped from cache
286  * @return 1 if the user was dropped from the object cache,
287  * 0 if it wasn't found in the object cache (regardless of this return value,
288  * the user might have been dropped from any persistence layer caches)
289  */
290 int uncache_user ( string identifier ) {
291  // drop from persistence layer caches:
292  foreach( indices(namespaces), mixed idx ) {
293  object handler = namespaces[idx];
294  if ( functionp( handler["uncache_user"] ) ) {
295  mixed err = catch( handler->uncache_user( identifier ) );
296  if ( err )
297  FATAL( "Error while uncaching user %O from namespace %O: %s\n%O\n",
298  identifier, idx, err[0], err[1] );
299  }
300  }
301  // drop from object cache:
302  array cache_entries = object_cache->find_by_identifier( identifier );
303  if ( !arrayp(cache_entries) ) return 0;
304  foreach ( cache_entries, object entry ) {
305  if ( entry->obj_class & CLASS_USER )
306  return uncache_object( entry->proxy );
307  }
308 }
309 
310 
311 /**
312  * Drops a group from any caches, so that it will receive fresh data on the
313  * next lookup. This can be useful if you know that group data has changed in
314  * one of the persistence layers and you want the group object to update its
315  * data accordingly (before the regular update after the cache times out).
316  *
317  * @param identifier the identifier (full group name with parent groups
318  * separated by ".") of the group that shall be dropped from cache
319  * @return 1 if the group was dropped from the object cache,
320  * 0 if it wasn't found in the object cache (regardless of this return value,
321  * the group might have been dropped from any persistence layer caches)
322  */
323 int uncache_group ( string identifier ) {
324  // drop from persistence layer caches:
325  foreach( indices(namespaces), mixed idx ) {
326  object handler = namespaces[idx];
327  if ( functionp( handler["uncache_group"] ) ) {
328  mixed err = catch( handler->uncache_group( identifier ) );
329  if ( err )
330  FATAL( "Error while uncaching group %O from namespace %O: %s\n%O\n",
331  identifier, idx, err[0], err[1] );
332  }
333  }
334  // drop from object cache:
335  array cache_entries = object_cache->find_by_identifier( identifier );
336  if ( !arrayp(cache_entries) ) return 0;
337  foreach ( cache_entries, object entry ) {
338  if ( entry->obj_class & CLASS_GROUP )
339  return uncache_object( entry->proxy );
340  }
341 }
342 
343 
344 int is_storing_content_in_database () {
345  return store_content_in_database;
346 }
347 
348 int is_storing_content_in_filesystem () {
349  return store_content_in_filesystem;
350 }
351 
352 
353 int get_save_size()
354 {
355  return saveQueue->size();
356 }
357 
358 protected:
359  void create()
360 {
361  thread_create(save_demon);
362 }
363 
364 public:
365 
366 
367 mixed get_config () {
368  if ( !GROUP("admin")->is_member( this_user() ) )
369  THROW( "Only administrators may query the persistence config!", E_ACCESS );
370  return config;
371 }
372 
373 
374 bool get_dont_create_exits () {
375  return Config.bool_value(config["dont-create-exits"]);
376 }
377 
378 
379 string safe_lower_case ( string s ) {
380  if ( !stringp(s) ) return s;
381  if ( xml.utf8_check( s ) )
382  return string_to_utf8( lower_case( utf8_to_string( s ) ) );
383  else
384  return lower_case( s );
385 }
386 
387 
388 /**
389  * Sends an email to the maintainers of namespaces.
390  *
391  * @param namespace a namespace id or a list of namespace ids whose
392  * maintainers to send the mail to
393  * @param subject the subject of the mail
394  * @param message the message body
395  * @param args arguments to the message body (works with the message body
396  * like sprintf or write)
397  */
398 void mail_maintainer ( array|int namespace, string subject, string message, mixed ... args ) {
399  array namespaces;
400  if ( arrayp(namespace) ) namespaces = namespace;
401  else if ( intp(namespace) ) namespaces = ({ namespace });
402  else return;
403 
404  PDEBUG("Mailing maintainers of %O", namespace);
405 
406  string title = "(" + BRAND_NAME + ") " + subject;
407  string body = sprintf( message, @args ) + "\n\n"
408  + "* Server: " + _Server->get_server_name() + "\n"
409  + "* Time: " + ctime(time()) + "\n";
410  foreach ( namespaces, int nid ) {
411  string namespace_type = search( namespace_types, nid );
412  string maintainer = config["maintainer"];
413  if ( mappingp(namespace_configs[nid]) )
414  maintainer = namespace_configs[nid]["maintainer"];
415  if ( !stringp(maintainer) )
416  maintainer = config["maintainer"];
417  if ( !stringp(maintainer) || sizeof(maintainer)<1 )
418  return; // no maintainer specified, don't send a mail
419  array maintainers = Config.array_value( maintainer );
420  if ( !arrayp(maintainers) || sizeof(maintainers)<1 )
421  return;
422  foreach ( maintainers, string maintainer ) {
423  object user = USER( maintainer );
424  if ( !objectp(user) ) continue;
425  PDEBUG("* Mailing maintainer %s of namespace %O\n", user->get_identifier(), nid );
426  user->mail( body + sprintf("* Persistence layer type: %O", namespace_type), title, 0, "text/plain" );
427  }
428  }
429 }
430 
431 
432 /** Prepends the namespace part to a given object id. May only be
433  * called by namespaces (persistence layers).
434  * @param small_oid the small object id (without namespace information)
435  * @return the new (complete) object id (or 0 on error)
436  */
437 int make_object_id (int small_oid)
438 {
439  if ( ! is_namespace( CALLER ) ) {
440  werror( "Persistence: make_object_id(): Caller is not a namespace: %O\n", CALLER );
441  return 0;
442  }
443  if ( CALLER == __Database ) return small_oid & 0x7fffffff; // 32bit, first bit must be zero
444  int nid = search( values(namespaces), CALLER );
445  if ( nid < 0 ) {
446  werror( "Persistence: make_object_id(): Caller is not registered as a namespace: %O\n", CALLER );
447  return 0;
448  }
449  nid = indices(namespaces)[nid];
450  int oid_length = (int)ceil( log( (float)small_oid ) / log( 2.0 ) );
451  if ( (oid_length % 8) != 0 ) oid_length = oid_length + 8 - (oid_length % 8);
452  oid_length /= 8; // byte length, not bit length
453  if ( oid_length > 0xfff ) {
454  werror( "Persistence: make_object_id(): oid too long (%d bytes)\n", oid_length );
455  return 0;
456  }
457  // build namespace part: 1[3bit-reserved][16bit-nid][12bit-oid_length]
458  nid = ((0x80000000 | (nid & 0xffff)) << 12) | oid_length;
459  // shift left to make room for oid:
460  nid = nid << oid_length * 8;
461  // cut object id to oid_length bits:
462  nid = nid | small_oid;
463  return nid;
464 }
465 
466 
467 /**
468  * @return the namespace id of the persistence layer
469  */
470 int register( string type_name, object handler )
471 {
472  if ( !stringp(type_name) || sizeof(type_name)<1 ) {
473  werror( "Namespace tried to register without a valid type name: %O\n", handler );
474  return -1;
475  }
476  if ( !objectp(handler) ) {
477  werror( "Namespace '%s' tried to register, but it is not an object: %O\n", type_name, handler );
478  return -1;
479  }
480  if ( !objectp( __Database ) ) {
481  if ( !objectp( master()->get_constant("_Database") ) ) {
482  werror( "Namespace tried to register, but database hasn't registered, yet!\n");
483  return -1;
484  }
485 
486  __Database = master()->get_constant("_Database");
487  namespaces[0] = __Database;
488  namespace_types[type_name] = 0;
489  }
490 
491  int nid = 0;
492  if ( handler != __Database ) {
493  nid = search( values(namespaces), handler );
494  if ( nid >= 0 ) // already registered
495  return nid;
496 
497  nid = 1;
498 
499  foreach( indices(namespaces), int tmp_nid )
500  if ( nid <= tmp_nid ) nid = tmp_nid+1;
501  namespaces[nid] = handler;
502 
503  if ( !zero_type(namespace_types[type_name]) )
504  werror( "Warning: namespace type '%s' already registered for layer %O\n", type_name, namespace_types[type_name] );
505  namespace_types[type_name] = nid;
506  }
507 
508  if ( arrayp(config["layer"]) ) {
509  foreach ( config["layer"], mixed layer ) {
510  if ( !mappingp(layer) ) continue;
511  if ( !stringp(layer["type"]) ) continue;
512  if ( layer["type"] == type_name ) {
513  namespace_configs[ nid ] = layer;
514  break;
515  }
516  }
517  }
518 
519  if ( nid != 0 && objectp(GROUP("admin")) ) {
520  GROUP("admin")->unlock_attribute("namespaces");
521  GROUP("admin")->set_attribute("namespaces", namespaces);
522  GROUP("admin")->set_attribute("namespace_types", namespace_types);
523  GROUP("admin")->lock_attribute("namespaces");
524  }
525 
526  return nid;
527 }
528 
529 
530 protected:
531  void save_demon()
532 {
533 }
534 
535 public:
536 
537 
538 void init ()
539 {
540  config = Config.read_config_file( _Server.get_config_dir()+"/persistence.cfg", "persistence" );
541  if ( !mappingp(config) ) {
542  config = ([ ]);
543  MESSAGE( "No persistence.cfg config file." );
544  }
545  PDEBUG( "persistence config is %O", config );
546  if ( arrayp(Config.array_value(config["restricted-users"])) ) {
547  restricted_users = ({ });
548  foreach ( Config.array_value( config["restricted-users"] ), string user )
549  restricted_users += ({ lower_case( user ) });
550  }
551  PDEBUG( "restricted users: %O", restricted_users );
552  if ( arrayp(Config.array_value(config["restricted-groups"])) ) {
553  restricted_groups = ({ });
554  foreach ( Config.array_value( config["restricted-groups"] ), string group )
555  restricted_groups += ({ lower_case( group ) });
556  }
557  PDEBUG( "restricted groups: %O", restricted_groups );
558  if ( arrayp(Config.array_value(config["whitelist-users"])) ) {
559  PDEBUG( "overriding whitelisted users by persistence.cfg" );
560  whitelist_users = ({ });
561  foreach ( Config.array_value( config["whitelist-users"] ), string user )
562  whitelist_users += ({ lower_case( user ) });
563  }
564  if ( arrayp(Config.array_value(config["whitelist-groups"])) ) {
565  PDEBUG( "overriding whitelisted groups by persistence.cfg" );
566  whitelist_groups = ({ });
567  foreach ( Config.array_value( config["whitelist-groups"] ), string group )
568  whitelist_groups += ({ lower_case( group ) });
569  }
570 
571  store_content_in_database = 1;
572  if ( mappingp(config["content"]) ) {
573  mixed db_content = config["content"]["database"];
574  if ( stringp(db_content) ) {
575  switch ( lower_case(db_content) ) {
576  case "off":
577  case "no":
578  case "false":
579  case "none":
580  store_content_in_database = 0;
581  MESSAGE( "Database content storage has been disabled" );
582  break;
583  }
584  }
585  }
586 
587  store_content_in_filesystem = 0;
588  if ( mappingp(config["content"]) && mappingp(config["content"]["filesystem"]) ) {
589  mapping server_config = Config.read_config_file( CONFIG_DIR + "/steam.cfg" );
590  string content_path = server_config["sandbox"];
591  if ( !stringp(content_path) || content_path == "" ) content_path = STEAM_DIR + "/tmp/content";
592  else content_path += "/content";
593 
594  // check whether to use sandbox directly for content storage:
595  if ( stringp(config["content"]["filesystem"]["sandbox"]) &&
596  Config.bool_value( config["content"]["filesystem"]["sandbox"] ) ) {
597  MESSAGE( "Sandbox content/ subdirectory will be used as content filesystem." );
598  store_content_in_filesystem = 1;
599  }
600  // check whether to mount a filesystem for content storage:
601  else if ( stringp(config["content"]["filesystem"]["mount"] ) ) {
602  mixed content_dir = get_dir( content_path );
603  if ( arrayp(content_dir) && sizeof(content_dir) > 0 ) {
604  MESSAGE( "Content filesystem already mounted on %s", content_path );
605  store_content_in_filesystem = 1;
606  }
607  else {
608  string tmp_content_path;
609  mixed err = catch( tmp_content_path = ContentFilesystem.mount() );
610  if ( err )
611  FATAL( err[0] );
612  else if ( stringp(tmp_content_path) ) {
613  MESSAGE( "Content filesystem mounted on %s", content_path );
614  content_path = tmp_content_path;
615  store_content_in_filesystem = 1;
616  }
617  // If tmp_content_path is no string but no exception occurred during mount,
618  // then filesystem content storage was not enabled.
619  }
620  }
621  }
622 
623 private:
624  if ( store_content_in_database )
625  MESSAGE( "Content will be stored in and read from database." );
626 private:
627  if ( store_content_in_filesystem )
628  MESSAGE( "Content will be stored in and read from filesystem." );
629 
630 private:
631  if ( !store_content_in_database && !store_content_in_filesystem )
632  steam_error( "Neither database nor filesystem content storage enabled!" );
633 }
634 
635 void post_init ()
636 {
637  root_user = USER("root");
638  if ( objectp(GROUP("admin")) ) {
639  GROUP("admin")->unlock_attribute("namespaces");
640  GROUP("admin")->set_attribute("namespaces",namespaces);
641  GROUP("admin")->set_attribute("namespace_types",namespace_types);
642  GROUP("admin")->lock_attribute("namespaces");
643  if ( !arrayp(Config.array_value(config["whitelist-users"])) )
644  whitelist_users = GROUP("admin")->query_attribute( "whitelist_users" );
645  PDEBUG( "whitelisted users: %O", whitelist_users );
646  if ( !arrayp(Config.array_value(config["whitelist-groups"])) )
647  whitelist_groups = GROUP("admin")->query_attribute( "whitelist_groups" );
648  PDEBUG( "whitelisted groups: %O", whitelist_groups );
649  }
650  else FATAL( "Could not get \"Admin\" group to store persistence namespace ids and whitelists." );
651 }
652 
653 public:
654 
655 
656 /**
657  * @return array { nid, oid_length(bytes), oid }
658  */
659 array split_object_id ( object|int p )
660 {
661  int nid;
662  int oid_length;
663  int oid;
664  if ( intp(p) ) oid = p;
665  else if ( objectp(p) ) oid = p->get_object_id();
666  else {
667  werror("Persistence: split_object_id(): param is not int or object: %O\n", p);
668  return UNDEFINED;
669  }
670  if ( oid < 0 ) return UNDEFINED;
671  else if ( oid == 0 ) return ({ 0, 0, 0 });
672  if ( (oid & 0x80000000) == 0 ) return ({ 0, 4, oid & 0xffffffff });
673  oid_length = (((int)(log( (float)oid ) / log( 2.0 ))) - 31);
674  nid = oid >> oid_length;
675  oid = oid & ((1 << oid_length) - 1);
676  return ({ nid, oid_length/8, oid });
677 }
678 
679 object get_namespace(object|int p)
680 {
681  mixed nid = split_object_id( p );
682  if ( !arrayp(nid) ) return UNDEFINED;
683  return namespaces[ nid[0] ];
684 }
685 
686 
687 array get_users_allowed () {
688  if ( !GROUP("admin")->is_member( this_user() ) )
689  THROW( "Only administrators may list allowed users!", E_ACCESS );
690  return whitelist_users;
691 }
692 
693 
694 bool user_allowed ( string user )
695 {
696  // restricted users are system users and should always be allowed:
697  if ( user_restricted( user ) ) return true;
698  // check whitelist:
699  if ( arrayp(whitelist_users) && sizeof(whitelist_users) > 0 )
700  return search( whitelist_users, safe_lower_case( user ) ) >= 0;
701  return true;
702 }
703 
704 
705 void add_user_allowed ( string user )
706 {
707  if ( !GROUP("admin")->is_member( this_user() ) )
708  THROW( "Only administrators may add allowed users!", E_ACCESS );
709  if ( arrayp(Config.array_value(config["whitelist-users"])) )
710  return; // overridden by config file
711  if ( !arrayp(whitelist_users) ) whitelist_users = ({ });
712  user = safe_lower_case( user );
713  if ( search( whitelist_users, user ) < 0 ) whitelist_users += ({ user });
714  GROUP("admin")->unlock_attribute( "whitelist_users" );
715  GROUP("admin")->set_attribute( "whitelist_users", whitelist_users );
716  GROUP("admin")->lock_attribute( "whitelist_users" );
717 }
718 
719 
720 void remove_user_allowed ( string user )
721 {
722  if ( !GROUP("admin")->is_member( this_user() ) )
723  THROW( "Only administrators may remove allowed users!", E_ACCESS );
724  if ( arrayp(Config.array_value(config["whitelist-users"])) )
725  return; // overridden by config file
726  if ( !arrayp(whitelist_users) ) return;
727  user = safe_lower_case( user );
728  whitelist_users -= ({ user });
729  GROUP("admin")->unlock_attribute( "whitelist_users" );
730  GROUP("admin")->set_attribute( "whitelist_users", whitelist_users );
731  GROUP("admin")->lock_attribute( "whitelist_users" );
732 }
733 
734 
735 array get_groups_allowed () {
736  if ( !GROUP("admin")->is_member( this_user() ) )
737  THROW( "Only administrators may list allowed groups!", E_ACCESS );
738  return whitelist_groups;
739 }
740 
741 
742 bool group_allowed ( string group )
743 {
744  if ( group_restricted( group ) ) return true;
745  if ( arrayp(whitelist_groups) && sizeof(whitelist_groups) > 0 )
746  return search( whitelist_groups, safe_lower_case( group ) ) >= 0;
747  return true;
748 }
749 
750 
751 void add_group_allowed ( string group )
752 {
753  if ( !GROUP("admin")->is_member( this_user() ) )
754  THROW( "Only administrators may add allowed groups!", E_ACCESS );
755  if ( arrayp(Config.array_value(config["whitelist-groups"])) )
756  return; // overridden by config file
757  if ( !arrayp(whitelist_groups) ) whitelist_groups = ({ });
758  group = safe_lower_case( group );
759  if ( search( whitelist_groups, group ) < 0 ) whitelist_groups += ({ group });
760  GROUP("admin")->unlock_attribute( "whitelist_groups" );
761  GROUP("admin")->set_attribute( "whitelist_groups", whitelist_groups );
762  GROUP("admin")->lock_attribute( "whitelist_groups" );
763 }
764 
765 
766 void remove_group_allowed ( string group )
767 {
768  if ( !GROUP("admin")->is_member( this_user() ) )
769  THROW( "Only administrators may remove allowed groups!", E_ACCESS );
770  if ( arrayp(Config.array_value(config["whitelist-groups"])) )
771  return; // overridden by config file
772  if ( !arrayp(whitelist_groups) ) return;
773  group = safe_lower_case( group );
774  whitelist_groups -= ({ group });
775  GROUP("admin")->unlock_attribute( "whitelist_groups" );
776  GROUP("admin")->set_attribute( "whitelist_groups", whitelist_groups );
777  GROUP("admin")->lock_attribute( "whitelist_groups" );
778 }
779 
780 
781 bool user_restricted ( string user )
782 {
783  if ( !stringp(user) ) return false;
784  // since no users and groups of the same name may exist, check both:
785  if ( search( restricted_users | restricted_groups, safe_lower_case( user ) ) >= 0 )
786  return true;
787  else return false;
788 }
789 
790 bool group_restricted ( string group )
791 {
792  if ( !stringp(group) ) return false;
793  // since no users and groups of the same name may exist, check both:
794  if ( search( restricted_groups | restricted_users, safe_lower_case( group ) ) >= 0 )
795  return true;
796  else return false;
797 }
798 
799 /**
800  * creates a new persistent sTeam object.
801  *
802  * @param string prog (the class to clone)
803  * @return proxy and id for object
804  * note that proxy creation implies creation of associated object.
805  * @see kernel.proxy.create, register_user
806  */
807 mixed new_object(mixed id)
808 {
809  mixed res;
810  string prog_name = master()->describe_program(object_program(CALLER));
811  object obj = CALLER;
812  foreach(indices(namespaces), mixed idx ) {
813  if ( idx == 0 )
814  continue;
815  object handler = namespaces[idx];
816  if ( !functionp(handler->new_object) )
817  continue;
818  if ( res = handler->new_object(id, obj, prog_name) ) {
819  return res;
820  }
821  }
822  //TODO: use new_object(id,obj,program) api in database.pike, too
823  return namespaces[0]->new_object(obj, prog_name);
824 }
825 
826 mapping get_namespaces()
827 {
828  return namespaces;
829 }
830 
831 int get_namespace_id (object ns)
832 {
833  mixed index = search( values(namespaces), ns );
834  if ( !intp(index) ) return -1;
835  return indices(namespaces)[index];
836 }
837 
838 int is_namespace(object ns)
839 {
840  //TODO: ???
841  return 1;
842 }
843 
844 void set_proxy_status(object p, int status)
845 {
846  if ( is_namespace(CALLER) )
847  p->set_status(status);
848 }
849 
850 bool delete_object(object p)
851 {
852  if ( object_cache )
853  object_cache->drop(p);
854 
855  object nid = get_namespace(p);
856  if ( !objectp(nid) ) {
857  werror( "delete_object: invalid namespace for %O\n", p->get_object_id() );
858  return false;
859  }
860  if ( !functionp(nid->delete_object) )
861  return false;
862  return nid->delete_object(p);
863 }
864 
865 
866 /**
867  * Synchronizes the object with data in the persistence layers.
868  * Right now, this only reads data from the persistence layers and doesn't
869  * write back changes.
870  * @TODO update data in persistence layers if the object has changed
871  *
872  * @return true if data in the object has changed, false otherwise
873  */
874 bool synchronize_object ( object obj ) {
875  if ( !objectp(obj) || !functionp(obj->get_object_class) )
876  return false;
877  // prevent cyclic recursion (e.g. through cache lookups):
878  if ( search( pending_synchronizations, obj ) >= 0 )
879  return false;
880  pending_synchronizations += ({ obj });
881  // don't create users while synchronizing a user (this prevents
882  // creation of other members of the user's groups by lookup):
883  if ( obj->get_object_class() & CLASS_USER )
884  dont_create_users = true;
885  bool result = synchronize_object_internal( obj );
886  if ( obj->get_object_class() & CLASS_USER )
887  dont_create_users = false;
888  pending_synchronizations -= ({ obj });
889  return result;
890 }
891 
892 
893 /**
894  * Used internally by synchronize_object() to prevent cyclic recursions.
895  * @see synchronize_object
896  */
897 protected:
898  bool synchronize_object_internal ( object obj ) {
899  if ( !objectp(obj) || !functionp(obj->status) )
900  return false;
901  int obj_status = obj->status();
902  if ( (obj_status != PSTAT_SAVE_OK) && (obj_status != PSTAT_SAVE_PENDING) )
903  return false;
904  if ( !functionp(obj->get_object_class) || !functionp(obj->get_identifier) )
905  return false;
906  int obj_class = obj->get_object_class();
907  string obj_class_name = "<unknown>";
908  if ( objectp(_Server->get_factory( obj_class )) )
909  obj_class_name = _Server->get_factory( obj_class )->get_class_name();
910  string identifier = obj->get_identifier();
911  if ( obj_class & CLASS_USER ) {
912  if ( user_restricted( identifier ) ) return false;
913  }
914  else if ( obj_class & CLASS_GROUP ) {
915  if ( group_restricted( identifier ) ) return false;
916  }
917  else {
918  //TODO: right now only users and groups can be synchronized with persistence layers.
919  // we will need a mechanism for objects that are restricted to local
920  // persistence, or better yet, for objects that can explicitly be
921  // synchronized with data from persistence layers...
922  return false;
923  }
924 
925  // prevent cyclic recursion (e.g. through cache lookups):
926  pending_synchronizations += ({ obj });
927 
928  bool changes = false;
929  int obj_namespace = get_namespace_id( get_namespace( obj ) );
930  array source_namespaces = ({ });
931  array ignore_namespaces = ({ });
932  mapping data = ([ ]);
933  mapping nonpersistent_attributes = ([ ]);
934  array collected_users = ({ });
935  array collected_groups = ({ });
936  foreach(indices(namespaces), int nid ) {
937  if ( nid == obj_namespace ) continue; // don't load data from object's ns
938  object handler = namespaces[nid];
939  if ( functionp(handler->supported_classes) &&
940  (( handler->supported_classes() & obj_class ) == 0) ) {
941  ignore_namespaces += ({ nid });
942  continue;
943  }
944  if ( functionp(handler->load_data) ) {
945  mixed res = handler->load_data( obj );
946  if ( intp(res) && res < 0 ) {
947  // an error occurred, we cannot determine whether the object has data
948  // in the namespace:
949  ignore_namespaces += ({ nid });
950  }
951  else if ( mappingp(res) && (res["class"] == obj_class_name) ) {
952  if ( !stringp(res["class"]) ) {
953  PDEBUG("%s : no class entry in data from %s", identifier,
954  handler->get_identifier());
955  continue;
956  }
957  if ( res["class"] != obj_class_name ) {
958  PDEBUG("%s : wrong class '%s' data from %s for object of class '%s'",
959  identifier, res["class"], handler->get_identifier(),
960  obj_class_name);
961  continue;
962  }
963  data |= res;
964  if ( mappingp(res["nonpersistent-attributes"]) ) {
965  foreach ( indices(res["nonpersistent-attributes"]), mixed nonattr) {
966  nonpersistent_attributes[ nonattr ] = handler;
967  }
968  }
969  if ( arrayp(data["users"]) ) {
970  foreach( data["users"], string uname ) {
971  uname = lower_case( uname );
972  if ( search( collected_users, uname ) < 0 )
973  collected_users += ({ uname });
974  }
975  }
976  if ( arrayp(data["groups"]) ) {
977  foreach( data["groups"], string gname ) {
978  gname = lower_case( gname );
979  if ( search( collected_groups, gname ) < 0 )
980  collected_groups += ({ gname });
981  }
982  }
983  source_namespaces += ({ nid });
984  }
985  }
986  }
987 
988  // check if a user has been suspended:
989  if ( obj_class & CLASS_USER ) {
990  if ( !zero_type(data["suspend"]) ) {
991  if ( data["suspend"] ) {
992  object auth = get_module( "auth" );
993  if ( ! auth->is_user_suspended( obj ) ) {
994  auth->suspend_user( obj, true );
995  PDEBUG( "suspended user %s", identifier );
996  }
997  }
998  else {
999  object auth = get_module( "auth" );
1000  if ( auth->is_user_suspended( obj ) ) {
1001  auth->suspend_user( obj, false );
1002  PDEBUG( "unsuspended user %s", identifier );
1003  }
1004  }
1005  }
1006  }
1007 
1008  // check appearance, disappearance or reappearance of object in namespaces:
1009  array old_namespaces = obj->query_attribute( OBJ_NAMESPACES );
1010  if ( !arrayp(old_namespaces) ) old_namespaces = ({ });
1011  array ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1012  if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1013 
1014  string obj_type = "object";
1015  if ( obj_class & CLASS_USER ) obj_type = "user";
1016  else if ( obj_class & CLASS_GROUP ) obj_type = "group";
1017 
1018  //TODO: check whether the object has appeared and wasn't in the namespace before (the action must be configurable, too), also in lookup!!!
1019 
1020  // check whether the object has been removed from namespaces:
1021  array diff_namespaces = old_namespaces - source_namespaces - ex_namespaces
1022  - ignore_namespaces;
1023  if ( arrayp(diff_namespaces) && sizeof( diff_namespaces ) > 0 ) {
1024  foreach ( diff_namespaces, int nid ) {
1025  array actions = Config.array_value( config[obj_type+"-disappeared"] );
1026  if ( mappingp(namespace_configs[nid]) ) {
1027  array tmp = Config.array_value(
1028  namespace_configs[nid][obj_type+"-disappeared"] );
1029  if ( arrayp(tmp) && sizeof(tmp)>0 ) actions = tmp;
1030  }
1031  if ( !arrayp(actions) ) actions = ({ });
1032  // only users can be deactivated:
1033  if ( (obj_class & CLASS_USER) == 0 ) actions -= ({ "deactivate" });
1034  actions = reverse( sort( actions ) ); // make sure "warn" comes first
1035  foreach ( actions, string action ) {
1036  switch ( action ) {
1037  case "warn" : {
1038  mail_maintainer( nid, obj_type + " disappeared",
1039  "The %s '%s' has disappeared from %O. The following actions"
1040  + "have been taken: %O", obj_type, identifier,
1041  namespaces[nid]->get_identifier(), actions );
1042  } break;
1043  case "delete" : {
1044  PDEBUG( "%s '%s' disappeared from namespace %O, deleting.",
1045  obj_type, identifier, nid );
1046  mixed err = catch {
1047  int del_res = get_factory( CLASS_OBJECT )->delete_for_me( obj );
1048  PDEBUG( "deleted %s '%s' : %O", obj_type, identifier, del_res );
1049  };
1050  if ( err )
1051  FATAL( "Could not delete disappeared %s '%s': %s\n%O",
1052  obj_type, identifier, err[0], err[1] );
1053  return true;
1054  } break;
1055  case "deactivate" :
1056  case "suspend" : {
1057  // only users can be deactivated:
1058  if ( (obj_class & CLASS_USER) == 0 ) break;
1059  changes = true;
1060  PDEBUG( "%s '%s' disappeared from namespace %O, suspending.",
1061  obj_type, identifier, nid );
1062  get_module("auth")->suspend_user( obj, true );
1063  } break;
1064  }
1065  }
1066  ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1067  if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1068  ex_namespaces += ({ nid });
1069  obj->set_attribute( OBJ_EX_NAMESPACES, ex_namespaces );
1070  }
1071  }
1072  // check whether the object reappears in new namespaces:
1073  ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1074  if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1075  diff_namespaces = ex_namespaces & source_namespaces;
1076  if ( arrayp(diff_namespaces) && sizeof(diff_namespaces) > 0 ) {
1077  foreach ( diff_namespaces, mixed nid ) {
1078  array actions = Config.array_value( config[obj_type+"-reappeared"] );
1079  if ( mappingp(namespace_configs[nid]) ) {
1080  array tmp = Config.array_value(
1081  namespace_configs[nid][obj_type+"-reappeared"] );
1082  if ( arrayp(tmp) && sizeof(tmp)>0 ) actions = tmp;
1083  }
1084  if ( !arrayp(actions) ) actions = ({ });
1085  // only users can be reactivated:
1086  if ( (obj_class & CLASS_USER) == 0 ) actions -= ({ "reactivate" });
1087  actions = reverse( sort( actions ) ); // make sure "warn" comes first
1088  foreach ( actions, string action ) {
1089  switch ( action ) {
1090  case "warn" : {
1091  mail_maintainer( nid, obj_type + " reappeared",
1092  "The %s '%s' has reappeared from %O. The following actions"
1093  + "have been taken: %O", obj_type, identifier,
1094  namespaces[nid]->get_identifier(), actions );
1095  } break;
1096  case "reactivate" :
1097  case "unsuspend" : {
1098  changes = true;
1099  PDEBUG( "%s '%s' reappeared in namespace %O, unsuspending.",
1100  obj_type, identifier, nid );
1101  get_module("auth")->suspend_user( obj, false );
1102  } break;
1103  case "delete" : {
1104  PDEBUG( "%s '%s' reappeared in namespace %O, deleting.",
1105  obj_type, identifier, nid );
1106  obj->delete();
1107  return true;
1108  } break;
1109  }
1110  ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1111  if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1112  ex_namespaces -= ({ nid });
1113  obj->set_attribute( OBJ_EX_NAMESPACES, ex_namespaces );
1114  }
1115  }
1116  }
1117 
1118  // sync attributes:
1119  mixed attributes = data["attributes"];
1120  if ( mappingp(attributes) && sizeof(attributes) > 0 ) {
1121  // namespaces are treated separately:
1122  m_delete( attributes, OBJ_NAMESPACES );
1123  m_delete( attributes, OBJ_EX_NAMESPACES );
1124  mixed old_attributes = obj->query_attributes();
1125  foreach( indices( attributes ), mixed key ) {
1126  if ( attributes[key] == old_attributes[key] )
1127  m_delete( attributes, key );
1128  }
1129  obj->set_attributes( attributes );
1130  // email:
1131  if ( obj_class & CLASS_USER ) {
1132  mixed new_email = old_attributes[ USER_EMAIL ];
1133  mixed old_email = old_attributes[ USER_EMAIL ];
1134  object fwmod = get_module("forward");
1135  if ( stringp(new_email) && new_email != old_email && objectp(fwmod) ) {
1136  PDEBUG("updating forwards for %s (USER_EMAIL changed)", identifier);
1137  if ( stringp(old_email) ) {
1138  PDEBUG( "deleting old forward for %s : %s", identifier, old_email );
1139  fwmod->delete_forward( obj, old_email );
1140  }
1141  fwmod->add_forward( obj, new_email );
1142  PDEBUG( "adding new forward for %s : %s", identifier, new_email );
1143  }
1144  }
1145  changes = true;
1146  }
1147 
1148  // handle non-persistent attributes:
1149  if ( mappingp(nonpersistent_attributes) &&
1150  sizeof(nonpersistent_attributes) > 0 ) {
1151  foreach ( indices(nonpersistent_attributes), mixed nonattr ) {
1152  mixed nonattr_handler = nonpersistent_attributes[ nonattr ];
1153  if ( objectp(nonattr_handler) &&
1154  functionp(nonattr_handler->query_attribute) ) {
1155  }
1156  }
1157  }
1158 
1159  // sync user options:
1160  if ( obj_class & CLASS_USER ) {
1161  PDEBUG("syncing user %s %O", identifier, obj );
1162 
1163  // sync password:
1164  if ( stringp(data["password"]) && sizeof(data["password"]) > 0 ) {
1165 private:
1166  call_storage_handler( obj->restore_user_data, data["password"],
1167  "UserPassword" );
1168  changes = true;
1169  }
1170 
1171  // sync group membership:
1172  if ( arrayp(data["groups"]) ) {
1173  array groups = data["groups"];
1174  int crc32 = Gz.crc32( sort(groups) * "," );
1175  mixed old_crc32 = obj->query_attribute( USER_NAMESPACE_GROUPS_CRC );
1176  if ( zero_type(old_crc32) )
1177  old_crc32 = crc32 + 1; // assume different crc32 by default
1178  // only sync if groups seem to have changed:
1179  if ( crc32 != old_crc32 ) {
1180  PDEBUG( "synchronizing groups of user %s : %O", identifier, groups );
1181  foreach ( obj->get_groups(), object group ) {
1182  string group_name = group->get_group_name();
1183  if ( search( groups, group_name ) >= 0 ) {
1184  groups -= ({ group_name }); // already member of group
1185  }
1186  else if ( lower_case( group_name ) != "steam" ) {
1187  array group_namespaces = group->query_attribute( OBJ_NAMESPACES );
1188  if ( ! arrayp(group_namespaces) || sizeof(group_namespaces) < 1 )
1189  continue;
1190  group_namespaces -= source_namespaces;
1191  // only remove user from group if they're from the same namespaces:
1192  if ( sizeof(group_namespaces) < 1 ) {
1193  remove_user_from_group( obj, group );
1194  }
1195  }
1196  }
1197  foreach ( groups, string group_name ) {
1198  object group = lookup_group( group_name );
1199  add_user_to_group( obj, group );
1200  }
1201  obj->set_attribute( USER_NAMESPACE_GROUPS_CRC, crc32 );
1202  }
1203  else
1204  PDEBUG( "crc32 check: user %s groups don't seem to have changed: %O",
1205  identifier, (groups * ",") );
1206  }
1207 
1208 public:
1209 
1210  // sync active group:
1211  if ( stringp(data["active_group"]) && sizeof(data["active_group"]) > 0 ) {
1212  object active_group = lookup_group( data["active_group"] );
1213  if ( objectp(active_group) &&
1214  (obj->get_active_group() != active_group) ) {
1215  if ( !active_group->is_member( obj ) )
1216  active_group->add_member( obj );
1217  if ( active_group->is_member( obj ) ) {
1218  obj->set_active_group( active_group );
1219  changes = true;
1220  }
1221  }
1222  }
1223  }
1224 
1225  // sync group options:
1226  else if ( obj_class & CLASS_GROUP ) {
1227  PDEBUG("syncing group %s %O", identifier, obj );
1228 
1229  // parent group:
1230  if ( stringp(data["parentgroup"]) ) {
1231  PDEBUG( "Group %s : name %O, parent %O\n",
1232  identifier, data["name"], data["parentgroup"] );
1233  object parent = obj->get_parent();
1234  if ( !objectp(parent) ||
1235  lower_case(parent->get_group_name()) != lower_case(data["parentgroup"]) ) {
1236  parent = lookup_group( data["parentgroup"] );
1237  if ( !objectp(parent) )
1238  FATAL( "Could not find parent group %s for group %s",
1239  data["parentgroup"], obj->get_group_name() );
1240  else {
1241  PDEBUG("moving group %O to parent group %O", obj, parent);
1242  get_factory( CLASS_GROUP )->move_group( obj, parent );
1243  }
1244  }
1245  }
1246 
1247  // sync group memberships:
1248  mixed users = data["users"];
1249  if ( !arrayp(users) ) users = ({ });
1250  int crc32 = Gz.crc32( sort(users) * "," );
1251  mixed old_crc32 = obj->query_attribute( GROUP_NAMESPACE_USERS_CRC );
1252  if ( zero_type(old_crc32) )
1253  old_crc32 = crc32 + 1; // assume different crc32 by default
1254  // only sync if users seem to have changed:
1255  if ( crc32 != old_crc32 ) {
1256  PDEBUG( "synchronizing members of group %s : %O", identifier, users );
1257  foreach ( obj->get_members(), object user ) {
1258  if ( !(user->get_object_class() & CLASS_GROUP) ) continue;
1259  string user_name = user->get_user_name();
1260  if ( search( users, user_name ) >= 0 ) {
1261  users -= ({ user_name }); // already member of group
1262  }
1263  else {
1264  array user_namespaces = user->query_attribute( OBJ_NAMESPACES );
1265  if ( ! arrayp(user_namespaces) || sizeof(user_namespaces) < 1 )
1266  continue;
1267  array tmp_namespaces = copy_value( source_namespaces );
1268  tmp_namespaces -= user_namespaces;
1269  // only remove user from group if they're from the same namespaces:
1270  if ( sizeof(tmp_namespaces) < 1 )
1271  remove_user_from_group( user, obj );
1272  }
1273  }
1274  foreach ( users, string user_name ) {
1275  object user = lookup_user( user_name );
1276  add_user_to_group( user, obj );
1277  }
1278  obj->set_attribute( GROUP_NAMESPACE_USERS_CRC, crc32 );
1279  }
1280  else PDEBUG( "crc32 check: group %s members don't seem to have " +
1281  "changed: %O", identifier, users );
1282 
1283  // sync sub groups:
1284  mixed subgroups = data["groups"];
1285  if ( !arrayp(subgroups) ) subgroups = ({ });
1286  crc32 = Gz.crc32( sort(subgroups) * "," );
1287  old_crc32 = crc32 + 1; // assume different crc32 by default
1288  if ( !zero_type(obj->query_attribute( GROUP_NAMESPACE_GROUPS_CRC )) )
1289  old_crc32 = obj->query_attribute( GROUP_NAMESPACE_GROUPS_CRC );
1290  // only sync if sub groups seem to have changed:
1291  if ( crc32 != old_crc32 ) {
1292  PDEBUG( "synchronizing sub groups of group %s", identifier );
1293  foreach ( obj->get_sub_groups(), object subgroup ) {
1294  if ( !(subgroup->get_object_class() & CLASS_GROUP) ) continue;
1295  string subgroup_name = subgroup->query_attribute( OBJ_NAME );
1296  if ( !stringp(subgroup_name) ) continue;
1297  subgroup_name = lower_case( subgroup_name );
1298  if ( search( subgroups, subgroup_name ) >= 0 ) {
1299  subgroups -= ({ subgroup_name }); // already sub group of group
1300  }
1301  else {
1302  array subgroup_namespaces = subgroup->query_attribute( OBJ_NAMESPACES );
1303  if ( ! arrayp(subgroup_namespaces) || sizeof(subgroup_namespaces) < 1 )
1304  continue;
1305  array tmp_namespaces = copy_value( source_namespaces );
1306  tmp_namespaces -= subgroup_namespaces;
1307  // only remove user from group if they're from the same namespaces:
1308  if ( sizeof(tmp_namespaces) < 1 ) {
1309  object privgroups = GROUP( "Groups" );
1310  if ( objectp(privgroups) ) {
1311  PDEBUG( "removing sub group %s of group %s (moving to Groups)", subgroup_name, identifier );
1312  get_factory( CLASS_GROUP )->move_group( subgroup, privgroups );
1313  }
1314  // what if we cannot move the group to privgroups?
1315  }
1316  }
1317  }
1318  foreach ( subgroups, string subgroup_name ) {
1319  object subgroup = lookup_group( subgroup_name );
1320  PDEBUG( "adding sub group %s to group %s", subgroup_name, identifier );
1321  if ( objectp(subgroup) )
1322  get_factory( CLASS_GROUP )->move_group( subgroup, obj );
1323  }
1324  obj->set_attribute( GROUP_NAMESPACE_GROUPS_CRC, crc32 );
1325  }
1326  else PDEBUG( "crc32 check: group %s sub groups don't seem to have " +
1327  "changed: %O", identifier, subgroups );
1328  }
1329 
1330  // store namespaces that had data:
1331  mixed err = catch {
1332  if ( functionp(obj->set_attribute) )
1333  obj->set_attribute( OBJ_NAMESPACES, source_namespaces );
1334  };
1335  if ( err )
1336  werror( "persistence: synchronize_object() error when trying to store " +
1337  "source namespaces in %O: %O\n%O\n%O\n",
1338  obj->get_identifier(), obj, err[0], err[1] );
1339 
1340  if ( changes ) {
1341  //TODO: update data in the persistence layers if the object has changed
1342  }
1343  return changes;
1344 }
1345 
1346 
1347 int|object load_object(object proxy, int|object iOID)
1348 {
1349  if ( object_program(CALLER) != (program)PROXY )
1350  error("Security Violation - caller not a proxy object !");
1351 
1352  object nid = get_namespace(iOID);
1353  if ( !objectp(nid) ) {
1354  werror("Namespace is not an object: %O (proxy: %O)\n", iOID, proxy );
1355  return UNDEFINED;
1356  }
1357  if ( !functionp(nid->load_object) ) return UNDEFINED;
1358  mixed obj = nid->load_object(proxy, iOID);
1359  if ( !objectp(obj) ) return obj;
1360 
1361  if ( objectp(cache_entry) )
1362  cache_entry->time_loaded = time();
1363  else
1364  PDEBUG( "could not cache object %O : %O", obj->get_identifier(), obj );
1365 
1366  return obj;
1367 }
1368 
1369 mapping get_storage_handlers(object o)
1370 {
1371  if ( !objectp(o) )
1372  return ([ ]);
1373 
1374  mapping m;
1375  mixed err = catch {
1376  m = o->get_data_storage();
1377  };
1378  if ( err ) {
1379  FATAL("Error calling get_data_storage in %O ", o);
1380  FATAL("Error: %O\n%O", err[0], err[1]);
1381  return ([ ]);
1382  }
1383  return m;
1384 }
1385 
1386 mixed call_storage_handler(function f, mixed ... params)
1387 {
1388  mixed res = namespaces[0]->call_storage_handler(f, @params);
1389  return res;
1390 }
1391 
1392 
1393 int check_read_attribute(object user, string attribute)
1394 {
1395  mixed err = catch(user->check_read_attribute(attribute,
1396  geteuid()||this_user()));
1397  if ( err ) {
1398  if ( sizeof(err) == 3)
1399  return 0;
1400  FATAL("Error while checking for readable attribute %O of %O\n%O:%O",
1401  attribute, user, err[0], err[1]);
1402  }
1403  return 1;
1404 }
1405 
1406 protected:
1407  int check_read_user(object user, mapping terms)
1408 {
1409  // check if user data are still ok ?!
1410  array attribute = ({ });
1411  foreach ( indices(terms), string key) {
1412  switch(key) {
1413  case "firstname":
1414  attribute += ({ USER_FIRSTNAME });
1415  break;
1416  case "lastname":
1417  attribute += ({ USER_LASTNAME });
1418  break;
1419  case "login":
1420  attribute += ({ OBJ_NAME });
1421  break;
1422  case "email":
1423  attribute += ({ USER_EMAIL });
1424  break;
1425  }
1426  }
1427  if (sizeof(attribute)>0) {
1428  foreach(attribute, string a)
1429  if ( check_read_attribute(user, a) == 0 )
1430  return 0;
1431  }
1432  return 1;
1433 }
1434 
1435 public:
1436 
1437 
1438 int get_content_size ( int content_id ) {
1439  int content_size;
1440 
1441 private:
1442  if ( store_content_in_filesystem ) {
1443  content_size = Stdio.file_size( _Server->get_sandbox_path() + "/content/" +
1444  ContentFilesystem.content_id_to_path( content_id ) );
1445  if ( content_size >= 0 ) return content_size;
1446  }
1447 
1448 public:
1449 
1450 private:
1451  if ( store_content_in_database ) {
1452  object oHandle = __Database->new_db_file_handle( content_id, "r" );
1453  content_size = oHandle->sizeof();
1454  destruct( oHandle );
1455  }
1456 
1457 public:
1458 
1459  return content_size;
1460 }
1461 
1462 
1463 string get_content ( int content_id, int|void length ) {
1464  string content;
1465 
1466 private:
1467  if ( store_content_in_filesystem ) {
1468  if ( length )
1469  content = Stdio.read_file( _Server->get_sandbox_path() + "/content/" +
1470  ContentFilesystem.content_id_to_path( content_id ), 0, length );
1471  else
1472  content = Stdio.read_file( _Server->get_sandbox_path() + "/content/" +
1473  ContentFilesystem.content_id_to_path( content_id ) );
1474  if ( stringp(content) )
1475  return content;
1476  }
1477 
1478 public:
1479 
1480 private:
1481  if ( store_content_in_database ) {
1482  object db_handle = __Database->new_db_file_handle( content_id, "r" );
1483  content = db_handle->read( length );
1484  destruct( db_handle );
1485  }
1486 
1487 public:
1488  return content;
1489 }
1490 
1491 
1492 int set_content ( string content ) {
1493  object oHandle = __Database->new_db_file_handle( 0, "wct" );
1494  int content_id = oHandle->dbContID();
1495 
1496 private:
1497  if ( store_content_in_filesystem ) {
1498  string path = _Server->get_sandbox_path() + "/content/" +
1499  ContentFilesystem.content_id_to_path( content_id );
1500  Stdio.mkdirhier( dirname( path ) );
1501  Stdio.write_file( path, content );
1502  }
1503 
1504 public:
1505 
1506 private:
1507  if ( store_content_in_database )
1508  oHandle->write_now( content );
1509 
1510  oHandle->close();
1511  destruct( oHandle );
1512  return content_id;
1513 }
1514 
1515 
1516 void delete_content ( int content_id ) {
1517 private:
1518  if ( store_content_in_filesystem )
1519  rm( _Server->get_sandbox_path() + "/content/" +
1520  ContentFilesystem.content_id_to_path( content_id ) );
1521 
1522 private:
1523  if ( store_content_in_database ) {
1524  object oHandle = __Database->new_db_file_handle( content_id, "wct" );
1525  oHandle->close();
1526  }
1527 
1528 public:
1529 }
1530 
1531 
1532 /**
1533  * Open a file, return a file object or zero, called from get_content_file
1534  * in Document.pike
1535  *
1536  * @return an open Stdio.File
1537  */
1538 object open_content_file(int content_id, string mode, void|mapping vars, void|string client)
1539 {
1540 private:
1541  if ( store_content_in_filesystem ) {
1542  mixed err = catch {
1543  return Stdio.File( _Server->get_sandbox_path() + "/content/" +
1544  ContentFilesystem.content_id_to_path( content_id ), mode );
1545  };
1546  if ( err ) PDEBUG( "Couldn't open content file: %s\n", err[0] );
1547  }
1548 
1549 public:
1550  return 0;
1551 }
1552 
1553 
1554 array search_user_names ( mapping terms, bool any, string|void wildcard ) {
1555  array users = ({ });
1556  foreach ( indices(namespaces), mixed idx ) {
1557  object handler = namespaces[idx];
1558  if ( (handler->supported_classes() & CLASS_USER) == 0 )
1559  continue;
1560  if ( functionp(handler->search_users) ) {
1561  array a = handler->search_users( terms, any, wildcard );
1562  if ( !arrayp(a) || sizeof(a) < 1 ) continue;
1563  users += a;
1564  }
1565  }
1566  return Array.uniq( users );
1567 }
1568 
1569 
1570 /**
1571  * Searches for users in the persistence layers.
1572  *
1573  * @param terms a mapping ([ attrib : value ]) where attrib can be "firstname",
1574  * "lastname", "login" or "email" and value is the text ot search for in the
1575  * attribute. If the values contain wildcards, specify the wildcard character
1576  * in the wildcard param.
1577  * @param any true: return all users that match at least one of the terms
1578  * ("Or"), false: return all users that match all of the terms ("and").
1579  * @param wildcard a string containing the wildcard used in the search term
1580  * values, or 0 (or unspecified) if no wildcards are used
1581  * @return an array of matching user objects
1582  */
1583 array lookup_users( mapping terms, bool any, string|void wildcard ) {
1584  array users = ({ });
1585  //foreach ( indices(namespaces), mixed idx ) {
1586  for ( mixed idx = 0; idx == 0; idx ++ ) { // only search in database!!!
1587  object handler = namespaces[idx];
1588  if ( (handler->supported_classes() & CLASS_USER) == 0 )
1589  continue;
1590  if ( functionp(handler->search_users) ) {
1591  array a = handler->search_users( terms, any, wildcard );
1592  if ( arrayp(a) && sizeof(a) > 0 ) {
1593  foreach ( a, string name ) {
1594  object user = lookup_user( name );
1595  if ( objectp(user) &&
1596  search( users, user ) < 0 &&
1597  check_read_user(user, terms))
1598  users += ({ user });
1599  }
1600  }
1601  }
1602  }
1603  return users;
1604 }
1605 
1606 
1607 array search_group_names ( mapping terms, bool any, string|void wildcard ) {
1608  array groups = ({ });
1609  foreach ( indices(namespaces), mixed idx ) {
1610  object handler = namespaces[idx];
1611  if ( (handler->supported_classes() & CLASS_GROUP) == 0 )
1612  continue;
1613  if ( functionp(handler->search_groups) ) {
1614  array a = handler->search_groups( terms, any, wildcard );
1615  if ( !arrayp(a) || sizeof(a) < 1 ) continue;
1616  groups += a;
1617  }
1618  }
1619  return Array.uniq( groups );
1620 }
1621 
1622 
1623 /**
1624  * Searches for groups in the persistence layers.
1625  *
1626  * @param terms a mapping ([ attrib : value ]) where attrib can be "name"
1627  * and value is the text ot search for in the attribute.
1628  * If the values contain wildcards, specify the wildcard character in the
1629  * wildcard param.
1630  * @param any true: return all groups that match at least one of the terms
1631  * ("or"), false: return all groups that match all of the terms ("and").
1632  * @param wildcard a string containing the wildcard used in the search term
1633  * values, or 0 (or unspecified) if no wildcards are used
1634  * @return an array of matching group objects
1635  */
1636 array lookup_groups ( mapping terms, bool any, string|void wildcard ) {
1637  array groups = ({ });
1638  //foreach ( indices(namespaces), mixed idx ) {
1639  for ( mixed idx = 0; idx == 0; idx ++ ) { // only search in database!!!
1640  object handler = namespaces[idx];
1641  if ( (handler->supported_classes() & CLASS_GROUP) == 0 )
1642  continue;
1643  if ( functionp(handler->search_groups) ) {
1644  array a = handler->search_groups( terms, any, wildcard );
1645  if ( arrayp(a) && sizeof(a) > 0 ) {
1646  foreach ( a, string name ) {
1647  object group = lookup_group( name );
1648  if ( objectp(group) && search( groups, group ) < 0 )
1649  groups += ({ group });
1650  }
1651  }
1652  }
1653  }
1654  return groups;
1655 }
1656 
1657 
1658 object lookup ( string identifier ) {
1659  if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1660  if ( has_index( pending_objects, identifier ) ) {
1661  PDEBUG("pending object %s : %O", identifier, pending_objects[identifier]);
1662  return pending_objects[identifier];
1663  }
1664  return lookup_internal( identifier, CLASS_OBJECT );
1665 }
1666 
1667 
1668 object lookup_user ( string identifier, void|string password ) {
1669  if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1670  if ( has_index( pending_users, identifier ) ) {
1671  PDEBUG("pending user %s : %O", identifier, pending_users[identifier]);
1672  return pending_users[identifier];
1673  }
1674  if ( user_restricted( identifier ) )
1675  return namespaces[0]->lookup_user( identifier );
1676  return lookup_internal( identifier, CLASS_USER, password );
1677 }
1678 
1679 
1680 object lookup_group ( string identifier ) {
1681  if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1682  if ( has_index( pending_groups, identifier ) ) {
1683  PDEBUG("pending group %s : %O", identifier, pending_groups[identifier]);
1684  return pending_groups[identifier];
1685  }
1686  if ( group_restricted( identifier ) )
1687  return namespaces[0]->lookup_group( identifier );
1688  return lookup_internal( identifier, CLASS_GROUP );
1689 }
1690 
1691 
1692 protected:
1693  object lookup_internal ( string identifier, int obj_class, void|string password ) {
1694  // check whether object is cached:
1695  array cache_entries = object_cache->find_by_identifier( identifier );
1696  if ( arrayp(cache_entries) ) {
1697  foreach ( cache_entries, object entry ) {
1698  if ( entry->obj_class & obj_class ) {
1699  //TODO: check whether object is "fresh", otherwise don't use cache
1700  object_cache->fetch( entry->proxy );
1701  return entry->proxy;
1702  }
1703  }
1704  }
1705 
1706  // find object in persistence layers:
1707  mixed res = 0;
1708  foreach(indices(namespaces), mixed idx ) {
1709  object handler = namespaces[idx];
1710  if ( (handler->supported_classes() & obj_class) == 0 )
1711  continue;
1712  string lookup_func = "lookup";
1713  string lookup_data_func = "lookup_data";
1714  if ( obj_class & CLASS_USER ) {
1715  lookup_func = "lookup_user";
1716  lookup_data_func = "lookup_user_data";
1717  }
1718  else if ( obj_class & CLASS_GROUP ) {
1719  lookup_func = "lookup_group";
1720  lookup_data_func = "lookup_group_data";
1721  }
1722  if ( functionp(handler[lookup_func]) ) {
1723  mixed tmp_res = handler[lookup_func]( identifier );
1724  if ( objectp(tmp_res) ) {
1725  // only proxies are cached, not the objects themselves:
1726  // do a cache fetch so that the data can be updated if out of date:
1727  object_cache->fetch( res );
1728  return res;
1729  }
1730  }
1731  else if ( functionp( handler[lookup_data_func] ) ) {
1732  if ( !res ) res = handler[lookup_data_func]( identifier );
1733  }
1734  else PDEBUG( "No lookup methods in layer %O (%O)", idx, handler->get_identifier() );
1735  }
1736 
1737  mixed requested_identifier = identifier;
1738  if ( mappingp(res) && stringp(res["name"]) && sizeof(res["name"]) != 0 ) {
1739  if ( stringp(res["parentgroup"]) && sizeof(res["parentgroup"]) != 0 )
1740  identifier = res["parentgroup"] + "." + res["name"];
1741  else
1742  identifier = res["name"];
1743  }
1744  // if we found only partial data, then we need to create a new object:
1745  //TODO: make this configurable! You might not want to automatically create new objects in all layers!
1746  if ( dont_create_users && (obj_class == CLASS_USER) )
1747  return 0;
1748  if ( (obj_class == CLASS_USER) && !user_allowed( identifier ) ) {
1749  PDEBUG("lookup: user not allowed: %O", identifier);
1750  return 0;
1751  }
1752  if ( (obj_class == CLASS_GROUP) && !group_allowed( identifier ) ) {
1753  PDEBUG("lookup: group not allowed: %O", identifier);
1754  return 0;
1755  }
1756  if ( mappingp(res) ) {
1757  PDEBUG( "lookup: found data for %O, creating object %O",
1758  requested_identifier, identifier );
1759  object factory;
1760  if ( !stringp(res["class"]) ||
1761  !objectp(factory = get_factory(res["class"])) ) {
1762  werror( "persistence: cannot create object for %s : invalid class: %O\n",
1763  identifier, res["class"] );
1764  return 0;
1765  }
1766  mapping factory_params = ([ "name":identifier ]) | res;
1767  if ( (factory_params["class"] == CLASS_NAME_USER) &&
1768  !stringp(factory_params["pw"]) &&
1769  !stringp(factory_params["pw:crypt"]) )
1770  factory_params["pw:crypt"] = "{NOPASSWORD}";
1771  if ( (factory_params["class"] == CLASS_NAME_GROUP) &&
1772  stringp(factory_params["parentgroup"]) ) {
1773  factory_params["parentgroup"] = lookup_group( factory_params["parentgroup"] );
1774  if ( !objectp( factory_params["parentgroup"] ) )
1775  return 0;
1776  }
1777  if ( obj_class & CLASS_USER ) pending_users[identifier] = 0;
1778  else if ( obj_class & CLASS_GROUP ) pending_groups[identifier] = 0;
1779  else pending_objects[identifier] = 0;
1780  object obj = factory->execute( factory_params );
1781  if ( obj_class & CLASS_USER ) m_delete( pending_users, identifier );
1782  else if ( obj_class & CLASS_GROUP ) m_delete( pending_groups, identifier );
1783  else m_delete( pending_objects, identifier );
1784  if ( !objectp(obj) ) {
1785  werror( "persistence: failed to create object for %s\n", identifier );
1786  return UNDEFINED;
1787  }
1788  if ( obj_class & CLASS_USER )
1789  obj->activate_user( factory->get_activation() );
1790  if ( mappingp(res["attributes"]) )
1791  obj->set_attributes( res["attributes"] );
1792  // synchronize object for the first time:
1793  object_cache->fetch( obj );
1794  return obj;
1795  }
1796 
1797  if ( !objectp(res) )
1798  res = namespaces[0]->lookup(identifier);
1799  if ( objectp(res) )
1800  object_cache->fetch( res );
1801  return res;
1802 }
1803 
1804 public:
1805 
1806 
1807 protected:
1808  void add_user_to_group ( object user, object group )
1809 {
1810  if ( !objectp(user) || !objectp(group) )
1811  return;
1812 
1813  PDEBUG( "adding user %s to group %s", user->get_identifier(),
1814  group->get_identifier() );
1815  group->add_member( user );
1816 
1817  object user_workroom = user->query_attribute(USER_WORKROOM);
1818  object group_workroom = group->query_attribute(GROUP_WORKROOM);
1819  if ( !objectp(user_workroom) || !objectp(group_workroom) )
1820  return;
1821 
1822  // add exit to the group workroom if it doesn't exist:
1823  if ( get_dont_create_exits() )
1824  return; // configured not to create exits
1825  foreach ( user_workroom->get_inventory_by_class(CLASS_EXIT), object gate ) {
1826  if ( gate->get_exit() == group_workroom )
1827  return; // exit already exists
1828  }
1829  object exit_factory = get_factory(CLASS_EXIT);
1830  if ( !objectp(exit_factory) ) {
1831  werror( "Persistence: Could not get ExitFactory.\n" );
1832  return;
1833  }
1834  object gate = exit_factory->execute( ([
1835  "name":group_workroom->get_identifier(),
1836  "exit_to":group_workroom
1837  ]) );
1838  if ( objectp(gate) )
1839  gate->move( user_workroom );
1840  else
1841  werror( "Persistence: Could not move exit to group workroom to users workarea:\nUser: %O, Group: %O\n", user, group );
1842 }
1843 
1844 public:
1845 
1846 
1847 protected:
1848  void remove_user_from_group ( object user, object group )
1849 {
1850  if ( !objectp(user) || !objectp(group) )
1851  return;
1852 
1853  PDEBUG( "removing user %s from group %s", user->get_identifier(),
1854  group->get_identifier() );
1855  group->remove_member( user ); // not member of group anymore
1856 
1857  // check whether an exit to the group workroom needs to be removed:
1858  object user_workroom = user->query_attribute(USER_WORKROOM);
1859  object group_workroom = group->query_attribute(GROUP_WORKROOM);
1860  if ( !objectp(user_workroom) || !objectp(group_workroom) )
1861  return;
1862  foreach ( user_workroom->get_inventory_by_class(CLASS_EXIT), object gate ) {
1863  if ( gate->get_exit() == group_workroom )
1864  gate->delete();
1865  }
1866 }
1867 
1868 public:
1869 
1870 
1871 final object find_object(int|string iOID)
1872 {
1873  object namespace;
1874  if ( stringp(iOID) )
1875  return namespaces[0]->find_object(iOID);
1876  namespace = get_namespace(iOID);
1877  if ( !objectp(namespace) || !functionp(namespace->find_object) )
1878  return UNDEFINED;
1879  return namespace->find_object(iOID);
1880 }
1881 
1882 
1883 // requires saving an object
1884 void require_save(void|string ident, void|string index)
1885 {
1886  object nid = get_namespace(proxy);
1887  if ( !objectp(nid) )
1888  werror( "require_save: invalid namespace\n" );
1889  if ( !functionp(nid->require_save) )
1890  return;
1891  nid->require_save(proxy, ident, index);
1892 }
1893 
1894 
1895 protected:
1896  void
1897 save_object(object proxy, void|string ident, void|string index)
1898 {
1899 }
1900 
1901 public:
1902 
1903 int change_object_class(object proxy, string newClass)
1904 {
1905  if ( !_Server->is_a_factory(CALLER) )
1906  steam_error("Illegal call to Persistence.change_object_class !");
1907  object nid = get_namespace(proxy);
1908  if ( !objectp(nid) )
1909  steam_error("Persistence.change_object_class failed: no namespace found!");
1910  if ( !functionp(nid->change_object_class) ) {
1911  FATAL("No function change_object_class in %O\n", nid);
1912  return false;
1913  }
1914  return nid->change_object_class(proxy, newClass);
1915 }
1916 
1917 
1918 void user_renamed ( object user, string old_name, string new_name ) {
1919  uncache_object( user );
1920  array errors = ({ });
1921  foreach ( indices(namespaces), mixed idx ) {
1922  if ( idx == 0 ) continue;
1923  object handler = namespaces[idx];
1924  if ( !functionp(handler->user_renamed) ) continue;
1925  handler->user_renamed( user, old_name, new_name );
1926  }
1927 }
1928 
1929 
1930 void group_renamed ( object group, string old_name, string new_name ) {
1931  uncache_object( group );
1932  foreach ( indices(namespaces), mixed idx ) {
1933  if ( idx == 0 ) continue;
1934  object handler = namespaces[idx];
1935  if ( !functionp(handler->group_renamed) ) continue;
1936  handler->group_renamed( group, old_name, new_name );
1937  }
1938 }
1939 
1940 
1941 string get_identifier() { return "PersistenceManager"; }
1942 string describe() { return "PersistenceManager"; }
1943 string _sprintf() { return "PersistenceManager"; }
1944 
1945 
1946 };